cms产生浮动垃圾的根本原因是用户线程在并发标记和清除阶段持续运行:标记阶段新产生的垃圾逃过标记,清除阶段新分配对象未被标记,且重新标记仅修正漏标存活对象以保障正确性,不处理漏标垃圾以避免stw延长。

CMS收集器在并发标记与并发清除阶段产生浮动垃圾,根本原因在于它必须容忍用户线程持续运行——这既是低延迟的来源,也是浮动垃圾的根源。
并发标记阶段:新垃圾“逃过标记”
标记过程从GC Roots出发,遍历可达对象并打上“存活”标记。但用户线程在标记过程中仍在运行,可能:
- 断开所有对某对象的引用,使其变为不可达(即新垃圾),但该对象已在标记中被标为黑色,不会再被重新扫描
- 分配全新对象,且该对象无法从当前GC Roots路径到达(比如刚new出来还没被任何变量引用),也不会被本次标记覆盖
这些对象未被标记为垃圾,自然不会进入后续清理范围,就成了浮动垃圾。
并发清除阶段:新分配对象直接“变白”
清除阶段只处理已被标记为“死亡”的对象。此时标记已结束,用户线程继续分配新对象:
- 新分配的对象默认是白色(未标记),而CMS不再启动新一轮标记
- 这些对象可能生命周期极短,在清除过程中就失去所有引用,成为新的不可达对象
- 它们既没被标记为存活,也没被标记为死亡,更不会被本次清除动作触及
重新标记阶段为何不解决浮动垃圾?
重新标记(Remark)的目标很明确:只修正“本该存活却被漏标”的对象(如并发期间被新引用指向的对象),防止误回收——这是正确性问题。
而浮动垃圾属于“本该回收却漏标”的对象,修正它需要重新遍历整个对象图,等价于再做一次并发标记,会显著拉长STW时间,违背CMS低延迟的设计初衷。因此CMS选择容忍这部分垃圾,留待下一次GC处理。
写屏障机制的覆盖边界限制
CMS依赖写屏障捕获老年代对象间的引用更新,用于支持增量更新和Remark阶段的脏卡扫描。但它不跟踪:
- 新生代→老年代的新引用(由卡表机制粗粒度记录,精度有限)
- 对象创建本身(new操作不触发写屏障)
- finalize复活、JNI绕过JVM内存管理等非常规路径
这些场景产生的对象或引用变动,容易导致标记结果滞后,进一步扩大浮动垃圾规模。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











