🔹 JVM 类加载的五个阶段(常说三个:加载、链接、初始化)
严格来说有 五个阶段,其中 链接 又分为三个子阶段:
1.加载(Loading)
通过类的全限定名(package + className),由类加载器(ClassLoader)查找并加载类的字节码文件(.class)。
常见的类加载器:
Bootstrap ClassLoader(启动类加载器):加载 JDK 核心类库
rt.jar
、java.base
等。Extension ClassLoader(扩展类加载器):加载
jre/lib/ext
或java.ext.dirs
目录下的类。Application ClassLoader(应用类加载器):加载
classpath
下的类。用户自定义 ClassLoader。
最终将
.class
字节流读入内存,在方法区生成对应的 运行时类结构(Class 对象),并在堆中创建java.lang.Class
实例。
2. 链接(Linking)
链接 = 验证 + 准备 + 解析
验证(Verification)
确保字节码文件的正确性与安全性,避免非法操作。
例如:栈数据不会溢出、方法调用合法、类型转换安全。
验证不通过会抛出
VerifyError
。准备(Preparation)
为类的 静态变量(static field) 分配内存,并赋予 默认值(而不是赋程序员写的值)。
例如:
public static int a = 10;
在 准备阶段,
a
的值是 0(默认 int 值),赋值为 10 会在 初始化阶段执行。解析(Resolution)
将常量池中的符号引用(Symbolic Reference,例如 "java/lang/String")转换为 直接引用(Direct Reference,例如内存地址指针、方法表索引)。
3. 初始化(Initialization)
执行类构造器
<clinit>()
方法(由编译器自动收集所有static {}
块和静态变量赋值语句组成)。JVM 保证 类初始化是线程安全的,即同一个类的
<clinit>()
方法在多线程下只会被执行一次。触发类初始化的时机:
使用
new
实例化对象调用类的静态方法
访问类的静态变量(非 final 常量)
反射
Class.forName()
JVM 启动时加载主类(含
main
方法的类)
4. 使用(Using)
类被加载到内存并初始化后,可以被正常使用。
例如:创建对象、调用方法、访问字段。
5. 卸载(Unloading)
类的生命周期结束后,卸载出 JVM。
卸载条件:
该类的所有对象实例都已被回收。
该类的 ClassLoader 已被回收。
对应的
java.lang.Class
对象不再被引用。一般只在 自定义类加载器 + 动态加载/卸载类(如 Tomcat 热部署、OSGi 框架) 时常见。
🔹 类加载的双亲委派机制
当一个类加载器收到类加载请求时,不会自己去加载,而是先委托给父类加载器。
如果父类加载器无法完成(找不到该类),才由子类加载器尝试加载。
优点:
避免重复加载类。
保证核心类库的安全(用户自定义的
java.lang.String
不会覆盖 JDK 自带的 String)。
🔹 总结(流程图式)
类加载过程: 加载 → 链接(验证 → 准备 → 解析) → 初始化 → 使用 → 卸载