cms并发清理阶段产生的浮动垃圾无法被当次gc回收,是其并发设计的固有特性,需通过gc日志识别堆积迹象、定位新生代晋升或对象复活等诱因,并调优cmsinitiatingoccupancyfraction等参数防控恶化。

CMS 在并发清理阶段产生的浮动垃圾无法被当次 GC 回收,这是其并发设计的固有特性,不是异常或配置错误,而是标记-清除类收集器在“应用线程不停止”前提下的必然代价。处理重点不在于“实时清除”,而在于识别堆积趋势、控制规模、防止触发 Concurrent Mode Failure 或 CMSParPromotionFailed 等严重故障。
看懂 GC 日志里的关键信号
启用 -XX:+PrintGCDetails -XX:+PrintGCTimeStamps 后,重点关注以下几类输出:
- 老年代使用率持续上升但迟迟不触发 CMS:比如已占 76%,却没进入 initial-mark,说明
CMSInitiatingOccupancyFraction设得过高,浮动窗口拉得太长 - 频繁出现
concurrent mode failure:表明浮动垃圾 + 碎片已超负荷,CMS 来不及清理,被迫退化为 Full GC -
CMS-concurrent-abortable-preclean耗时陡增或失败:预清理卡住,往往意味着新生代晋升压力大、跨代引用暴增,间接推高浮动垃圾生成速率 - 每次 CMS 后老年代下降量明显萎缩:如上次清掉 45MB,这次只清掉 8MB,说明大量新分配/复活对象逃过标记,成了新增浮动垃圾
定位浮动垃圾的主要来源
浮动垃圾本质是「并发标记结束后、并发清理开始前」这段时间内产生的新垃圾,或因引用变更而漏标的老对象。常见源头包括:
- 新生代频繁晋升:Minor GC 高频发生,短命对象直接进老年代,CMS 清理时已不可达,但未被标记
- 对象“复活”:通过
finalize()将自身重新加入静态集合、缓存 Map 等,使本该回收的对象变为存活 - 大对象直入老年代:尤其在分配速率高、CMS 周期长时,这些对象可能很快失效,成为浮动垃圾主力
- 写屏障未覆盖场景:JNI 调用、反射操作绕过 JVM 写屏障,导致卡表未及时标记脏卡,影响重新标记准确性
调参控制浮动垃圾影响范围
浮动垃圾无法消除,但可通过参数压缩其窗口、延缓恶化速度:
- 下调
-XX:CMSInitiatingOccupancyFraction:根据历史日志中 CMS 实际触发点(如稳定在 72%–78%),设为 62–67,让 CMS 更早介入,缩短浮动窗口 - 禁用自适应策略:
-XX:-UseCMSInitiatingOccupancyOnly(JDK 7u4+ 默认关闭),避免 JVM 动态覆盖你设定的阈值 - 加强预清理控制:适当增加
-XX:CMSScavengeBeforeRemark,在重新标记前强制一次 Minor GC,减少跨代引用干扰 - 监控碎片水位:配合
-XX:+PrintGCDetails观察每次 CMS 后的空闲块数量与大小,CMSParPromotionFailed频发即表明碎片已达临界点
理解它为什么“留着不扫”
CMS 的并发清理阶段只清理「上一轮并发标记所确认的死亡对象」。清理过程中应用线程照常运行,新分配对象、新建立引用、对象复活等行为都会让部分本该回收的对象“漏网”。这部分对象不会在本次清理中被扫描或回收,只能等到下一次初始标记阶段重新纳入可达性分析——所以它叫“浮动垃圾”,本质是延迟处理,不是遗漏。











