Java双亲委派模型
大家好,今天咱们来聊 Java 类加载的核心灵魂机制 —— 双亲委派模型。不管是面试问 JVM 底层原理,还是实际开发中遇到类加载冲突、自定义类加载器问题,双亲委派模型都是绕不开的关键。
一、先搞懂:什么是类加载器?
要理解双亲委派模型,得先知道类加载器(ClassLoader) 是什么。
简单说,类加载器就是负责把 .class 字节码文件加载进 JVM 内存,生成 java.lang.Class 对象的工具。
Java 提供了三层默认类加载器,从顶层到底层依次为:
| 类加载器类型 | 全称 | 加载范围 | 核心作用 |
|---|---|---|---|
| 引导类加载器 | BootstrapClassLoader |
JVM 核心类库(rt.jar、jre/lib 下的核心类) |
加载 Java 最基础的核心类(如 java.lang.String、java.lang.Object),由 C++ 实现,是所有类加载器的顶层 |
| 平台类加载器 | PlatformClassLoader |
JDK 扩展类库(jre/lib/ext 下的类) |
加载扩展功能的类,承上启下 |
| 应用类加载器 | AppClassLoader |
项目自身的类路径(classpath 下的类,包括我们写的代码、第三方依赖) |
加载我们日常开发的业务类,是程序中最常用的类加载器 |
还有用户自定义类加载器:我们可以继承 ClassLoader 类,自定义加载逻辑(比如加载加密的 class 文件、加载网络上的类)。

二、双亲委派模型:核心定义与原理
1. 什么是双亲委派模型?
双亲委派模型是 Java 类加载的核心机制,核心规则一句话概括:
当一个类加载器收到类加载请求时,不会自己先去加载,而是先委托给父类加载器去加载;只有父类加载器无法加载时,才尝试自己加载。
这里的 “双亲” 不是指父类子类的继承关系,而是委托层级关系(引导类加载器 → 平台类加载器 → 应用类加载器 → 自定义类加载器)。
2. 核心执行流程(面试必背)
双亲委派的执行过程分为两步,逻辑非常清晰:
第一步:自下向上委托(检查是否已加载)
类加载器收到加载请求后,先不加载,而是向上委托,直到顶层的引导类加载器:
- 用户自定义类加载器收到请求 → 委托给
AppClassLoader AppClassLoader收到请求 → 委托给PlatformClassLoaderPlatformClassLoader收到请求 → 委托给BootstrapClassLoader
关键作用:自底向上检查类是否已经被加载过。如果父类加载器已经加载过该类,直接返回已加载的 Class 对象,避免重复加载(保证类的唯一性)。
第二步:自顶向下尝试加载(安全加载)
如果顶层的引导类加载器无法加载(比如不是核心类),则向下委托,让下层类加载器尝试加载:
BootstrapClassLoader加载失败 →PlatformClassLoader尝试加载PlatformClassLoader加载失败 →AppClassLoader尝试加载AppClassLoader加载失败 → 用户自定义类加载器尝试加载- 所有加载器都加载失败 → 抛出
ClassNotFoundException
关键作用:保护核心类不被篡改。比如核心类 java.lang.String,只能由引导类加载器加载,自定义类加载器无法加载,避免恶意类替换 JDK 核心类,实现沙箱安全机制。
3. 通俗理解:双亲委派的逻辑
可以把类加载器想象成公司的审批流程:
- 你(自定义类加载器)想加载一个类,先找部门经理(
AppClassLoader) - 经理再找总监(
PlatformClassLoader) - 总监再找老板(
BootstrapClassLoader) - 老板说:“这个是公司核心类,我来加载”→ 完成加载
- 如果老板说 “这不是我管的”→ 总监来加载
- 总监说 “管不了”→ 经理来加载
- 经理说 “也加载不了”→ 你自己来加载
- 最后都加载不了→ 报错(找不到类)
三、代码实战:验证双亲委派模型
我们通过代码验证一下双亲委派的执行逻辑,核心是看类加载器的名称。
1. 基础验证:获取系统类的类加载器
/**
* 验证核心类的类加载器(双亲委派的体现)
*/
public class ClassLoaderTest {
public static void main(String[] args) {
// 获取 String 类的类加载器(核心类,由 BootstrapClassLoader 加载)
ClassLoader stringClassLoader = String.class.getClassLoader();
// 注意:BootstrapClassLoader 是 C++ 实现的,Java 中获取不到,会返回 null
System.out.println("String 类的类加载器:" + stringClassLoader); // 输出 null
// 获取当前类的类加载器(我们自己写的类,由 AppClassLoader 加载)
ClassLoader currentClassLoader = ClassLoaderTest.class.getClassLoader();
System.out.println("当前类的类加载器:" + currentClassLoader); // 输出 sun.misc.Launcher$AppClassLoader@xxx
// 获取当前类加载器的父类加载器(PlatformClassLoader)
ClassLoader parentClassLoader = currentClassLoader.getParent();
System.out.println("当前类加载器的父类:" + parentClassLoader); // 输出 sun.misc.Launcher$PlatformClassLoader@xxx
}
}
2. 自定义类加载器:验证委托逻辑
我们自定义一个类加载器,重写 loadClass 方法,验证双亲委派的委托过程:
import java.io.InputStream;
/**
* 自定义类加载器,验证双亲委派模型
*/
public class CustomClassLoader extends ClassLoader {
// 自定义类加载器的名称
private String name;
public CustomClassLoader(String name) {
this.name = name;
}
/**
* 重写 loadClass 方法,打印类加载过程
*/
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
// 如果加载的是核心类(以 java. 开头),直接委托给父类加载器
if (name.startsWith("java.")) {
return super.loadClass(name);
}
// 打印当前类加载器的加载过程
System.out.println(this.name + " 收到加载请求:" + name);
// 1. 先检查是否已经加载过该类
Class<?> loadedClass = findLoadedClass(name);
if (loadedClass != null) {
System.out.println(this.name + " 已加载过该类,直接返回");
return loadedClass;
}
// 2. 委托给父类加载器(双亲委派的核心)
try {
if (getParent() != null) {
System.out.println(this.name + " 委托给父类加载器:" + getParent().getClass().getSimpleName());
return getParent().loadClass(name);
} else {
// 父类是 BootstrapClassLoader,直接尝试加载核心类
System.out.println(this.name + " 委托给引导类加载器");
return findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 父类加载失败,尝试自己加载
System.out.println(this.name + " 父类加载失败,尝试自己加载");
return findClass(name);
}
}
/**
* 自定义类加载逻辑:从文件中读取 class 字节码
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
// 模拟从本地文件读取 class 字节码(这里简化为读取自身类的字节码)
String path = name.replace('.', '/') + ".class";
InputStream is = getClass().getResourceAsStream(path);
if (is == null) {
throw new ClassNotFoundException("类找不到:" + name);
}
// 读取字节码
byte[] bytes = is.readAllBytes();
// 定义类
Class<?> clazz = defineClass(name, bytes, 0, bytes.length);
System.out.println(this.name + " 成功加载类:" + name);
return clazz;
} catch (Exception e) {
throw new ClassNotFoundException("加载失败", e);
}
}
// 测试
public static void main(String[] args) throws ClassNotFoundException {
// 创建自定义类加载器
CustomClassLoader customLoader = new CustomClassLoader("自定义类加载器");
// 加载自定义类(注意类名要和当前类一致,这里测试加载自身)
Class<?> clazz = customLoader.loadClass("com.example.CustomClassLoader");
// 输出加载后的类的类加载器
System.out.println("加载后的类的类加载器:" + clazz.getClassLoader());
}
}
运行结果分析
- 加载
java.lang.String时,直接委托给父类,由引导类加载器加载(返回 null)。 - 加载我们自定义的类时,会先委托给
AppClassLoader,加载成功后返回。 - 验证了先委托、后自己加载的双亲委派核心逻辑。
四、双亲委派模型的核心优势(面试必问)
1. 避免类重复加载,保证类的唯一性
通过自底向上的委托检查,同一个类只会被一个类加载器加载一次。比如 java.lang.String 只会被引导类加载器加载,不会被其他类加载器重复加载,保证了 JVM 核心类的稳定性。
2. 保护核心类,防止恶意篡改
核心类(以 java. 开头)只能由引导类加载器加载,自定义类加载器无法加载这些核心类,避免了恶意代码替换 JDK 核心类(比如自定义一个 java.lang.String 类来窃取数据),实现了沙箱安全机制。
3. 提升类加载效率
提前加载过的类会被缓存,再次加载时直接返回缓存的 Class 对象,无需重复加载,提升了程序的运行效率。
五、双亲委派模型的破坏与场景(进阶了解)
双亲委派模型不是绝对的,也有被破坏的场景,主要分为主动破坏和被动破坏:
1. 主动破坏:自定义类加载器重写 loadClass
如果我们自定义类加载器时,重写 loadClass 方法,不遵循先委托、后加载的逻辑,而是直接自己加载,就会破坏双亲委派。
比如 Tomcat 等 Web 容器的类加载器:为了实现热部署、隔离不同 Web 应用的类,Tomcat 自定义了多个类加载器,打破了双亲委派模型,实现了不同 Web 应用的类隔离。
2. 被动破坏:SPI 机制(JDK 核心类加载第三方实现)
JDK 的 SPI 机制(如 JDBC、Spring 工厂加载机制)会破坏双亲委派。
以 JDBC 为例:
- 核心类
java.sql.Driver由引导类加载器加载。 - 但第三方数据库驱动(如
com.mysql.cj.jdbc.Driver)是由应用类加载器加载的。 - 引导类加载器无法加载第三方类,于是 JDK 提供了
Thread.getContextClassLoader()线程上下文类加载器,来突破双亲委派的限制,加载第三方类。
3. 被动破坏:热部署、动态代理
一些框架(如 Spring Boot DevTools)的热部署功能,会自定义类加载器,动态加载修改后的类,打破了双亲委派模型。
六、面试高频考点总结(背会直接拿分)
1. 核心问题(必背)
- 双亲委派模型的核心规则是什么?收到加载请求时,先自下向上委托父类加载器,直到顶层引导类加载器;父类无法加载时,再自顶向下尝试加载。
- 双亲委派模型的作用?避免类重复加载,保证类的唯一性;保护核心类不被篡改,实现沙箱安全;提升类加载效率。
- Java 中的三层类加载器分别是什么?各自加载什么类?引导类加载器(加载核心类
rt.jar)、平台类加载器(加载扩展类jre/lib/ext)、应用类加载器(加载项目类classpath)。 - 为什么 BootstrapClassLoader 获取不到?因为它是由 C++ 实现的,不是 Java 类,所以 Java 中获取它的引用会返回
null。
2. 进阶问题
- 双亲委派模型的破坏场景有哪些?自定义类加载器重写
loadClass、SPI 机制(如 JDBC)、热部署、动态代理。 - Tomcat 为什么要破坏双亲委派模型?为了隔离不同 Web 应用的类,避免类冲突,实现热部署。
七、总结
双亲委派模型是 Java 类加载的基石,核心逻辑可以概括为:
自下向上委托检查 → 自顶向下尝试加载
它的核心价值是保证类的唯一性和核心类的安全性。理解了双亲委派模型,你就掌握了 JVM 类加载的核心逻辑,不管是面试还是实际开发中遇到类加载相关问题,都能轻松应对!
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)