题目
设计支持模块化热部署的自定义类加载器
信息
- 类型:问答
- 难度:⭐⭐⭐
考点
类加载过程,双亲委派机制,自定义类加载器,打破双亲委派,类卸载机制
快速回答
实现要点:
- 继承
ClassLoader重写findClass()方法 - 使用
defineClass()转换字节码为Class对象 - 破坏双亲委派:重写
loadClass()优先加载本地模块 - 实现类卸载:使用弱引用管理模块,无引用时触发GC卸载
- 线程上下文类加载器处理SPI服务加载
1. 核心原理说明
类加载过程:
- 加载:查找字节码并创建Class对象
- 链接:验证、准备(分配内存)、解析(符号引用转直接引用)
- 初始化:执行<clinit>静态代码块
双亲委派机制:
- 类加载请求优先委派父加载器处理
- 避免核心类被篡改,保证唯一性
- 父加载器无法完成时才自己加载
类卸载条件:
- Class对象无实例
- 加载该类的ClassLoader被回收
- 无地方引用该Class对象
2. 代码实现示例
public class ModuleClassLoader extends ClassLoader {
private final String modulePath;
private final WeakHashMap<String, Class<?>> cache = new WeakHashMap<>();
public ModuleClassLoader(String path, ClassLoader parent) {
super(parent);
this.modulePath = path;
}
// 打破双亲委派:优先加载模块内类
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// 1. 检查本地缓存
Class<?> clazz = findLoadedClass(name);
if (clazz != null) return clazz;
// 2. 优先加载模块包内的类
if (name.startsWith("com.module.")) {
clazz = findClass(name);
if (clazz != null) {
if (resolve) resolveClass(clazz);
return clazz;
}
}
// 3. 委派父加载器
return super.loadClass(name, resolve);
}
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] bytes = loadClassData(name);
if (bytes == null) throw new ClassNotFoundException(name);
Class<?> clazz = defineClass(name, bytes, 0, bytes.length);
cache.put(name, clazz); // 弱引用缓存
return clazz;
}
private byte[] loadClassData(String className) {
String path = modulePath + className.replace('.', '/') + ".class";
try (InputStream is = new FileInputStream(path)) {
return is.readAllBytes();
} catch (IOException e) {
return null;
}
}
// 热卸载模块
public void unloadModule() {
cache.clear();
}
}3. 最佳实践
- 隔离性:每个模块使用独立ClassLoader
- 资源释放:重写
close()方法关闭JAR文件句柄 - SPI处理:
Thread.currentThread().setContextClassLoader(this); ServiceLoader<ServiceInterface> loader = ServiceLoader.load(ServiceInterface.class); - 内存管理:使用
WeakHashMap避免阻止类卸载
4. 常见错误
- 类冲突:不同加载器加载同名类导致
ClassCastException - 内存泄漏:静态引用持有ClassLoader导致无法卸载
- 资源未关闭:未重写
close()方法造成文件句柄泄漏 - 死锁风险:同步块中执行未知代码(如静态初始化块)
5. 扩展知识
- OSGi:使用图结构管理类加载器依赖
- Java模块化:JPMS通过
module-info.java控制可见性 - 类加载器层次:
- Bootstrap(加载rt.jar)
- Platform(加载JDK模块)
- Application(classpath类)
- 调试技巧:
clazz.getClassLoader().getResource(""); // 查看加载源