双亲委派模型是类加载器间“先向上委托、父失败才自加载”的协作规则,旨在保障类唯一性(如java.util.ArrayList仅由Bootstrap加载一次)和核心安全性(防止java.lang.*被自定义类替换),其流程由ClassLoader.loadClass方法强制实现。

双亲委派模型不是“父母一起管孩子”,而是类加载器之间一种向上委托、失败才向下处理的协作规则。它的目标很实在:防止同一个类被不同加载器重复加载,更关键的是守住 java.lang.* 这类核心类不被替换或污染。
它解决的两个核心问题
一是类唯一性——比如 java.util.ArrayList,不管你的项目里引入多少个 jar 包,JVM 里只允许有一份由启动类加载器加载的合法版本。否则 instanceof 失效、类型转换报错、序列化失败都会接踵而至。
二是安全兜底——你哪怕在 classpath 下放一个自己写的 java.lang.String,JVM 也根本不会用它。因为请求一上来就被 Bootstrap 加载器“截胡”了,它早就把真正的 String 加载进来了;子加载器连动手的机会都没有。
三个默认加载器怎么配合
-
启动类加载器(Bootstrap):C++ 实现,加载
$JAVA_HOME/jre/lib/rt.jar等核心类,Java 层拿不到它的引用(getClassLoader()返回 null) -
扩展类加载器(Extension):Java 实现,父加载器是 Bootstrap,负责
lib/ext目录下的扩展类 - 应用类加载器(Application):也叫系统类加载器,父加载器是 Extension,负责 classpath 下所有类,也就是你写的代码和依赖 jar
它们构成一条链:Application → Extension → Bootstrap。每次加载都从 Application 开始,但动作是先往上推,不是往下找。
关键实现就在 loadClass 方法里
JDK 的 ClassLoader.loadClass(String name, boolean resolve) 方法已经写死了流程:
- 先调
findLoadedClass(name)查本加载器是否已加载(注意:不查 parent) - 没加载过,就调
parent.loadClass(...)向上委托;如果 parent 是 null,就直接调 VM 底层的findBootstrapClassOrNull - 所有上级都返回
ClassNotFoundException,才轮到自己调findClass(name)去磁盘或网络读字节码
所以你自定义类加载器时,通常只需重写 findClass,而不是重写 loadClass——改它就等于绕开双亲委派,除非你明确知道要打破它。
什么时候会打破它
双亲委派不是铁律,而是默认策略。真实场景中常有例外:
-
Tomcat 隔离 Web 应用:每个 app 有自己的加载器,优先加载自己
WEB-INF/lib里的类,避免 Spring 版本冲突——这是反向委派(先自己,再 parent) -
SPI 机制(如 JDBC):
DriverManager在 Bootstrap 中,但它要加载用户 jar 里的com.mysql.cj.jdbc.Driver,只能靠线程上下文类加载器(TCCL)来“跨级点名” - 热部署、模块化框架(如 OSGi):需要精细控制类可见范围,必须绕过默认委派链
打破的方式通常是重写 loadClass,跳过父委派步骤,或在特定条件下切换加载器上下文。


















