一、双亲委派机制核心概述
双亲委派机制(Parent Delegation Model)是 Java 类加载器的核心协作规则,也是 JVM 保障类加载安全、有序的核心设计。其核心思想可通俗理解为:「类加载请求先 "向上找爸爸"(委托父加载器),爸爸搞不定了,儿子再自己动手」------ 即子加载器收到类加载请求时,优先委托父加载器加载,仅当父加载器无法完成加载时,子加载器才自行加载。
该机制在 Java 17 中保留核心逻辑,但对类加载器层级做了调整(移除扩展类加载器,替换为平台类加载器),同时强化了与模块系统(JPMS)的融合。
二、双亲委派机制工作原理
1. Java 17 类加载器层级(核心调整点)
Java 17 简化并规范了类加载器层级,相比 Java 8 移除了「扩展类加载器(Extension ClassLoader)」,核心层级如下:
tex
启动类加载器(Bootstrap ClassLoader)→
平台类加载器(Platform ClassLoader)→
应用类加载器(App ClassLoader)→
自定义类加载器
各加载器的核心职责:
| 加载器类型 | 所属层面 | 加载范围 | Java 17 特性说明 |
|---|---|---|---|
| 启动类加载器(Bootstrap) | JVM 底层(C++) | java.base 等核心模块(如 rt.jar、jrt-fs.jar 中的核心类,如 java.lang.String) |
无对应的 Java 对象,getClassLoader() 返回 null |
| 平台类加载器(Platform) | JDK 内置(Java) | JDK 平台模块(如 java.sql、java.xml 等非核心模块) |
替代 Java 8 的扩展类加载器,管控平台级 API |
| 应用类加载器(App) | JDK 内置(Java) | 应用程序的 classpath 路径下的所有类(用户编写的业务类) |
也叫系统类加载器(System ClassLoader) |
| 自定义类加载器 | 用户自定义 | 自定义路径下的类(如热部署、类隔离场景) | 需重写 findClass(),推荐注册并行加载能力 |
2. 双亲委派完整工作流程
以「自定义类加载器请求加载 com.dwl.Demo」为例,流程如下:
-
请求委托:自定义类加载器收到加载请求,不立即处理,先委托给父加载器(应用类加载器);
-
递归向上:应用类加载器继续委托给父加载器(平台类加载器),平台类加载器再委托给顶层的启动类加载器;
-
自上而下尝试加载:
- 启动类加载器检查自身加载范围:若能加载(如核心类),直接返回
Class对象;若不能,通知子加载器(平台类加载器); - 平台类加载器检查自身加载范围:能加载则返回,不能则通知子加载器(应用类加载器);
- 应用类加载器检查
classpath:能加载则返回,不能则通知子加载器(自定义类加载器); - 自定义类加载器检查自定义路径:能加载则返回,不能则抛出
ClassNotFoundException。
- 启动类加载器检查自身加载范围:若能加载(如核心类),直接返回
3. 工作流程可视化
- 委托加载请求 2. 委托加载请求 3. 委托加载请求 4. 检查是否可加载
如 rt.jar 核心类 能
不能
检查平台模块/ext 目录
能
不能
检查 classpath 路径
能
不能
检查自定义路径
能
不能
自定义类加载器
Custom ClassLoader
应用类加载器
App ClassLoader
平台类加载器
Platform ClassLoader
启动类加载器
Bootstrap ClassLoader
能否加载?
返回已加载的 Class 对象
通知子加载器尝试加载
能否加载?
通知子加载器尝试加载
能否加载?
通知子加载器尝试加载
能否加载?
抛出 ClassNotFoundException
4. 核心原则
- 单向委托:仅子委托父,父不委托子(反向委托需特殊机制,如 SPI 服务加载);
- 父优先加载 :核心类(如
java.lang.String)必须由顶层加载器加载,防止篡改; - 类唯一性:同一类仅被「首次成功加载的加载器」缓存,避免重复加载(不同加载器加载同一类视为不同类)。
三、代码演示(Java 17 可直接运行)
所有示例均基于 Java 17 编写,需确保:
① JDK 17 环境;
② 代码路径与包名一致;
③ 自定义路径需提前创建并放入编译后的 .class 文件。
演示 1:核心类加载流程(验证双亲委派优先级)
功能 :查看 java.lang.String 和自定义类的加载器,验证层级关系。
java
package com.dwl.ex01_类加载子系统;
/**
* @Description
* @Version 1.0.0
* @Date 2026/2/9 2:09
* @Author Dwl
*/
public class CoreClassLoadingDemo {
public static void main(String[] args) {
// 1. 获取核心类(String)的加载器:启动类加载器无Java对象,返回null
ClassLoader stringClassLoader = String.class.getClassLoader();
System.out.println("1. java.lang.String 的加载器: " + stringClassLoader);
System.out.println(" 说明:null 代表启动类加载器(Bootstrap ClassLoader)\n");
// 2. 获取当前类的加载器:应用类加载器
ClassLoader appClassLoader = CoreClassLoadingDemo.class.getClassLoader();
System.out.println("2. 当前类(CoreClassLoadingDemo)的加载器: " + appClassLoader);
System.out.println(" 说明:Java 17 中显示为 jdk.internal.loader.ClassLoaders$AppClassLoader\n");
// 3. 打印完整的类加载器层级
System.out.println("3. 类加载器层级(双亲委派链):");
printClassLoaderHierarchy(appClassLoader);
}
/**
* 递归打印类加载器层级(从当前加载器到顶层)
*
* @param loader 起始类加载器
*/
private static void printClassLoaderHierarchy(ClassLoader loader) {
int level = 0;
while (loader != null) {
System.out.println(" " + "→ ".repeat(level + 1) + loader);
loader = loader.getParent();
level++;
}
// 启动类加载器无Java对象,单独标注
System.out.println(" " + "→ ".repeat(level + 1) + "启动类加载器(Bootstrap ClassLoader,C++实现)");
}
}
运行结果:

关键说明 :启动类加载器由 C++ 实现,不属于 Java 对象,因此 getClassLoader() 返回 null,这是 Java 17 与其他版本一致的特性。
演示 2:禁止自定义核心包类(安全保护)
功能 :验证双亲委派机制阻止自定义 java.lang 包下的类,防止核心 API 被篡改。
步骤 1:尝试定义 java.lang.String(编译期报错)
java
package java.lang;
/**
* @Description javac -encoding UTF-8 --patch-module java.base=F:\idea-workspase\JVM-Explorer\JDK17 "-XDignore.symbol.file"
* F:\idea-workspase\JVM-Explorer\JDK17\java\lang\String.java
* @Version 1.0.0
* @Date 2026/2/9 2:22
* @Author Dwl
*/
public class String {
static {
System.out.println("自定义 java.lang.String 静态代码块");
}
// 核心修正:显式指定参数为原生 java.lang.String[]
public static void main(java.lang.String[] args) {
System.out.println("自定义 String 类运行成功!");
}
}
编译错误信息:

步骤 2:尝试通过自定义加载器加载(运行期拦截)
java
package com.dwl.ex01_类加载子系统;
import java.nio.file.Files;
import java.nio.file.Paths;
/**
* @Description 类加载安全拦截演示
* 演示如何触发 JVM 对 java.lang 包的安全保护机制(SecurityException)
* 原理说明:
* 1. JVM 默认通过「双亲委派模型」保护 java.lang 核心包,自定义类加载器默认无法加载该包下的类
* 2. 重写 loadClass 方法破坏双亲委派后,自定义类加载器会尝试加载 java.lang.String,
* 此时 JVM 在 defineClass 阶段会检测到非法的 java.lang 包名,抛出安全异常
* 注意:该代码仅用于学习演示,生产环境绝对禁止破坏双亲委派/操作 java.lang 包
* @Version 1.0.0
* @Date 2026/2/9 2:21
* @Author Dwl
*/
public class ForbiddenClassLoadDemo {
/**
* 主方法:执行自定义类加载器加载 java.lang.String 的测试
*
* @param args 命令行参数(未使用)
* @throws Exception 捕获类加载过程中的所有异常(简化演示)
*/
public static void main(String[] args) throws Exception {
// 1. 创建自定义类加载器,指定自定义 java.lang.String.class 文件的存放根路径
// 路径说明:该路径下需存在 "java/lang/String.class" 自定义类文件
CustomClassLoader loader = new CustomClassLoader("F:/idea-workspase/JVM-Explorer/JDK17/java/lang");
try {
// 2. 尝试加载自定义的 java.lang.String 类
// 注:默认 loadClass 会触发双亲委派,重写后会跳过父加载器直接加载
Class<?> forbiddenClass = loader.loadClass("java.lang.String");
// 3. 若加载成功(实际不会走到这一步),打印加载的类名
System.out.println("加载的类: " + forbiddenClass.getName());
} catch (Exception e) {
System.err.println("加载失败(安全拦截): " + e.getMessage());
}
}
/**
* 自定义类加载器(核心逻辑)
* 作用:重写 loadClass 破坏双亲委派,强制加载自定义路径下的 java.lang.String
* 父类:继承自 JDK 核心类 ClassLoader
*/
static class CustomClassLoader extends ClassLoader {
// 自定义类文件的根路径(存放编译后的 .class 文件)
private final String classPath;
/**
* 自定义类加载器构造方法
*
* @param classPath 自定义 .class 文件的根路径
*/
public CustomClassLoader(String classPath) {
// 显式指定父加载器为「应用类加载器(AppClassLoader)」
// 注:ClassLoader 默认父加载器就是 AppClassLoader,此处显式指定是为了清晰
super(ClassLoader.getSystemClassLoader());
this.classPath = classPath;
}
/**
* 重写 loadClass 方法,破坏「双亲委派模型」
* 双亲委派默认逻辑:先委托父加载器加载 → 父加载器加载失败才自己加载
* 重写后逻辑:跳过父加载器,直接调用自身的 findClass 方法加载
*
* @param name 要加载的类全限定名(如 java.lang.String)
* @param resolve 是否解析类(解析:验证/准备/解析类的符号引用,此处按需开启)
* @return 加载后的 Class 对象
* @throws ClassNotFoundException 类加载失败时抛出
*/
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
// 类加载锁:保证类加载的线程安全(JVM 规范要求)
synchronized (getClassLoadingLock(name)) {
// 第一步:检查该类是否已被当前类加载器加载过(缓存检查)
Class<?> c = findLoadedClass(name);
// 第二步:若未加载过,跳过父加载器,直接调用自身 findClass 加载(破坏双亲委派的核心)
if (c == null) {
// 核心:不调用 super.loadClass,跳过父加载器
c = findClass(name);
}
// 第三步:若需要解析类,则执行解析操作(可选,演示场景下不影响核心逻辑)
if (resolve) {
resolveClass(c);
}
// 返回加载后的 Class 对象
return c;
}
}
/**
* 重写 findClass 方法:实现自定义的类加载逻辑
* 作用:从指定路径读取 .class 文件字节数组,调用 defineClass 定义类
* 注:defineClass 是触发 JVM 安全拦截的核心方法
*
* @param name 要加载的类全限定名
* @return 加载后的 Class 对象
* @throws ClassNotFoundException 读取文件/定义类失败时抛出
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// 打印日志:验证该方法是否被执行(破坏双亲委派后才会执行)
System.out.println("自定义类加载器的 findClass 执行了!目标类:" + name);
try {
// 1. 拼接类文件路径:将类全限定名的 "." 替换为 "/",拼接 .class 后缀
// 例如:java.lang.String → java/lang/String.class
String className = name.replace('.', '/') + ".class";
// 2. 读取自定义路径下的 .class 文件为字节数组
byte[] classBytes = Files.readAllBytes(Paths.get(classPath, className));
// 3. 调用父类的 defineClass 方法:将字节数组转换为 Class 对象
// 核心触发点:JVM 会检查类的包名,若为 java.lang 则抛出 SecurityException
return defineClass(name, classBytes, 0, classBytes.length);
} catch (Exception e) {
throw new ClassNotFoundException("类加载失败:" + name, e);
}
}
}
}
运行错误信息(Java 17 安全拦截):

核心结论 :Java 17 从「编译期 + 运行期」双层拦截自定义 java.* 核心包类,彻底杜绝核心 API 被篡改。
演示 3:自定义加载器加载普通类(正常流程)
功能:演示自定义加载器遵循双亲委派机制,加载非核心类的完整流程。
步骤 1:待加载的普通类
java
package com.dwl;
/**
* 普通业务类:用于演示自定义加载器加载
*/
public class Demo {
// 静态代码块:类加载时执行,用于验证加载时机
static {
System.out.println("[Demo 类] 已被加载(静态代码块执行)");
}
public void hello() {
System.out.println("[Demo 类] Hello from Demo!");
}
}
步骤 2:带日志追踪的自定义加载器
java
package com.dwl.ex01_类加载子系统;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
/**
* @Description 演示:自定义类加载器遵循双亲委派机制加载普通类
* @Version 1.0.0
* @Date 2026/2/9 17:41
* @Author Dwl
*/
public class CustomClassLoaderDemo {
public static void main(String[] args) throws Exception {
// 前置条件:将 Demo.class 文件放入以下自定义路径(需替换为自己的路径)
String customClassPath = "F:\\idea-workspase\\JVM-Explorer\\JDK17\\java";
// 1. 创建自定义类加载器
CustomClassLoader customLoader = new CustomClassLoader(customClassPath);
// 2. 打印加载器层级信息
System.out.println("===== 加载器层级信息 =====");
System.out.println("自定义加载器: " + customLoader);
System.out.println("自定义加载器的父加载器(AppClassLoader): " + customLoader.getParent());
System.out.println("AppClassLoader 的父加载器(PlatformClassLoader): " + customLoader.getParent().getParent());
System.out.println("PlatformClassLoader 的父加载器(Bootstrap,null): " + customLoader.getParent().getParent().getParent());
System.out.println("========================\n");
// 3. 触发类加载(遵循双亲委派:先父后子)
Class<?> demoClass = customLoader.loadClass("com.dwl.Demo");
// 4. 验证加载结果
System.out.println("\n===== 最终加载结果 =====");
System.out.println("Demo 类的实际加载器: " + demoClass.getClassLoader());
}
/**
* 自定义类加载器
* 核心:重写 loadClass 保持双亲委派,重写 findClass 实现自定义加载逻辑
*/
static class CustomClassLoader extends ClassLoader {
private final String classPath;
/**
* 构造器:指定父加载器 + 注册并行加载能力
*
* @param classPath 自定义类文件的根路径
*/
public CustomClassLoader(String classPath) {
super(ClassLoader.getSystemClassLoader()); // 父加载器为应用类加载器
this.classPath = classPath;
registerAsParallelCapable(); // 注册并行加载能力,提升多线程加载性能
}
/**
* 重写 loadClass:严格遵循双亲委派逻辑,增加日志追踪流程
*/
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
// 加锁:保证类加载的线程安全(JVM 类加载器规范要求)
synchronized (getClassLoadingLock(name)) {
System.out.printf("[自定义加载器] 处理类加载请求:%s%n", name);
// 第一步:检查类是否已缓存(避免重复加载)
Class<?> loadedClass = findLoadedClass(name);
if (loadedClass != null) {
System.out.printf("[自定义加载器] 类已缓存,直接返回:%s%n", name);
if (resolve) {
resolveClass(loadedClass);
}
return loadedClass;
}
try {
// 第二步:委托父加载器加载(双亲委派核心:父优先)
System.out.printf("[自定义加载器] 委托父加载器(%s)加载:%s%n",
getParent().getClass().getSimpleName(), name);
loadedClass = super.loadClass(name, false);
System.out.printf("[自定义加载器] 父加载器加载成功:%s%n", name);
} catch (ClassNotFoundException e) {
// 第三步:父加载器加载失败,自定义加载器自行加载
System.out.printf("[自定义加载器] 父加载器加载失败,尝试自定义加载:%s%n", name);
loadedClass = findClass(name);
System.out.printf("[自定义加载器] 自定义加载成功:%s%n", name);
}
// 解析类(链接阶段,可选)
if (resolve) {
resolveClass(loadedClass);
}
return loadedClass;
}
}
/**
* 自定义加载核心逻辑:从指定路径读取 .class 文件并定义类
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
// 拼接类文件路径:包名转路径 + .class 后缀
String classFilePath = classPath + "/" + name.replace('.', '/') + ".class";
System.out.printf("[自定义加载器] 读取类文件:%s%n", classFilePath);
// 读取字节码
byte[] classBytes = Files.readAllBytes(Paths.get(classFilePath));
// 定义类:将字节码转换为 Class 对象(ClassLoader 核心 API)
return defineClass(name, classBytes, 0, classBytes.length);
} catch (IOException e) {
throw new ClassNotFoundException("自定义加载器读取类文件失败:" + name, e);
}
}
}
}
运行结果 :

关键说明:
- 若
classpath中存在该类,父加载器(AppClassLoader)会优先加载,自定义加载器不会执行findClass; - 若
classpath中不存在,自定义加载器才会从指定路径加载,符合「父优先」的双亲委派规则。
四、Java 17 中双亲委派的核心优势
- 核心 API 安全防护 :彻底阻止自定义
java.*包类,避免核心类(如String、Object)被恶意篡改,是 JVM 安全沙箱的基础; - 避免类重复加载:父加载器加载过的类,子加载器直接复用缓存,减少内存占用,保证类的唯一性;
- 适配模块系统(JPMS):启动类加载器加载核心模块、平台加载器加载平台模块、应用加载器加载用户模块,实现模块隔离;
- 并行加载优化 :Java 17 支持
registerAsParallelCapable()注册并行加载能力,提升多线程场景下的类加载效率。
五、总结(核心要点)
- 双亲委派核心逻辑 :子加载器先委托父加载器加载,父加载器无法加载时,子加载器通过
findClass()自定义加载,核心是「父优先、防篡改」; - Java 17 关键调整:移除扩展类加载器,替换为平台类加载器,强化模块系统融合,推荐自定义加载器注册并行加载能力;
- 安全核心 :任何尝试覆盖
java.*核心包的行为,都会被编译期 / 运行期拦截,抛出SecurityException,这是双亲委派机制的核心价值; - 实践要点 :自定义类加载器需重写
findClass()(而非loadClass()),避免破坏双亲委派规则,保证类加载的规范性。