三色标记仅发生在初始标记、并发标记和重新标记三个阶段;初始标记标gc roots为灰色,并发标记执行深度遍历并存在漏标风险,重新标记通过写屏障修正漏标,而并发清除阶段不进行任何标记操作。

CMS并发清理阶段本身不执行三色标记,三色标记只在并发标记和重新标记阶段起作用;浮动垃圾则是在并发标记结束后、并发清除开始前及过程中新产生的垃圾,无法被本轮GC回收,只能留待下次GC处理。
三色标记实际发生在哪几个阶段
三色标记是CMS标记过程的核心机制,但它并不贯穿整个GC周期:
- 初始标记阶段:将GC Roots直接可达的对象标为灰色,是三色标记的起点,但仅限于根对象,不涉及深度遍历。
- 并发标记阶段:真正执行三色标记逻辑——从灰色集合中取对象,将其引用的对象加入灰色,自身移入黑色;该阶段与用户线程并发运行,因此存在漏标风险。
- 重新标记阶段:修正并发标记期间因用户线程修改引用导致的漏标(如黑色对象新增指向白色对象),通过“增量更新”或“原始快照”(SATB)等写屏障技术捕获变更,确保最终存活对象不被误判为垃圾。
- 并发清除阶段:不进行任何标记操作,仅扫描并回收仍为白色的对象;此时三色标记已结束,状态固化,不再动态更新。
浮动垃圾是怎么产生的
浮动垃圾指在并发标记完成后、并发清除完成前,由用户线程新创建或重新引用后又弃用的对象,它们未被本轮标记覆盖,因而逃过回收:
- 并发标记结束后,所有未被标记(即仍为白色)的对象本应被清除;但此时用户线程可能刚分配了一个新对象,或把某个已标记为黑色的对象的引用置为null,导致其子对象变为不可达。
- 这些对象在并发清除阶段尚未被识别为垃圾,也不会被重新标记,因此不会被清理,只能成为“浮动垃圾”,堆积在老年代中。
- 典型例子:A→B→C构成引用链,标记过程中A、B已变黑,C尚未访问(仍白);随后用户线程执行
B.c = null,C立刻不可达,但因C未被标记且标记已结束,它将被错误保留,成为浮动垃圾。
CMS如何应对浮动垃圾的累积
浮动垃圾无法避免,CMS的设计默认接受这一代价,重点在于防止其引发更严重问题:
- 通过参数
-XX:CMSInitiatingOccupancyFraction提前触发GC,预留足够空间容纳浮动垃圾,避免老年代填满导致“Concurrent Mode Failure”(退化为Serial Old的STW Full GC)。 - 浮动垃圾本身不影响程序正确性,只是多占内存;只要下一次GC能及时启动,它们就会被正常回收。
- 不进行压缩整理,所以浮动垃圾会加剧内存碎片;若碎片过多影响大对象分配,可能触发额外的Full GC。
- 监控
CMS-concurrent-abortable-preclean等日志项,可辅助判断浮动垃圾增长是否异常,结合堆使用率趋势调整触发阈值。
为什么不能在并发清除时动态重标
并发清除阶段放弃动态标记,是CMS在吞吐量与正确性之间做的关键权衡:
- 若在清除阶段允许标记更新,需持续维护写屏障、同步灰色集合,大幅增加开销,抵消低停顿优势。
- 清除本身是无状态扫描:只需遍历卡表或 remembered set,快速释放白色对象内存,无需再做图遍历。
- 设计目标明确——“低延迟优先”,接受少量内存浪费,换取确定性的短暂停顿(仅初始标记和重新标记)。











