jvm类卸载需同时满足三个条件且仅在full gc或g1并发标记完成阶段触发:所有实例被回收、classloader被回收、class对象无任何强引用;系统类加载器永不回收,仅自定义类加载器可卸载。

JVM 类卸载不是主动触发的行为,而是一次被动、严苛的回收动作,必须同时满足三个条件,并且恰逢 Full GC(或 G1 的并发标记完成阶段)才会发生。它不响应内存压力本身,也不会在 Minor GC 或常规运行中执行。
所有该类的实例必须被彻底回收
不只是 new 出来的对象要消失,还包括:
- 内部类隐式持有的外部类引用
- 静态集合(如 static List
)中缓存的对象 - ThreadLocal 中残留的实例
- 线程池未完成任务导致的隐式持有
- 子类实例存在也会阻止父类卸载
- finalizer 或 Cleaner 关联对象可能延迟回收
常见疏漏:缓存未 clear()、监听器注册后没反注册、shutdown() 方法未调用。
加载它的 ClassLoader 必须被 GC 回收
ClassLoader 是类元数据的归属主体,Metaspace 按加载器隔离管理:
- 系统类加载器(Bootstrap、Platform、AppClassLoader)生命周期与 JVM 绑定,永不回收
- 真正可卸载的,只来自自定义加载器(如 WebAppClassLoader、URLClassLoader、OSGi BundleClassLoader)
- 若被静态字段、线程上下文类加载器(Thread.currentThread().getContextClassLoader())、Spring 上下文或日志框架间接持有,就无法回收
- Web 应用重启时,线程池未关闭、ServletContext 未清理、Filter/Listener 未注销,都会造成 ClassLoader 泄漏
该类对应的 Class 对象不能有任何强引用
这是最隐蔽的泄漏点:
-
static Map
反射缓存未清理 - ThreadLocal
使用后未 remove() - Method.setAccessible(true) 后未恢复访问权限
- JNI 调用 NewGlobalRef 后遗漏 DeleteGlobalRef
- Spring 类型注册表、Hibernate 元模型缓存、AOP 代理生成逻辑也可能长期持有 Class 引用
必须发生在 Full GC 或 G1 并发标记完成阶段
类卸载逻辑不参与任何年轻代回收,只嵌入在 Full GC 流程中:
- G1 中自动集成,无需额外参数
- CMS 需启用 -XX:+CMSClassUnloadingEnabled
- 可通过 -XX:+TraceClassUnloading 查看卸载日志确认是否成功
- Metaspace 释放后的内存不一定立刻还给操作系统,需满足阈值(默认 70%)且有连续大块才尝试返还











