双亲委派模型通过“先委托父加载器、父无法加载再自行加载”确保类唯一性和核心类安全;打破它需重写loadclass方法绕过默认委托,典型场景包括tomcat应用隔离、osgi模块化和jdbc spi服务加载。

Java 的双亲委派模型(Parent Delegation Model)是类加载器(ClassLoader)加载类时的核心机制,它保证了类的唯一性和安全性;而“打破”它并非错误操作,而是有明确场景需求(如热部署、模块隔离、自定义类加载逻辑)时的主动设计。
双亲委派模型怎么工作
当一个类加载器收到类加载请求时,它不会立即尝试自己加载,而是先委托给父类加载器去完成。这个过程逐级向上,直到到达最顶层的 Bootstrap ClassLoader(启动类加载器)。只有当父类加载器无法加载该类(即在它的搜索路径中找不到对应 class 文件)时,子加载器才尝试自己加载。
典型的类加载器层级关系如下(由高到低):
-
Bootstrap ClassLoader:C++ 实现,负责加载
$JAVA_HOME/jre/lib下的核心类(如java.lang.*) -
Extension ClassLoader:Java 实现,加载
$JAVA_HOME/jre/lib/ext或java.ext.dirs指定路径下的扩展类 -
Application ClassLoader(也叫 System ClassLoader):加载
classpath指定路径下的应用类 -
自定义 ClassLoader:开发者继承
ClassLoader编写的加载器,通常用于特殊资源加载或隔离
这种“先查父、再查己”的策略确保了核心类(如 java.lang.String)不会被用户代码替换,防止恶意篡改和版本冲突。
怎么打破双亲委派模型
打破双亲委派,本质是**重写 loadClass(String name) 方法,绕过默认的委托逻辑**。标准做法是:在自定义类加载器中,不调用 super.loadClass(name),而是直接调用 findClass(name) 加载字节码。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
常见打破方式和适用场景:
-
重写
loadClass,优先自行加载:适用于插件系统、OSGi、热部署框架(如 Tomcat、JRebel),让不同模块可加载同名但不同版本的类 -
只对特定类绕过委托:比如 Tomcat 的 WebAppClassLoader 对
javax.*和org.apache.catalina.*等包仍走双亲委派,但对应用自己的com.example.*则优先本地加载 -
线程上下文类加载器(TCCL):JDK 提供
Thread.setContextClassLoader(),SPI 服务(如 JDBC 驱动加载)依赖它实现“父加载器委托给子加载器”,这是官方支持的“间接打破”方式
⚠️ 注意:不能简单覆盖 findClass 就算打破——findClass 本身不触发委托,真正控制流程的是 loadClass。默认实现正是在其中先调用父加载器的 loadClass,失败后再调 findClass。
一个最小可运行的打破示例
public class MyClassLoader extends ClassLoader {
private final String baseDir;
public MyClassLoader(String baseDir, ClassLoader parent) {
super(parent); // 显式指定父加载器
this.baseDir = baseDir;
}
@Override
protected Class> loadClass(String name, boolean resolve) throws ClassNotFoundException {
// ? 关键:不调用 super.loadClass,跳过双亲委派
Class> clazz = findLoadedClass(name);
if (clazz != null) return clazz;
// 这里可以加白名单/黑名单逻辑,比如只对某些包自行加载
if (name.startsWith("com.myapp.")) {
byte[] bytes = loadClassBytes(name);
clazz = defineClass(name, bytes, 0, bytes.length);
if (resolve) resolveClass(clazz);
return clazz;
}
// 其他类仍走双亲委派(可选,保持兼容性)
return super.loadClass(name, resolve);
}
private byte[] loadClassBytes(String name) throws IOException {
String path = baseDir + "/" + name.replace('.', '/') + ".class";
return Files.readAllBytes(Paths.get(path));
}
}
上面代码中,对 com.myapp. 开头的类直接加载,其余类仍委托给父加载器——这是一种安全、可控的打破方式。
为什么不能随便打破?要注意什么
随意打破双亲委派会引发严重问题:
- 类重复定义异常(LinkageError):同一类被不同加载器多次加载,即使字节码相同,JVM 也视为不同类,无法强转、无法共享静态变量
- 核心类被污染风险:若 Bootstrap 或 Extension 类加载器本该加载的类被子加载器提前加载,可能破坏 JVM 稳定性
- 反射、序列化、JNI 失效:涉及类身份判断的地方都可能出错
- 内存泄漏:尤其在 Web 容器中,未正确清理自定义类加载器会导致老年代堆溢出
所以,打破必须有明确理由,并配合严格的类隔离策略、生命周期管理和资源释放。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










