java类卸载是jvm在full gc时被动清理metaspace元数据的过程,核心是回收整个classloader及其所有加载类;成功需同时满足:该类所有实例被gc回收、classloader实例被回收、class对象无任何强引用。

Java 中没有“主动卸载类”的 API,所谓卸载,是 JVM 在满足严格条件后,于 Full GC 阶段被动清理 Metaspace 中类元数据的过程。核心逻辑不是“卸载某个类”,而是“回收整个 ClassLoader 及其加载的所有类”。能否成功,取决于三者是否同时不可达:该类所有实例、加载它的 ClassLoader 实例、对应的 Class 对象。
卸载发生的三个硬性条件
缺一不可,任意一条未断开,类就会永久驻留 Metaspace:
- 所有实例已被 GC 回收:包括显式 new 的对象、匿名内部类隐式持有的外部类引用、被静态集合(如 ConcurrentHashMap 缓存)、线程局部变量(ThreadLocal)或监听器长期持有的对象;
- 自定义 ClassLoader 实例被 GC 回收:系统类加载器(Bootstrap/Platform/AppClassLoader)由 JVM 持有,不会被回收;只有你手动创建的 URLClassLoader 或子类才可能被回收,但若它被 ServletContext、静态字段、线程、ThreadLocal 等间接持有,就无法释放;
- Class 对象无强引用:常见泄漏点包括 Spring 的 BeanDefinitionRegistry、MyBatis 的 MapperRegistry、未清理的 ThreadLocal、反射后残留的 setAccessible(true) 引用、JNI 全局引用等。
设计可回收的自定义 ClassLoader
关键在于让 ClassLoader 成为 GC 不可达的“孤岛”:
- 继承 ClassLoader,避免重写 findClass 以外的复杂逻辑(除非需从非 classpath 源加载);
- 加载完类后,立即把局部变量中的 Class、ClassLoader 引用设为 null;
- 若调用 Thread.currentThread().setContextClassLoader(),务必在使用后恢复原值或设为 null;
- 不在静态字段中缓存 Class、实例、ClassLoader 本身;
- 动态类避免持有宿主类的强引用(例如不要在匿名类中直接引用 this)。
验证卸载是否真正发生
System.gc() 仅是建议,JVM 可忽略。可靠方式依赖日志与工具:
- 启动参数加 -XX:+TraceClassUnloading,运行时观察控制台是否输出
[Unloading class com.example.MyService]; - 配合 -verbose:class 查看类加载/卸载总数变化;
- 用 jstat -gc
关注 MU(Metaspace used)是否下降(注意:下降不绝对等于卸载成功,可能是加载失败回滚); - 更精准的方式是启用 JFR(Java Flight Recorder),录制后筛选
ClassUnloading事件。
为什么不能“主动卸载”?
JVM 规范未定义卸载接口。类元数据按 ClassLoader 隔离存储在 Metaspace,卸载粒度是 ClassLoader 级别——只有当整个 ClassLoader 被判定为垃圾,它名下所有类才会被批量清除。一旦你的 CustomClassLoader 被框架、静态监听器或线程悄悄持有,它和它加载的所有类就全部卡死在内存里了。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











