JVM已加载的Class实例能否释放?规则、时机与监控咨询
JVM中Class实例卸载规则与相关问题解答
首先明确核心前提:JVM中的java.lang.Class实例没有任何公开的主动释放API,所有类的卸载动作完全由JVM垃圾回收器自动完成,不存在确定性的执行时间点。
核心问题逐一说明
1. 自动释放的时机与确定性
- 从JDK8移除永久代开始,Class对象本身存储在堆内存中,类的元数据存储在本地内存的元空间,二者的回收都完全遵循GC的可达性分析规则:只有当Class对象、对应元数据都处于完全不可达状态时,才会在某次GC周期中被标记并回收。
- 不存在可预期的确定执行时间点:和所有普通Java对象一样,哪怕对象已经完全不可达,JVM也不保证会立刻触发回收。如果应用运行全程没有触发覆盖老年代、元空间的垃圾回收,这些不可达的Class可能一直驻留到进程退出。
2. 是否支持手动释放Class
- JDK没有提供任何公开API可以主动卸载某个已经加载完成的Class。所有所谓“手动卸载类”的操作,本质都是主动清理所有指向目标Class、对应ClassLoader、类静态成员、类实例的强引用,让Class对象进入不可达状态,等待GC自行回收。
- 不要尝试通过反射、
Unsafe等非公开接口强行触发类卸载,不同JVM版本、不同厂商实现的类加载逻辑差异极大,这类操作大概率会引发兼容性问题甚至JVM崩溃。
3. 类卸载过程的监控
类的卸载过程完全可以监控,也支持外部工具无侵入实现:
- 启动时添加JVM参数
-verbose:class,类加载、卸载事件都会直接打印到控制台,卸载日志格式为[Unloading class 全限定类名 0x对象地址] - 通过JMX的
ClassLoadingMXBean可以直接读取已卸载类的总计数,也可以注册监听器实时捕获类卸载事件 - 外部工具包括JConsole、VisualVM、JDK内置的Java Flight Recorder(JFR)都支持直接监控类卸载数量、追踪具体被卸载的类信息,不需要修改业务代码。
新版本JVM的类卸载规则有效性
你提到的早期判定规则在JDK17版本的Oracle HotSpot VM、GraalVM中依然完全生效,类可被卸载的核心判定条件没有变化:
- 堆中不存在该类的任何实例对象的强引用
- 加载该类的
ClassLoader实例已经被回收,无任何强引用残留 - 该类对应的
java.lang.Class对象没有被任何位置强引用(包括反射缓存、静态字段、线程上下文引用等)
注意:这套规则生效不代表动态生成临时类就完全不会出现内存泄漏。很多动态类生成场景(比如动态代理、CGLIB字节码增强、脚本引擎执行)很容易出现隐式引用残留:比如全局缓存的代理类对象、ThreadLocal中存储的类关联值、JIT编译后的代码缓存引用,都可能导致类一直无法卸载,最终触发元空间OOM。
对你提供的测试代码的判定
不能认为testMethod方法执行完成后,classTest占用的所有资源就会立刻释放,原因如下:
- 局部变量
cl、classTest出了方法作用域不代表引用会立刻失效:如果栈帧里的局部变量槽位没有被后续写入的变量覆盖,部分JVM实现中这些引用还会暂时作为GC Root存在,不会立刻判定为不可达。 - 就算两个引用已经完全不可达,只要没有触发覆盖老年代、元空间的GC(比如G1/ZGC/Shenandoah的并发标记周期、Full GC),这些临时加载的类和对应的ClassLoader依然会占用内存,不会被回收。
- 代码中调用了类的静态方法
testMethodInClass,如果该方法触发了JIT编译,或者类内的静态字段持有了自身Class、ClassLoader的引用,也会延长类的存活时间。
实际运行这段循环9999次的代码时,只要元空间占用达到GC阈值,JVM确实会自动回收之前加载的临时类,不会出现持续的内存泄漏,但回收的时间点完全不确定,不存在“方法执行完就立刻释放”的保证。
内容的提问来源于stack exchange,提问作者Firok
相关产品推荐
相关产品推荐

