java类加载器不支持真正卸载,动态卸载本质是让旧类及classloader被gc回收:需新建独立classloader、清空所有引用、停止相关线程、清理静态字段与jni句柄,并通过反射创建实例和调用方法。

Java 类加载器本身不支持真正的“卸载”——JVM 规范中没有提供卸载类的接口,所谓动态卸载,本质是让旧类及其 ClassLoader 被垃圾回收器(GC)回收,再用新 ClassLoader 加载同名类的新版本。关键不在“卸”,而在“清干净、换干净、断干净”。
必须新建独立的 ClassLoader 实例
每次热更新都要创建全新的 URLClassLoader(或其他自定义加载器),不能复用旧实例。复用会导致类路径污染、委托冲突,甚至触发双亲委派直接加载老版本。
- 构造时显式传入
new URL[]{jarUrl},避免父加载器意外参与委托 - 明确设置父加载器为
null或指定最小信任域(如仅委托给PlatformClassLoader),防止系统类加载器偷偷加载旧类 - 加载后立即保存该
ClassLoader引用,后续卸载操作全依赖它
确保旧类所有实例彻底不可达
只要有一个对象或静态引用还连着旧 Class,整个类元数据就无法被 GC 回收,Metaspace 内存会持续增长。
- 停止所有使用旧类的线程(尤其是守护线程),并将其
contextClassLoader设为null - 清理静态字段:检查插件类、工具类、单例、缓存 Map 中是否持有旧类实例或其子类对象
- 避免在新类中直接引用旧类的
public static final常量——编译期会内联,隐式绑定旧类 - 关闭监听器、注销回调、释放 JNI 句柄——这些常被忽略,但会强引用类加载器
用反射调用,避免编译期类型绑定
硬编码类型(如 MyService service = new MyService())会在编译时绑定到旧类,运行时无法切换。
- 一律通过
loadClass("com.example.MyService")获取Class> - 用
getDeclaredConstructor().newInstance()创建实例 - 方法调用全部走
Method.invoke(),不声明具体接口类型 - 若需统一接口,把接口放在共享类路径(由父加载器加载),实现类放插件 JAR 中
主动触发回收与验证是否成功
GC 不保证立刻回收,但可提示 JVM 并检查结果。
- 将旧
ClassLoader引用设为null,清空所有持有它的容器(List、Map、static 字段) - 调用
System.gc()(仅建议用于开发/测试环境,生产慎用) - 通过
ManagementFactory.getMemoryMXBean().getHeapMemoryUsage()和HotSpotDiagnosticMXBean查看 Metaspace 使用量变化 - 用
jcmd <pid> VM.native_memory summary</pid>或jstat -gcmetacapacity观察元空间回收情况
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











