前言
一直想好好的了解一下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 classJvmTest1 {public static voidmain(String[] args) {
Test1 test1=Test1.getTest();
System.out.println(Test1.val1);
System.out.println(Test1.val2);
}
}classTest1{public static intval1 ;
Test1(){
val1++;
val2++;
}public static Test1 instance = newTest1();public static int val2 = 1 ; //该代码放在在初始化代码
public staticTest1 getTest(){returninstance;
}
}
运行结果:
运行结果是1和1 ,造成这个结果的原因就是静态代码的顺序执行,先创建了对象,对象中构造函数执行了, 后面再执行 public static int val2 = 1 ,值就被覆盖回去了。
6. 使用
对象已经初始化完成,这个对象就可以再其他地方被使用。
7. 卸载
销毁之前加载的对象信息。
Jvm里面有一个类状态的枚举:
JVM类加载细节
1. jvm类加载会加锁,防止类加载出现资源争用的情况。
通过一个案例证明一下:
public classJvmTest3 {public static voidmain(String[] args) {new Thread(() ->{while(true){newAA();
}
}).start();new Thread(() ->{while(true) {newBB();
}
}).start();
}
}classAA{static{
System.out.println("创建AA对象");newBB();
}
}classBB{static{
System.out.println("创建BB对象");newAA();
}
}
运行结果:
执行被锁,无法继续执行。
2. Jvm加载对象属于懒加载(对象没有使用的时候,不会去加载该对象),下面通过案例说明:
public classJvmTest2 {public static voidmain(String[] args) {
System.out.println(B.str);
}
}classA {static String str = "a";static{
System.out.println("static a");
}
}class B extendsA {static{
System.out.println("static b");
}
}
运行结果:
可以明显的看到B的static方法没有被执行,因为这段逻辑中,不需要是到B这个对象,哪怕B对象是A对象的子类。
总结
Jvm 类加载是核心逻辑之一,非常重要,对一个变成人员的提升也是非常的有帮助。