JVM 类加载过程
一、加载(Loading)
目标:把字节码文件(.class)“读入 JVM”,生成类的 “半成品”(Class
对象)。
- Bootstrap ClassLoader(启动类加载器):
- 负责加载 JVM 核心类库(如
java.lang
包),用 C++ 实现(不同 JVM 有差异),无对应的 Java 类。
- 负责加载 JVM 核心类库(如
- Platform ClassLoader(平台类加载器):
- 加载 Java 标准库扩展(如
java.sql
、javax
包),JDK 9 后从Extension ClassLoader
改名而来。
- 加载 Java 标准库扩展(如
- App ClassLoader(应用类加载器):
- 加载项目自己写的类、第三方库(classpath 路径下的类),是开发中最常用的加载器。
类加载器(不同 JDK 版本的差异)
- 低版本 JDK
- 启动类加载器:负责加载
rt.jar
(包含 Java 核心类库)中的类。 - 扩展类加载器:负责加载
ext
目录下的扩展类库中的类。 - 系统类加载器(
System ClassLoader
):负责加载classpath
中程序员自己编写的类。
- 启动类加载器:负责加载
- 高版本 JDK(9+,引入模块化思想)
- 启动类加载器:仍负责加载 JDK 核心类库。
- 平台类加载器:替代低版本的扩展类加载器,负责加载 Java 平台扩展的非核心类。
- 应用类加载器:替代低版本的系统类加载器,负责加载
classpath
中的类。 - 变化:
rt.jar
和ext
目录消失,类库被拆分为多个模块(.jmod
文件),不同模块由不同类加载器加载。
流程:类加载器按 双亲委派机制 工作(优先让父加载器尝试加载,保证核心类不被篡改),最终找到字节码文件,读入内存并生成 Class
对象,存入方法区。
二、链接(Linking)
目标:把 “半成品类” 变成可执行的 “成品”,拆成 验证、准备、解析 三步:
1. 验证(Verify)
- 检查字节码是否符合 JVM 规范(比如魔数是否是
0xCAFEBABE
、语法是否合法),防止恶意 / 错误字节码搞崩 JVM。
2. 准备(Prepare)
- 给类的静态变量分配内存 + 设置默认值(比如
static int num = 10
,准备阶段会先设num = 0
,真正赋值在初始化阶段)。 - 注意:静态常量(
static final
)直接赋 “用户写的值”(比如static final int num = 10
,准备阶段就会设num = 10
)。
3. 解析(Resolve)
- 把符号引用替换成直接引用:比如代码里写
Object obj = new Object()
,编译后是 “符号引用”(类似 “找名为Object
的类”),解析阶段会换成内存地址(直接引用),让 JVM 真正能找到对应的类。
三、初始化(Initialization)
目标:执行类的静态代码块、给静态变量赋 “用户写的值”(比如 static int num = 10
在这里真正赋值为 10
)。
- 触发时机:首次用类的静态成员、创建对象、反射调用等(遵循 主动使用规则 )。
- 流程:按代码顺序执行静态变量赋值、静态代码块,完成后类才算真正 “可用”。
关键总结
- 类加载器:用 “双亲委派” 保证类加载安全,避免核心类被篡改。
- 链接阶段:验证字节码合法性 → 给静态变量分配内存 → 把符号引用转成内存地址。
- 初始化:执行静态逻辑,给静态变量赋最终值,让类真正 “激活”。
类加载器获取
(1)方式 1:通过当前类的 getClassLoader()
获取
ClassLoader appClassLoader = ReflectTest.class.getClassLoader();
- 逻辑:
ReflectTest.class
拿到当前类的Class
对象,调用getClassLoader()
,获取 “加载当前类的类加载器”。- 因
ReflectTest
是项目 classpath 内的自定义类,加载它的就是 应用类加载器。
- 作用:验证 “自定义类由应用类加载器加载”。
(2)方式 2:通过 ClassLoader.getSystemClassLoader()
获取
ClassLoader appClassLoader2 = ClassLoader.getSystemClassLoader();
- 逻辑:
ClassLoader
是类加载器基类,静态方法getSystemClassLoader()
直接返回 应用类加载器。- 这是 Java 提供的 “直接获取应用类加载器” 的标准写法。
- 作用:更直接获取应用类加载器,与方式 1 对比,结果一致。
(3)方式 3:通过线程上下文类加载器获取
ClassLoader appClassLoader3 = Thread.currentThread().getContextClassLoader();
System.out.println("应用类加载器:" + appClassLoader3);
- 逻辑:
- 每个 Java 线程默认有 “上下文类加载器(Context ClassLoader)”,默认就是 应用类加载器。
- 通过
Thread.currentThread()
拿到当前线程,调用getContextClassLoader()
获取。
- 作用:框架(如 Tomcat )中常用,可动态修改上下文类加载器,实现复杂加载逻辑(此处演示默认情况 )。