Java类加载过程分为加载、Linking、初始化、使用和卸载五个阶段,其中Linking阶段是加载阶段之后、初始化阶段之前的关键环节,负责将类的二进制数据合并到虚拟机的运行时环境中,确保类的合法性并为后续运行做准备。

Linking阶段的核心作用
Linking阶段的核心目标是验证类的正确性,为类变量分配内存并设置初始值,同时将类中的符号引用转换为直接引用。整个过程分为验证、准备、解析三个独立步骤,每个步骤承担不同的职责,共同保障类能够被虚拟机正确执行。
验证过程:确保类的合法性
验证是Linking阶段的第一个步骤,目的是保证加载的类的二进制数据符合Java虚拟机规范,不会危害虚拟机的安全。验证过程主要包含四个方面的检查:
文件格式验证
验证字节码文件是否符合Class文件格式规范,比如魔数是否为0xCAFEBABE,主次版本号是否在当前虚拟机支持的范围内,常量池中的常量类型是否合法等。如果文件格式验证失败,虚拟机会抛出java.lang.VerifyError异常。
元数据验证
对类的元数据信息进行语义校验,比如这个类是否有父类(除了java.lang.Object之外所有类都应该有父类),是否继承了不允许被继承的final类,类中的方法是否和父类的方法冲突等。这一步确保类的语义符合Java语言规范。
字节码验证
通过数据流分析和控制流分析,校验类的方法体中的字节码是否合法。比如保证任意时刻操作数栈的数据类型和指令代码序列都能配合工作,不会跳转到不存在的字节码指令位置,方法体中的类型转换是有效的等。这一步是最复杂的验证环节,很多虚拟机都会采用类型推导等优化手段来减少验证开销。
符号引用验证
验证类中对其他类、方法、字段的符号引用是否合法,比如符号引用指向的类是否能被找到,对应的方法和字段是否存在,访问权限是否足够等。这一步的验证通常和解析过程一起进行,如果符号引用验证失败,会抛出java.lang.IncompatibleClassChangeError等异常。
准备过程:为类变量分配内存
准备阶段的任务是为类变量(被static修饰的变量)分配内存并设置初始值,这些内存都在方法区中分配。需要注意的是,这里的初始值是数据类型的零值,而不是代码中显式赋的值。
比如下面的类定义:
public class PrepareTest {
// 准备阶段会为value分配内存,初始值设为0,而不是123
private static int value = 123;
// 被final修饰的常量在准备阶段就会直接赋值为代码中的值,这里初始值就是456
private static final int CONST_VALUE = 456;
}
对于普通的static变量value,准备阶段只会设置初始值为0,123的赋值操作会在初始化阶段执行;而对于static final的基本类型或String常量,由于是编译期常量,准备阶段就会直接赋值为定义的值。
解析过程:符号引用转直接引用
解析阶段是虚拟机将常量池中的符号引用替换为直接引用的过程,符号引用是用一组符号来描述引用的目标,直接引用是可以直接指向目标在内存中的地址的指针、偏移量或者句柄。
符号引用与直接引用的区别
符号引用和虚拟机的内存布局无关,引用的目标不一定已经加载到内存中;直接引用和虚拟机的内存布局相关,引用的目标一定已经存在于内存中。
解析的主要对象
解析过程主要针对类或接口、字段、方法、接口方法、方法类型、方法句柄和调用点限定符这7类符号引用进行,分别对应常量池中的CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等类型的常量。
比如下面的代码中,调用另一个类的方法就属于符号引用:
public class ResolveTest {
public void test() {
// 这里的OtherClass和otherMethod都是符号引用,解析阶段会将其转换为直接引用
OtherClass other = new OtherClass();
other.otherMethod();
}
}
class OtherClass {
public void otherMethod() {
System.out.println("执行其他方法");
}
}
在解析阶段,虚拟机会先检查OtherClass是否已经被加载,如果没有加载会先触发其加载过程,然后将OtherClass的符号引用替换为指向其Class对象的直接引用,同样otherMethod的符号引用也会替换为指向方法数据的直接引用。
Linking阶段的注意事项
Linking阶段的三个步骤并不是严格按顺序执行的,解析阶段在某些情况下可以在初始化阶段之后再开始,这是为了支持Java的动态绑定特性。另外,验证阶段虽然很重要,但是可以通过-Xverify:none参数来关闭大部分验证,加快虚拟机的类加载速度,不过这种方式只适合在开发测试环境使用,生产环境不建议关闭验证。
理解Linking阶段的工作过程,有助于我们排查类加载相关的异常,比如java.lang.NoSuchMethodError、java.lang.IllegalAccessError等,这些异常大多和Linking阶段的验证、解析过程有关。