题目
请解释JVM类加载机制中的双亲委派模型,并说明如何自定义类加载器打破该模型
信息
- 类型:问答
- 难度:⭐⭐
考点
类加载机制,双亲委派模型,自定义类加载器
快速回答
双亲委派模型的核心机制是:
- 类加载请求优先委派给父加载器处理
- 父加载器无法完成时才由子加载器自行加载
- 通过层级委托避免核心类被篡改
自定义类加载器打破模型的关键步骤:
- 继承
ClassLoader类 - 重写
findClass()方法 - 在
loadClass()中绕过父加载器检查
一、双亲委派模型原理
JVM类加载采用层级委托机制:
- 加载流程:
- 子加载器收到请求后先委托父加载器
- 父加载器尝试加载,失败则返回
- 子加载器调用
findClass()自行加载
- 层级结构:
BootstrapClassLoader → ExtClassLoader → AppClassLoader → 自定义ClassLoader - 核心目的:
- 避免核心类(如
java.lang.Object)被重复加载 - 防止用户伪造核心类库
- 保证类加载的全局唯一性
- 避免核心类(如
二、自定义类加载器实现
打破双亲委派的示例代码:
public class CustomClassLoader extends ClassLoader {
private String classPath;
public CustomClassLoader(String classPath) {
this.classPath = classPath;
}
// 打破双亲委派:直接自行加载
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// 1. 先检查是否已加载
Class<?> c = findLoadedClass(name);
if (c == null) {
// 2. 绕过父加载器,直接尝试自定义加载
if (!name.startsWith("java.")) { // 排除核心类
c = findClass(name);
}
// 3. 核心类仍走双亲委派
if (c == null) {
c = super.loadClass(name, resolve);
}
}
return c;
}
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] data = loadClassData(name);
return defineClass(name, data, 0, data.length);
}
private byte[] loadClassData(String className) {
// 从指定路径读取.class文件(此处省略具体IO操作)
return ...;
}
}三、关键机制说明
| 方法 | 作用 | 打破委派的关键点 |
|---|---|---|
loadClass() | 类加载入口 | 绕过parent.loadClass()调用 |
findClass() | 实际加载逻辑 | 需自行实现字节码获取 |
defineClass() | 字节码转换 | JVM将二进制转为Class对象 |
四、应用场景与最佳实践
- 典型场景:
- 热部署(如Tomcat reload应用)
- 模块化加载(OSGi框架)
- 加密类文件解密加载
- 最佳实践:
- 非必要不打破双亲委派
- 隔离加载时使用不同ClassLoader实例
- 注意资源释放防止内存泄漏
五、常见错误
- 类转换异常:
java.lang.ClassCastException: com.Foo cannot be cast to com.Foo
原因:不同类加载器加载的相同全限定名类,JVM视为不同类 - 资源冲突:自定义加载器未正确覆盖
findResource()导致资源加载失败 - 内存泄漏:持有ClassLoader引用导致类无法卸载
六、扩展知识
- 破坏双亲委派的实际案例:
- JDBC通过
Thread.currentThread().setContextClassLoader()加载驱动 - Tomcat为每个Web应用创建独立的
WebappClassLoader
- JDBC通过
- 类卸载条件:
- 无该类的实例
- 无该类的
ClassLoader引用 - 无该类的
Class对象引用