双亲委派是classloader.loadclass方法中写死的三步逻辑:先查findloadedclass缓存,再委托parent.loadclass,最后自身调用findclass;破坏它必须重写loadclass并跳过委托调用。
双亲委派不是概念,是写死在 classloader.loadclass(string name, boolean resolve) 方法里的几行代码。真正掌握它,必须盯住这个方法的执行路径,而不是背口诀。
看懂 loadClass 的三步骨架
JDK 源码中(以 Java 8/17 为准),loadClass 默认实现就是双亲委派的完整逻辑:
-
第一步:查缓存 —— 调用
findLoadedClass(name)。这是 JVM 内部维护的加载器私有缓存,按“类全名 + 当前加载器实例”双重判定。命中就直接返回,不走任何委托。 -
第二步:向上委托 —— 若缓存未命中,且
parent != null,则调用parent.loadClass(name, resolve)。注意:这是组合调用,不是继承调用;父加载器可能是AppClassLoader、ExtClassLoader,最终到null时触发findBootstrapClassOrNull(name)(底层 C++ 实现)。 -
第三步:自己兜底 —— 所有父加载器都抛出
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。
验证逻辑最直接的方式
别只看文档,动手验证更可靠:
- 用
javap -c java.lang.ClassLoader查看loadClass字节码,确认委托分支结构。 - 在自定义加载器中打日志:在
loadClass开头、委托前、findClass前分别输出当前加载器和类名,观察调用链。 - 写两个同名类(如
com.example.Utils),分别由不同加载器加载,用==和isAssignableFrom测试是否为同一类型。











