java类卸载是jvm在full gc中被动完成的、以类加载器为单位的批量回收行为,需同时满足实例全回收、classloader被回收、class对象无引用三个条件;自定义类加载器是关键突破口,其动态加载的类才可能卸载;元空间内存下降是卸载成功的直接证据。

Java 中类的卸载不是主动操作,而是 JVM 在满足严格条件后自动触发的回收行为,核心发生在方法区(Java 8+ 为 Metaspace)。能否卸载,不取决于“要不要”,而取决于“能不能”——关键看 类实例、ClassLoader 实例、Class 对象 这三者是否全部脱离了 GC 可达性链。
类卸载的三个必要条件缺一不可
只有当以下三点同时成立时,JVM 才可能在下次 Metaspace 垃圾回收时卸载该类:
- 该类所有实例已被 GC 回收:堆中不存在任何该类的活跃对象(包括子类实例间接持有时也会影响);
- 加载它的 ClassLoader 实例已被 GC 回收:ClassLoader 与它加载的类之间存在强引用,只要 ClassLoader 还活着,类就无法被卸载;
-
对应的 java.lang.Class 对象不可达:没有静态变量、线程局部变量、反射缓存、JNI 全局引用等持有该 Class 的强引用(哪怕只是
Class.forName("X")后未释放引用,也会阻断卸载)。
为什么系统类加载器加载的类几乎从不卸载
Bootstrap、Platform 和 AppClassLoader 是 JVM 启动时创建的长期存活对象,通常被 static 引用或 JVM 内部结构强持有。它们不会被 GC 回收,因此由它们加载的类(如 java.util.ArrayList、用户 main 类路径下的类)在应用生命周期内永远不会卸载。
这也是热部署、插件化、动态脚本等场景必须使用自定义 ClassLoader的根本原因:只有你可控的、可显式置 null 的 ClassLoader,才可能被回收,进而带动其加载的类一起退出 Metaspace。
Metaspace 回收不是“按类触发”,而是“按 ClassLoader 触发”
Metaspace 的清理粒度是 ClassLoader 级别,而非单个类。JVM 不会单独卸载某个类,而是当一个 ClassLoader 被判定为垃圾后,将其整个命名空间(namespace)下所有已加载的类元数据一并清除。
这意味着:
- 同一个自定义 ClassLoader 加载的多个类,要么全留,要么全卸;
- 若该 ClassLoader 加载了 A 类和 B 类,而 B 类仍有 Class 引用未释放,则 A 类也无法卸载;
- 频繁创建/丢弃 ClassLoader(如每次热更都新建一个),需警惕 Metaspace 内存碎片和 native 内存泄漏风险。
如何验证类是否真正卸载
仅靠日志或内存监控不够可靠。推荐组合手段:
- 启动参数加入
-XX:+TraceClassUnloading -XX:+PrintGCDetails,观察 GC 日志中是否有Unloading class xxx记录; - 用
jcmd <pid> VM.native_memory summary scale=MB</pid>查看 Metaspace 实际占用变化; - 借助 JFR(Java Flight Recorder)录制事件,筛选
jdk.ClassUnload事件确认卸载时机; - 注意:
jstat -gc <pid></pid>中的MU(Metaspace used)下降,才是卸载生效的直接证据。
不复杂但容易忽略:卸载不是功能开关,而是 GC 可达性分析的自然结果。控制好 ClassLoader 生命周期,比琢磨“怎么卸载”更重要。











