虚拟机类加载机制
定义: 虚拟机把代表类的二进制流(常见格式为class文件)读入到内存中,转化为方法区的运行时数据结构, 对数据进行验证, 准备, 解析和初始化, 最终形成可以被虚拟机直接使用的java类型;
-
类的加载过程
,类的生命周期
与对象的生命周期
之间的关系
-
类加载的时机(主动引用):
- 遇到
new
getstatic
putstatic
或invokestatic
这四条字节码指令时, 如果类没有进行初始化, 则需要先触发其初始化; - 使用
java.lang.reflect
包的方法对类进行反射调用的时候, 如果类没有进行过初始化, 则需要先触发其初始化; - 当初始化一个类的时候, 若其父类还没有进行过初始化, 则需要先触发其父类的初始化(类与接口在该点上有区别);
- 虚拟机启动时, 用户指定一个要执行的主类(包含main() 方法的那个类), 虚拟机会先初始化这个主类;
- 若
java.lang.invoke.MethodHandle
实例最后的解析结果是
REF_getStatic
,REF_putStatic
,REF_invokeStatic
的方法句柄, 并且该方法句柄所对应的类没有进行初始化, 则需要先触发其初始化;
- 遇到
被动应用的例子
public class SuperClass {
static {
System.out.println("SuperClass init!");
}
public static int value = 123;
}
public class SubClass extends SuperClass {
static {
System.out.println("SubClass init!");
}
}
public class NonInitializationOne {
public static void main(String[] args) {
System.out.println(SubClass.value);
}
}
结果:
SuperClass init!
123
结论: 对于静态字段, 只有直接定义这个字段的类才会被初始化
public class NonInitializationTwo {
public static void main(String[] args) {
SuperClass[] superClasses = new SuperClass[10];
}
}
结果: 无任何输出
分析: 上述代码触发了名为 [LSuperClass
的类的初始化阶段, 没有触发 SuperClass
类的初始化阶段;
结论: 数组类 是由虚拟机自动生成的, 直接继承于 java.lang.Object
的子类, 创建动作由字节码指令 newarray
触发;
public class ConstClass {
static {
System.out.println("ConstClass init!");
}
public static final String HELLO_WORLD = "hello world";
}
public class NonInitializationThree {
public static void main(String[] args) {
System.out.println(ConstClass.HELLO_WORLD);
}
}
结果:
hello world
分析: 在编译阶段通过常量传播优化, 将常量值 hello world
存储到 NonInitializationThree
类的常量池中, 故实际上
NonInitializationThree.class 文件之中并没有 ConstClass
类的符号引用入口;
类加载过程解析
1. 装载(loading)
虚拟机在装载阶段的任务为:
1). 通过一个类的 全限定名
来获取定义此类的 二进制字节流
;
2). 将这个字节流所代表的 静态存储结构
转化为 方法区的运行时数据结构
;
3). 在内存中生成一个代表这个类的 java.lang.Class
对象, 作为方法区这个类的各种数据的访问入口;
2. 验证(verification)
验证
的作用: 确保 Class文件
的字节流中包含的信息符合当前虚拟机的要求, 并且不会危害虚拟机自身的安全;
阶 段 | 作 用 |
---|---|
文件格式验证 | 保证输入的字节流能正确地解析并存储于方法区之内 |
元数据验证 | 对类的元数据信息进行语义校验, 保证元数据信息符合java语言规范 |
字节码验证 | 对类的方法进行校验分析, 保证被校验类的方法在运行时不会做出危害虚拟机安全的事件 |
符号引用验证 | 在解析阶段发生, 对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验, 保证解析能正常执行 |
3. 准备(preparation)
准备阶段
的作用: 为 类变量 分配内存并设置 类变量初始值 的阶段
注意: 这里的 类变量初始值 指的是 "通常情况" 下数据类型的零值;
特殊情况: 类字段的字段属性表中存在
ConstantValue
属性, 则在准备阶段 变量 就会被初始化为ConstantValue
属性所指定的值;
4. 解析(resolution)
解析阶段
是 虚拟机将常量池内的符号引用 替换为 直接引用 的过程
符号引用(symbolic references)
: 以一组符号来描述所引用的目标, 符号可以是任何形式的字面量, 只要使用时能无歧义地定位到目标即可, 符号引用与虚拟机实现的内存布局无关;
直接引用(direct references)
: 是直接指向目标的指针, 相对偏移量或一个能间接定位到目标的句柄, 直接引用与虚拟机实现的内存布局相关;
解析
的类型:
类或接口的解析
字段的解析
类方法的解析
接口方法的解析
方法类型, 方法句柄 和 调用点限定符 的解析
5. 初始化(initialization)
初始化阶段
是执行 类构造器<clinit>() 方法的过程;
<clinit>() 方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static {} 块) 中的语句合并产生的, 编译器收集的顺序是由语句在源文件中出现的顺序所决定的, 静态语句块中只能访问到定义在静态语句块之前的变量, 定义在它之后的变量在静态语句块中可以被赋值但不可以被访问;
注意: 当多个线程同时去初始化一个类, 则只会有一个线程去执行这个类的
<clinit>()
方法, 其他线程都需要阻塞等待;