CMS GC堆初始值等于最大值时效率矛盾及Finalizer内存占用问题
问题解答
两个GC说法的冲突解释
两个说法不存在矛盾,属于不同层面的GC逻辑:
- 说法1的效率提升是堆容量动态调整开销层面的确定优化:当
-Xms和-Xmx相等时,JVM会完全跳过运行时向操作系统申请/释放内存、调整堆各代空间边界的逻辑,这部分 overhead 是明确减少的,不会带来反向的效率损耗。 - 说法2中GC的触发逻辑和堆固定大小没有强绑定关系:CMS的并发回收触发是基于当前老年代的占用比例,不是按相对于Xmx的剩余空间判断。哪怕
-Xms和-Xmx相等都是7G,CMS默认的并发回收阈值是老年代占用达到68%左右就会启动,不会等到堆几乎占满才触发,不存在“长时间不启动GC”的情况,你也可以通过-XX:CMSInitiatingOccupancyFraction参数手动调整触发阈值。
Finalizer堆占用高的问题排查
你遇到的java.lang.ref.Finalizer实例过多、占用堆空间的问题,和-Xms/-Xmx相等的配置无关,优先排查以下方向:
- 首先定位Finalizer引用的具体对象类型:Finalizer是JVM内置类,只要类重写了
finalize()方法,实例被回收前都会被Finalizer引用挂载到Finalizer队列中,不是只有你自己写的业务代码才会用,JDK内置的IO流、Socket连接、JNI关联对象、本地资源类都会重写finalize()方法用于兜底释放资源。 - Finalizer大量堆积的核心原因通常是Finalizer线程被阻塞:Finalizer是单线程执行,且优先级低于普通业务线程,如果业务线程CPU占比极高抢占了调度资源,或者某个类的
finalize()方法执行卡死、逻辑过慢,都会导致队列里的待处理Finalizer对象越堆越多,这些对象属于不可达但未完成回收的状态,会持续占用堆空间,甚至占满堆触发OOM。 - CMS默认不会在并发回收阶段强制处理所有待执行的Finalizer任务,只有Full GC才会一次性清理完队列中的Finalizer,如果堆占满时仍未触发Full GC,就会出现内存耗尽的情况。
排查&修复建议
- 先通过
jstack命令查看Finalizer线程的栈信息,定位它当前卡在执行哪个类的finalize()方法,锁定对应的资源类 - 排查业务代码中是否存在资源未手动关闭的情况:所有实现了
AutoCloseable接口的资源都要通过try-with-resources语法声明,不要依赖JVM的finalize()机制兜底释放资源 - 可以新增GC日志参数
-XX:+PrintGCDetails -XX:+PrintGCApplicationStoppedTime确认GC触发频率和停顿时间,如果确实存在GC触发滞后的问题,可以手动调低CMS的并发回收阈值
内容的提问来源于stack exchange,提问作者arqam
相关产品推荐
相关产品推荐

