一、类加载完整五阶段流程
加载 → 验证 → 准备 → 解析 → 初始化
1. 加载(Load)
- 通过类的全限定名获取二进制字节流(class 文件 / 网络 / 动态生成)
- 将字节流存储的静态结构转为方法区运行时数据结构
- 在内存堆中生成该类的
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. 核心原理
层层向上委托,顶层优先加载
- 收到类加载请求,先不自己加载
- 向上委托给父类加载器处理
- 递归直到启动类加载器
- 顶层无法加载时,再向下回调由子加载器加载
2. 核心作用
- 沙箱安全:防止核心类被篡改(如自定义 java.lang.String)
- 类全局唯一:保证全环境同一份类只加载一次,避免重复加载
- 统一类加载优先级,规范加载规则
3. 破坏双亲委派场景
Tomcat
每个 Web 应用独立类加载器,隔离不同项目依赖版本,打破委派,优先自己加载
SPI 服务提供者接口
核心接口由启动类加载器加载,实现类由应用加载器加载,通过线程上下文类加载器反向委派
OSGi
模块化热部署,按需动态加载、卸载类,完全自定义加载逻辑
热部署、热修复
自定义加载器重新加载 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 { Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test"); System.out.println("DriverManager 类加载器:" + DriverManager.class.getClassLoader()); 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 { @Override public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { if (name.startsWith("java.")) { return getParent().loadClass(name); }
try { return findClass(name); } catch (ClassNotFoundException e) { return getParent().loadClass(name); } }
@Override protected Class<?> findClass(String name) throws ClassNotFoundException { byte[] bytes = loadClassBytes(name); 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; }
@Override protected Class<?> findClass(String className) throws ClassNotFoundException { try { String path = classPath + className.replace(".", "/") + ".class"; FileInputStream fis = new FileInputStream(path); byte[] bytes = new byte[fis.available()]; fis.read(bytes); fis.close();
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());
HotDeployClassLoader loader2 = new HotDeployClassLoader("./classes/"); Class<?> clazz2 = loader2.loadClass("com.test.UserService"); System.out.println("Class2 = " + clazz2); System.out.println("ClassLoader2 = " + clazz2.getClassLoader());
System.out.println("两个类是否相同:" + (clazz1 == clazz2)); } }
|
热部署原理
- 一个类加载器只能加载一次类
- 热部署 = 新建类加载器 + 重新加载
- JVM 判定类唯一:
类全限定名 + 类加载器实例
4. OSGi 模块化(极简原理)
OSGi 是彻底自定义类加载机制
每个模块(Bundle)一个类加载器,模块间依赖手动声明,可动态安装 / 启动 / 停止 / 卸载。
核心逻辑(伪代码)
1 2 3 4 5
| public class BundleClassLoader extends ClassLoader { }
|
特点:无双亲委派,全自定义,热插拔
四、自定义类加载器
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 { byte[] bytes = getClassBytes(className); return defineClass(className, bytes, 0, bytes.length); } }
|
3. 适用场景
- 加密 class 解密加载
- 自定义路径加载类
- 热部署、代码热更新
- 隔离依赖包版本
极简背诵版总结
- 类加载五步:加载→验证→准备→解析→初始化
- 三类加载器:启动类、扩展类、应用类
- 双亲委派:向上委托、父优先加载,作用:安全 + 类唯一
- 破坏场景:Tomcat、SPI、OSGi、热部署
- 自定义加载器:重写
findClass,不改动双亲委派逻辑