双亲委派机制的核心实现在ClassLoader.loadClass()方法中:先调findLoadedClass查缓存,再委托parent.loadClass向上委派,父加载器无法加载时才调用findClass自行加载。

loadClass 方法就是双亲委派的执行体
双亲委派不是抽象概念,它就实打实地写在 ClassLoader.loadClass(String name, boolean resolve) 的源码里。这个方法默认实现就是整个模型的骨架:先查缓存、再委派父加载器、父不行才调用 findClass() 自己加载。
关键逻辑是向上委托链:如果当前加载器的 parent 不为 null,就直接调用 parent.loadClass(name, resolve) —— 注意,这不是继承调用,而是组合关系下的显式委托。启动类加载器(BootstrapClassLoader)没有 Java 对应对象,所以它的 parent 是 null,递归在此终止。
常见误判点:
- 以为重写了
findClass()就打破了双亲委派 —— 错,只要没动loadClass(),委托链依然完整 - 看到
String.class.getClassLoader()返回null就认为“没加载器” —— 实际是 Bootstrap 加载的,Java 层不可见,null是设计使然 - 在自定义加载器中直接 new 一个 Class 对象返回 —— 这绕过了验证和链接阶段,JVM 会抛
SecurityException或直接拒绝
findLoadedClass 是第一道安全闸门
loadClass() 开头必调 findLoadedClass(name),这是 JVM 内部维护的已加载类缓存(基于类全名 + 加载器实例双重判定)。它防止同一个类被同一加载器重复定义,也杜绝了“父加载器没加载,子加载器又加载一遍”的冲突。
这个缓存是加载器实例私有的,不同加载器之间互不共享。这也是为什么 Tomcat 用多个 WebAppClassLoader 隔离应用 —— 即便两个应用都含 com.fasterxml.jackson.databind.ObjectMapper,只要加载器不同,它们在 JVM 中就是两个完全无关的类,不会互相干扰。
注意:findLoadedClass 查不到时,才进入委托流程;它查到了,就直接返回,不走任何父加载器。
为什么不能直接重写 loadClass 来跳过委托
手动重写 loadClass() 并跳过 parent.loadClass() 调用,等于主动切断双亲委派。这种操作在绝大多数场景下会导致严重问题:
- 重复加载核心类:比如你自己的类加载器去加载
java.lang.Object,JVM 会拒绝,抛出LinkageError - 破坏类唯一性:两个不同加载器加载的
ArrayList,哪怕字节码一模一样,也不能相互赋值,运行时报ClassCastException - 引发
NoClassDefFoundError:某个类由子加载器加载,但它依赖的java.util.concurrent类却被父加载器加载,而该类引用了子加载器未加载的辅助类,就会在初始化时失败
真正需要打破委派的场景极少,比如 JDBC 的 DriverManager 要主动用线程上下文类加载器(Thread.currentThread().getContextClassLoader())去加载驱动,因为 DriverManager 是 Bootstrap 加载的,无法直接访问应用 classpath 下的驱动类。
调试时怎么确认委托是否发生
最直接的方式是加断点或日志:在自定义 ClassLoader 的 loadClass() 开头打印当前加载器身份和类名,在 findClass() 入口再打一次。你会看到类似这样的调用链:
AppClassLoader.loadClass("com.example.Service") →
ExtClassLoader.loadClass("com.example.Service") →
BootstrapClassLoader.loadClass("com.example.Service") →
(返回 null)→
ExtClassLoader.findClass("com.example.Service") →
(找不到,返回 null)→
AppClassLoader.findClass("com.example.Service")
注意两点:
- Bootstrap 的
loadClass是 native 方法,Java 层看不到源码,但它的行为可推断:只认java.*、javax.*等白名单包 - 一旦某一级在
findClass中成功返回Class对象,后续所有上级加载器的findClass都不会再执行
真正容易被忽略的是:委托链上每一级都会先查自己缓存,再查父级 —— 这个“查缓存”动作本身也是双亲委派的一部分,不是只发生在第一次加载时。










