JVM之类加载过程

一、类加载完整五阶段流程

加载 → 验证 → 准备 → 解析 → 初始化

1. 加载(Load)

  1. 通过类的全限定名获取二进制字节流(class 文件 / 网络 / 动态生成)
  2. 将字节流存储的静态结构转为方法区运行时数据结构
  3. 在内存堆中生成该类的java.lang.Class对象,作为方法区数据的访问入口

2. 验证(Verify)

保证加载的 Class 文件字节码合法、安全,防止恶意代码破坏 JVM

  • 文件格式验证:魔数、版本号、结构规范
  • 元数据验证:类继承、权限、字段方法合理性
  • 字节码验证:指令逻辑、跳转、栈操作合法性
  • 符号引用验证:外部引用的类 / 方法 / 字段是否可访问

3. 准备(Prepare)

  • 类静态变量分配内存(方法区)
  • 给静态变量设置默认初始值(0、null、false)
  • 注意:static final常量会直接赋字面量真实值,不走默认值

4. 解析(Resolve)

把常量池中的符号引用,替换为直接指向内存的直接引用

包含:类、字段、方法、接口方法、方法类型、句柄引用解析

5. 初始化(Init)

类加载最后一步,主动执行类静态代码块 + 静态变量显式赋值

  • 触发时机:首次 new 对象、调用静态方法 / 静态变量、反射、子类初始化、启动主类
  • 严格保证单例、线程安全,由 JVM 保证并发初始化锁

二、类加载器分类

启动类加载器(Bootstrap ClassLoader)

  • C++ 实现,JVM 内置
  • 加载 JAVA_HOME/lib 核心基础类(Object、String、集合核心类)
  • 无父加载器

扩展类加载器(Extension ClassLoader)

  • Java 实现,继承ClassLoader
  • 加载 JAVA_HOME/lib/ext 扩展 jar 包

应用类加载器(Application ClassLoader / 系统类加载器)

  • 默认日常使用的加载器
  • 加载项目 class、第三方依赖 jar
  • 是自定义类加载器默认父加载器

三、双亲委派模型

1. 核心原理

层层向上委托,顶层优先加载

  1. 收到类加载请求,先不自己加载
  2. 向上委托给父类加载器处理
  3. 递归直到启动类加载器
  4. 顶层无法加载时,再向下回调由子加载器加载

2. 核心作用

  1. 沙箱安全:防止核心类被篡改(如自定义 java.lang.String)
  2. 类全局唯一:保证全环境同一份类只加载一次,避免重复加载
  3. 统一类加载优先级,规范加载规则

3. 破坏双亲委派场景

  1. Tomcat

    每个 Web 应用独立类加载器,隔离不同项目依赖版本,打破委派,优先自己加载

  2. SPI 服务提供者接口

    核心接口由启动类加载器加载,实现类由应用加载器加载,通过线程上下文类加载器反向委派

  3. OSGi

    模块化热部署,按需动态加载、卸载类,完全自定义加载逻辑

  4. 热部署、热修复

    自定义加载器重新加载 class,实现不停机更新


示列代码

1. SPI 破坏双亲委派:代码示例(最经典)

原理

  • 接口:java.sql.Driver启动类加载器 加载
  • 实现类:com.mysql.cj.jdbc.Driver应用类加载器 加载
  • 启动类加载器无法加载应用层类,所以破坏双亲委派,用线程上下文类加载器(TCCL)反向加载

极简运行代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class SpiDemo {
public static void main(String[] args) throws SQLException {
// SPI 核心:启动类加载器加载的 DriverManager,能加载应用层的 MySQL 驱动
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test");

// 打印当前类的类加载器
System.out.println("DriverManager 类加载器:" + DriverManager.class.getClassLoader()); // null(启动类加载器)
System.out.println("MySQL 驱动类加载器:" + conn.getClass().getClassLoader()); // 应用类加载器
}
}

底层破坏代码(JDK 源码简化版)

1
2
3
4
5
6
7
8
9
10
public class DriverManager {
// 核心:用 线程上下文类加载器 打破双亲委派
private static void loadInitialDrivers() {
// 获取线程上下文类加载器(= 应用类加载器)
ClassLoader cl = Thread.currentThread().getContextClassLoader();

// 启动类加载器 反向委托 应用类加载器 加载驱动实现类
ServiceLoader.load(Driver.class, cl);
}
}

结论

启动类加载器 → 委托 → 应用类加载器

= 反向委派 = 破坏双亲委派


2. Tomcat 破坏双亲委派:代码模拟

原理

  • 每个 Web 应用一个独立 WebAppClassLoader
  • 优先自己加载,不交给父类 → 打破双亲委派
  • 目的:不同项目用不同版本 Spring/MyBatis 互不干扰

模拟 Tomcat 类加载器(核心代码)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class TomcatWebAppClassLoader extends ClassLoader {
// 打破双亲委派:重写 loadClass,先自己加载,不向上抛
@Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
// 1. JDK 核心类(java.xxx)必须交给启动类加载器,防止篡改
if (name.startsWith("java.")) {
return getParent().loadClass(name);
}

// 2. 自己先加载(打破双亲委派的关键!)
try {
return findClass(name); // 自己加载 WEB-INF/classes 或 lib
} catch (ClassNotFoundException e) {
// 3. 自己找不到,再交给父类(共享类加载器)
return getParent().loadClass(name);
}
}

// 读取 class 文件字节码
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] bytes = loadClassBytes(name); // 从 WEB-INF 读取
return defineClass(name, bytes, 0, bytes.length);
}
}

关键点

正常双亲委派:先父后子

Tomcat 加载器:先子后父

→ 这就是打破双亲委派


3. 自定义类加载器(热部署 / 热修复):完整可运行代码

用途

  • 热更新 class
  • 加密类加载
  • 隔离类
  • 模块化卸载

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import java.io.FileInputStream;
import java.io.IOException;

// 自定义类加载器
public class HotDeployClassLoader extends ClassLoader {
private final String classPath;

public HotDeployClassLoader(String classPath) {
this.classPath = classPath;
}

// 重写 findClass,不破坏双亲委派(如需破坏可重写 loadClass)
@Override
protected Class<?> findClass(String className) throws ClassNotFoundException {
try {
// 1. 读取磁盘上的 class 文件字节码
String path = classPath + className.replace(".", "/") + ".class";
FileInputStream fis = new FileInputStream(path);
byte[] bytes = new byte[fis.available()];
fis.read(bytes);
fis.close();

// 2. 将 byte[] 转为 Class 对象
return defineClass(className, bytes, 0, bytes.length);
} catch (IOException e) {
throw new ClassNotFoundException(className);
}
}

// 测试热部署
public static void main(String[] args) throws Exception {
// 第一次加载
HotDeployClassLoader loader1 = new HotDeployClassLoader("./classes/");
Class<?> clazz1 = loader1.loadClass("com.test.UserService");
System.out.println("Class1 = " + clazz1);
System.out.println("ClassLoader1 = " + clazz1.getClassLoader());

// 修改 class 文件后,第二次加载(热部署)
HotDeployClassLoader loader2 = new HotDeployClassLoader("./classes/");
Class<?> clazz2 = loader2.loadClass("com.test.UserService");
System.out.println("Class2 = " + clazz2);
System.out.println("ClassLoader2 = " + clazz2.getClassLoader());

// clazz1 != clazz2 热部署成功!
System.out.println("两个类是否相同:" + (clazz1 == clazz2));
}
}

热部署原理

  • 一个类加载器只能加载一次类
  • 热部署 = 新建类加载器 + 重新加载
  • JVM 判定类唯一:类全限定名 + 类加载器实例

4. OSGi 模块化(极简原理)

OSGi 是彻底自定义类加载机制

每个模块(Bundle)一个类加载器,模块间依赖手动声明,可动态安装 / 启动 / 停止 / 卸载。

核心逻辑(伪代码)

1
2
3
4
5
public class BundleClassLoader extends ClassLoader {
// 只加载自己模块的 class
// 只导入声明过的依赖包
// 运行时可卸载
}

特点:无双亲委派,全自定义,热插拔

四、自定义类加载器

1. 实现规范

  • 继承 ClassLoader
  • **不要重写 loadClass ()**(会破坏双亲委派)
  • 只重写 findClass(String name)
  • 在 findClass 中:读取 class 字节数组 → 调用defineClass()生成 Class 对象

2. 极简核心结构

1
2
3
4
5
6
7
8
9
public class CustomClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String className) throws ClassNotFoundException {
// 1. 读取外部class文件/网络字节码 转为byte[]
byte[] bytes = getClassBytes(className);
// 2. 调用defineClass生成类
return defineClass(className, bytes, 0, bytes.length);
}
}

3. 适用场景

  • 加密 class 解密加载
  • 自定义路径加载类
  • 热部署、代码热更新
  • 隔离依赖包版本

极简背诵版总结

  1. 类加载五步:加载→验证→准备→解析→初始化
  2. 三类加载器:启动类、扩展类、应用类
  3. 双亲委派:向上委托、父优先加载,作用:安全 + 类唯一
  4. 破坏场景:Tomcat、SPI、OSGi、热部署
  5. 自定义加载器:重写findClass,不改动双亲委派逻辑

JVM之类加载过程
http://hanqichuan.com/2019/07/23/jvm/JVM之类加载过程/
作者
韩启川
发布于
2019年7月23日
许可协议