《Java 底层原理》Jvm 类的加载原理

前言

一直想好好的了解一下JVM,这次就来一起了解一下JVM是如何实现类的加载过程的。

原理

类加载的生命周期

1. 加载

5种类加载情况:

  • 在遇到 new、putstatic、getstatic、invokestatic 字节码指令时,如果类尚未初始化,则需要先触发初始化。
  • 对类进行反射调用时,如果类还没有初始化,则需要先触发初始化。
  • 初始化一个类时,如果其父类还没有初始化,则需要先初始化父类。
  • 虚拟机启动时,用于需要指定一个包含 main() 方法的主类,虚拟机会先初始化这个主类。
  • 当使用 JDK 1.7 的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果为 REF_getStatic、REF_putStatic、REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类还没初始化,则需要先触发初始化。

因为Java类加载是不关心这个文件来自哪里?

所有Java的对象可以来自Jar,war,网络,CGlib,数据库等等。

使用到对象才会加载。

2. 验证

  • 文件格式验证
  • 元数据验证
  • 字节码验证
  • 符号引用验证

3. 准备

为静态变量分配内存、为基础数据类型赋初值,

如果被final修饰,在编译的时候会给属性添加ConstantValue属性,准备阶段直接完成赋值操作,无需赋初值。

4. 解析

间接引用转直接引用。

间接引用:指向常量池。

直接引用:指向内存地址。

5. 初始化

执行静态代码段(静态代码块,静态变量):clinit

测试代码写了一个静态变量b 通过字节码文件也能清楚的看到信息。

静态代码的执行顺序和定义的顺序一致,改点可以通过案例说明。

public class JvmTest1 {

    public static void main(String[] args) {
        Test1 test1 = Test1.getTest();
        System.out.println(Test1.val1);
        System.out.println(Test1.val2);
    }
}

class Test1{
    public static int val1 ;

    Test1(){
        val1++;
        val2++;
    }

    public static Test1 instance = new Test1();

    public static int val2 = 1 ;    // 该代码放在在初始化代码

    public static Test1 getTest(){
        return instance;
    }
}

运行结果:

运行结果是1和1 ,造成这个结果的原因就是静态代码的顺序执行,先创建了对象,对象中构造函数执行了, 后面再执行 public static int val2 = 1 ,值就被覆盖回去了。

6. 使用

对象已经初始化完成,这个对象就可以再其他地方被使用。

7. 卸载

销毁之前加载的对象信息。

Jvm里面有一个类状态的枚举:

JVM类加载细节

1. jvm类加载会加锁,防止类加载出现资源争用的情况。

通过一个案例证明一下:

public class JvmTest3 {

    public static void main(String[] args) {
        new Thread(() -> {
            while(true){
                new AA();
            }

        }).start();

        new Thread(() -> {
            while(true) {
                new BB();
            }
        }).start();
    }
}

class AA{
    static {
        System.out.println("创建AA对象");
        new BB();
    }
}

class BB{
    static {
        System.out.println("创建BB对象");
        new AA();
    }
}

运行结果:

执行被锁,无法继续执行。

2. Jvm加载对象属于懒加载(对象没有使用的时候,不会去加载该对象),下面通过案例说明:

public class JvmTest2 {

    public static void main(String[] args) {
        System.out.println(B.str);
    }
}

class A {
    static String str = "a";
    static {
        System.out.println("static a");
    }
}

class B extends A {
    static {
        System.out.println("static b");
    }
}

运行结果:

可以明显的看到B的static方法没有被执行,因为这段逻辑中,不需要是到B这个对象,哪怕B对象是A对象的子类。

总结

Jvm 类加载是核心逻辑之一,非常重要,对一个变成人员的提升也是非常的有帮助。

posted @ 2021-01-23 12:36  加速丨世界  阅读(304)  评论(0编辑  收藏  举报