类加载过程分为加载、连接、初始化,连接过程又可分为验证、准备、解析。
加载
使用类加载器将class文件加载到JVM中,加载阶段会将类的元数据(方法、字段、类名等)存储到方法区,然后在堆中生成Class对象,Class对象持有引用,指向方法区中的类的元信息。
连接
验证
验证字节码的文件格式、语法、内容等,确保字节码符合JVM规范。
验证内容:
- 文件格式验证:魔数(
0xCAFEBABE
)、版本号等。 - 元数据验证:类是否有父类、是否继承 final 类等语义检查。
- 字节码验证:确保指令不会导致 JVM 崩溃(如操作数栈溢出)。
- 符号引用验证:检查引用的类、方法、字段是否存在。
准备
为静态变量分配内存,并设置初始值。
准备阶段会给静态变量设置初始值,如下:
给b设置一个初始值0,初始化阶段再赋值为10;
对于基本类型和String的常量,会直接在准备阶段设置为我们赋予的值,c赋值为20,d赋值为"hello"。
对于其余引用类型的常量会在初始化阶段赋值
解析
将常量池内的符号引用替换为直接引用。
符号引用是类的元数据(字段、方法、类名等)的字符串,存储在常量池中。
直接引用是类的元数据的内存地址。
初始化
调用clinit方法对静态代码、静态代码块进行初始化。
对于初始化阶段,虚拟机严格规范了有且有 6 种情况下,必须对类进行初始化(只有主动去使用类才会初始化类):
- 当遇到
new
、getstatic
、putstatic
或invokestatic
这 4 条字节码指令时,比如new
一个类,读取一个静态字段(未被 final 修饰)、或调用一个类的静态方法时。- 当 jvm 执行
new
指令时会初始化类。即当程序创建一个类的实例对象。 - 当 jvm 执行
getstatic
指令时会初始化类。即程序访问类的静态变量(不是静态常量,常量会被加载到运行时常量池)。 - 当 jvm 执行
putstatic
指令时会初始化类。即程序给类的静态变量赋值。 - 当 jvm 执行
invokestatic
指令时会初始化类。即程序调用类的静态方法。
- 当 jvm 执行
- 使用
java.lang.reflect
包的方法对类进行反射调用时如Class.forName("...")
,newInstance()
等等。如果类没初始化,需要触发其初始化。 - 初始化一个类,如果其父类还未初始化,则先触发该父类的初始化。
- 当虚拟机启动时,用户需要定义一个要执行的主类 (包含
main
方法的那个类),虚拟机会先初始化这个类。 MethodHandle
和VarHandle
可以看作是轻量级的反射调用机制,而要想使用这 2 个调用,
就必须先使用findStaticVarHandle
来初始化要调用的类。