双亲委派模型是类加载器的协作规则,非设计模式,其核心是loadClass()中“先父后己”的强制约定;Bootstrap、Platform/App等加载器无继承关系,仅通过显式调用实现委托;破坏它需谨慎,否则触发SecurityException或LinkageError。

双亲委派模型不是设计模式,是类加载器的协作规则
它本质是一套「先让父类加载器试加载,失败才轮到自己」的强制约定,不是 Java 语言层面的语法或接口,而是 ClassLoader.loadClass() 方法里写死的逻辑。JVM 启动时内置的 BootstrapClassLoader、ExtensionClassLoader(Java 8)或 PlatformClassLoader(Java 9+)、AppClassLoader 构成层级,但它们之间**没有继承关系**——只是在 loadClass() 中显式调用父加载器的 loadClass()。
常见错误现象:NoClassDefFoundError 或 ClassNotFoundException 出现在自定义类加载器场景下,往往是因为绕过了双亲委派(比如重写了 loadClass() 却没调用 super.loadClass()),导致 java.lang.String 这类核心类被重复加载或加载失败。
- 所有标准类加载器(包括你继承
URLClassLoader写的)默认都遵守双亲委派,除非你主动破坏它 -
ClassLoader.defineClass()只负责把字节码转成Class对象,不触发双亲委派;真正触发的是loadClass() - Java 9 引入模块系统后,
PlatformClassLoader和AppClassLoader都是jdk.internal.loader.ClassLoaders的静态内部类,不再暴露为 public 类型
为什么必须用双亲委派防篡改?关键在 defineClass() 的包级保护
JVM 在 defineClass() 阶段会校验:如果要定义的类属于 java.* 包(或 javax.* 等受保护包),且当前加载器不是 BootstrapClassLoader,就直接抛 SecurityException。也就是说,哪怕你写了个恶意的 java.util.ArrayList 字节码,用自定义类加载器去 defineClass(),也会被拦住。
这个机制依赖双亲委派才能生效:因为你的自定义加载器在调用 loadClass("java.util.ArrayList") 时,会一路委托到 BootstrapClassLoader,它用本地 C++ 实现,从 rt.jar(Java 8)或 modules(Java 9+)里加载真实类,根本不会走到你的 defineClass()。
立即学习“Java免费学习笔记(深入)”;
- 破坏双亲委派(比如重写
loadClass()并跳过super.loadClass())后,若又尝试加载java.*类,会直接失败,不是因为“加载不到”,而是 JVM 主动拒绝定义 -
ClassLoader.findSystemClass()是留给子加载器向 Bootstrap 请求类的后门,但它只对系统类有效,且不能绕过包保护 - 自定义类加载器想加载非
java.*的第三方库(如com.example.MyUtil),双亲委派反而能避免重复加载,提升启动速度
哪些场景必须破坏双亲委派?Thread.getContextClassLoader() 是关键开关
典型场景是 SPI(如 JDBC 驱动加载)、OSGi、热部署、模块化容器。它们需要「让子加载器加载的类,能被父加载器加载的代码使用」,而双亲委派天然做不到这点——父加载器看不到子加载器加载的类。
解决方式不是删掉双亲委派,而是「换一个委托起点」:JDBC 的 ServiceLoader 默认用 Thread.currentThread().getContextClassLoader() 去加载驱动类,这个 CL 通常是 AppClassLoader 或更下层的自定义加载器,从而打破父优先的僵局。
- Tomcat 的每个 WebAppClassLoader 先自己加载
/WEB-INF/classes,再委托给父加载器——这是「反向双亲委派」,但仅限于应用类,对java.*仍严格委托 - 破坏时务必保留对
java.*和javax.*的委托,否则可能触发LinkageError或SecurityException -
ClassLoader.getSystemClassLoader()返回的是AppClassLoader,但它不是所有线程的上下文加载器;线程创建时继承父线程的上下文 CL,主线程默认是 AppClassLoader,但线程池或框架可能重设
Java 9+ 模块系统如何影响双亲委派?ModuleLayer 和 ModuleFinder 接管了类定位
模块路径(--module-path)替代了类路径(-cp),类加载不再只看「能不能找到 class 文件」,还要看「这个模块是否导出该包」「当前模块是否读取了它」。双亲委派依然存在,但委托链多了模块解析环节。
例如:你用自定义加载器加载一个模块 JAR,它必须通过 ModuleFinder 找到,再由 ModuleLayer.defineModules() 构建层结构,最后类加载才走传统委托。如果模块未导出某包,即使类文件存在,loadClass() 也会返回 ClassNotFoundException,而不是等到 defineClass() 阶段才报错。
-
ClassLoader.getUnnamedModule()返回当前类加载器的未命名模块,它自动读取所有已加载模块,但不自动导出任何包 - 模块系统下,
ClassLoader.getResources("META-INF/MANIFEST.MF")可能返回空,因为资源查找也受模块导出限制 - 不要试图在模块化应用中用反射修改
ClassLoader的parent字段——PlatformClassLoader是 final 的,且模块层绑定后不可变
defineClass() 的硬编码校验;一旦你绕过委托去调 defineClass() 加载核心包,失败是确定的,不是概率问题。










