双亲委派是ClassLoader.loadClass方法中写死的三步逻辑:先查findLoadedClass缓存,再委托parent.loadClass,最后自身调用findClass;破坏它必须重写loadClass并跳过委托调用。

直接看 JDK 源码里的 ClassLoader.loadClass(String, boolean) 方法,就能抓住双亲委派的全部逻辑——它不是概念描述,而是几行清晰、可验证的代码。
loadClass 方法就是双亲委派的完整实现
这个方法在 java.lang.ClassLoader 中是模板方法,其默认行为就是双亲委派模型本身:
- 先调用
findLoadedClass(name)查本加载器缓存:命中则直接返回,不走任何委托;这是第一道安全阀,防止重复定义。 - 若未命中且父加载器非空(
parent != null),就递归调用parent.loadClass(name, false)——注意是组合调用,不是继承调用。 - 若父加载器为
null(即到达顶层),则调用findBootstrapClassOrNull(name),由 JVM 底层 C++ 实现尝试加载核心类(如java.lang.String)。 - 所有上级都抛出
ClassNotFoundException后,才执行findClass(name),由子类实现具体查找逻辑(比如读文件、解压 JAR、网络拉取等),再经defineClass转为Class对象。
关键细节决定你是否真懂委派机制
很多理解偏差,源于没细读这几处源码行为:
-
findLoadedClass是加载器私有缓存,按“类全名 + 当前加载器实例”双重判定;不同加载器缓存互不共享——这正是 Tomcat 隔离同名类的基础。 - 启动类加载器没有 Java 对应对象,所以
String.class.getClassLoader()返回null,不代表“没加载器”,而是设计如此。 -
parent是ClassLoader的成员变量,初始化来自构造函数传入(如new URLClassLoader(urls, parent)),父子是组合关系,不是继承关系。 - 重写
findClass不等于破坏委派;只有重写loadClass并跳过parent.loadClass()调用,才算真正切断链条。
哪些场景真正“破坏”了双亲委派?看源码证据
所谓“破坏”,不是靠猜测,而是看有没有改 loadClass 的调用顺序:
-
Tomcat 的 WebAppClassLoader:显式重写了
loadClass,把顺序变成“先findClass→ 失败再委托父加载器”,实现应用间类隔离。 -
SPI 机制(如 JDBC):通过
Thread.currentThread().getContextClassLoader()获取应用类加载器,逆向调用其loadClass,绕过启动类加载器无法加载业务驱动的问题。 -
自定义热部署加载器:每次新建一个加载器实例,重写
loadClass禁用缓存与委托,确保旧类可卸载、新类可重新 define。
类加载器的层次结构本质是逻辑委派链
JDK 默认三类加载器构成一条明确的委托链:
-
启动类加载器(Bootstrap):C++ 实现,加载
JAVA_HOME/jre/lib/rt.jar等核心类;Java 层不可见,表现为null。 -
扩展类加载器(ExtClassLoader):加载
lib/ext目录下类;父加载器逻辑上是 Bootstrap,自身是 AppClassLoader 的父加载器。 - 应用程序类加载器(AppClassLoader):加载 classpath 下用户代码;是绝大多数自定义加载器的默认父加载器。
它们之间没有继承关系,而是通过构造时传入 parent 字段形成组合链。每个加载器只负责自己路径,其余交由上级处理。


















