双亲委派模型是Java类加载的安全隔离机制:类的唯一性由“类加载器+全限定名”共同决定,同一类名不同加载器加载即为互不可转的独立类型,且核心类(如java.lang.String)只能由Bootstrap加载器加载,确保JVM信任链不被篡改。
双亲委派模型不是抽象概念,而是 java 面向对象安全隔离的底层支柱。它让“类的唯一性”真正落地——同一个类名、同一个包路径,若由不同类加载器加载,就是两个完全无关、不可互转、彼此隔离的类型。这种隔离不是靠语法或设计模式实现的,而是 jvm 在类加载阶段就硬性保证的。
类的唯一性由“类加载器 + 全限定名”共同决定
Java 中判断两个 Class 对象是否相等,必须同时满足:全限定名相同 且 由同一个类加载器加载。哪怕字节码一模一样,只要加载器不同,JVM 就视为两个独立类。
- 比如两个 Web 应用各自打包了
com.example.User,Tomcat 为每个应用分配独立的WebAppClassLoader,它们加载出的User类在 JVM 内存中是两个不同的Class实例 - 这就天然阻断了跨应用的强转、继承、方法重写等面向对象行为,避免了意外交互和状态污染
- 你不能把 App1 的
User实例传给 App2 的服务方法,即使签名完全匹配——运行时会抛ClassCastException
双亲委派是安全边界的建立机制
它确保核心类(如 java.lang.Object、java.util.List)只能由启动类加载器加载,用户代码无法覆盖或替换。
- 如果有人试图在项目里定义一个
java.lang.String,双亲委派会让请求一路向上交给 Bootstrap 加载器;它早已加载了 JDK 自带的String,直接返回,自定义版本被彻底忽略 - 没有这层委派,恶意代码就能通过类名劫持篡改基础行为,整个运行时信任链就崩塌了
- 这种“只认父加载、不认同名类”的策略,本质上是对面向对象中“契约一致性”的强制保障:所有代码都基于同一套可信基类运行
打破委派需明确目的,否则破坏隔离前提
像 Tomcat、OSGi 或 JDBC SPI 这类场景确实会绕过双亲委派,但它们不是取消隔离,而是重构隔离粒度。
- Tomcat 为每个 Web 应用创建专属类加载器,并重写
loadClass:先尝试自己加载/WEB-INF/classes和/WEB-INF/lib下的类,失败后再委派——这样既隔离业务类,又共享javax.servlet.*等平台类 - JDBC 使用线程上下文类加载器(TCCL),让 Bootstrap 加载的
DriverManager能回调到应用加载器去发现mysql.Driver,这是跨层级协作,不是放弃隔离 - 关键点在于:每一次打破,都是为了在更高维度(如应用级、模块级)建立更精细、更可控的面向对象边界
分析时可聚焦三个典型现象
验证双亲委派是否生效,不必读源码,观察运行时行为即可:
-
类冲突静默:两个不同版本的
commons-collections同时存在 classpath,但只有 ExtClassLoader 或 AppClassLoader 加载的那个生效,另一个根本不会进入内存 - 热部署可行:重启单个 Web 模块时,只需销毁其专属类加载器,它加载的所有类(包括静态变量、JIT 编译代码)全部卸载,不影响其他模块——这是类加载器实例级隔离的直接体现
-
SPI 加载成功:
ServiceLoader.load(XxxService.class)能找到META-INF/services/下的服务实现类,说明 TCCL 正确切换到了应用类加载器,打破了默认委派路径
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











