类卸载时静态变量和常量池随元数据一并释放,但必须同时满足三条件:所有实例被回收、classloader被gc回收、class对象无任何强引用;否则无法卸载。

类被卸载时,其关联的静态变量和常量池数据会随整个类元数据一并释放,但前提是该类满足全部卸载条件——不是“类一卸载就自动清理”,而是“卸载发生时,这些内容才真正被回收”。
静态变量的释放时机与依赖关系
静态变量属于类级别,存储在堆内存中(对应 Class 对象的静态字段),其生命周期绑定于类本身。只有当以下三点同时成立,静态变量才能被回收:
- 该类的所有实例(包括子类实例)已被 GC 回收
- 加载该类的 ClassLoader 实例已不可达(无任何 GC Root 引用)
- 该类的 java.lang.Class 对象本身不再被任何地方引用(例如无缓存、无反射强引用、无 lambda 捕获等)
一旦类卸载成功,JVM 会清除 Metaspace 中该类的元数据结构,同时堆中由该类持有的静态变量引用也会失效;若这些静态变量没有被其他对象引用,它们将在下一次普通 GC 中被回收。
常量池数据的归属与清理方式
常量池是类元数据的一部分,存在于 Metaspace 中,而非堆或方法区旧模型中的独立区域。它不单独管理,而是作为类结构的附属内容存在:
- 字符串字面量(如 "hello")在 JDK 7+ 后存放在堆的字符串常量池中,但仅当该字符串对象被 Class 的静态字段或代码引用且无外部引用时,才可能随类卸载间接释放
- 类、字段、方法的符号引用(如类名、方法签名)在解析阶段转为直接引用后,原始符号信息仍保留在常量池结构中,随类元数据整体释放
- 数值型常量(如 public static final int MAX = 100;)在编译期可能被内联,运行时不一定保留——若未被引用,常量池中对应项在类卸载时一并清除
实际中容易卡住卸载的关键点
很多看似“该卸载”的类迟迟不释放,往往是因为静态变量或常量池内容意外维持了引用链:
- 静态集合类(如 static Map
CACHE = new HashMap(); )长期持有对象引用,导致 Class 对象无法被回收 - 通过 Class.forName("X") 或反射获取 Class 后未及时置 null,造成隐式强引用
- 日志框架、序列化工具、AOP 代理等第三方库缓存 Class 对象,且未配合类加载器生命周期清理
- 线程本地变量(ThreadLocal)中存有该类实例或 Class 引用,而线程未结束
JVM 参数辅助验证释放行为
可通过以下参数观察卸载是否真正发生:
- -XX:+TraceClassUnloading:输出类卸载日志,确认目标类是否出现在日志中
- -XX:MaxMetaspaceSize=256m:限制元空间大小,加速暴露卸载失败问题
- -verbose:class:查看类加载/卸载全过程,比对 ClassLoader 实例是否被回收
注意:系统类加载器(如 AppClassLoader)加载的类,其静态变量和常量池永远不会卸载——因为类加载器始终可达,这是 JVM 的设计约束,不是 bug。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











