cms的并发标记阶段不是多此一举,而是承担98%存活对象识别的核心阶段,通过与用户线程并发执行避免长时间stw,依赖增量更新和写屏障保障标记准确性,并为重新标记和清除阶段奠定基础。

CMS 的并发标记阶段不是“多此一举”,而是整个回收流程中真正承担核心工作的部分——它完成了约98%的存活对象识别任务,且全程与用户线程并行运行,是 CMS 实现低停顿目标的关键所在。
为什么必须并发?因为标记过程太重
初始标记只扫 GC Roots 直接引用的对象(比如栈帧里的局部变量、静态字段),数量极少;而并发标记要从这些对象出发,逐层遍历整个对象图,可能涉及数十万甚至上百万对象。若此时 STW,停顿可达数秒,对 Web 服务不可接受。所以 JVM 让 GC 线程和业务线程同时跑,用时间换响应。
- 它不暂停应用,但会占用 CPU 资源,高并发场景下可能加剧毛刺
- 标记范围包括老年代全部可达对象,也隐含扫描新生代——因为新生代对象可能持有对老年代的引用
- 过程中若发生 Young GC,晋升到老年代的新对象会被自动纳入本轮标记范围
怎么保证标记结果不漏?靠增量更新 + 写屏障
业务线程在跑,对象引用随时可能被修改(比如 obj.field = newObj),CMS 用写屏障机制实时捕获这些变动:只要老年代对象的字段被赋值指向另一个老年代对象,对应卡页(card)就被标为“脏”。这些脏卡不会立刻处理,而是留到重新标记阶段集中扫描。
- 写屏障开销小,但高频字段赋值(尤其老年代间引用)会导致脏卡堆积,拖长 Remark 时间
- 可通过
-XX:+PrintGCDetails观察日志中[Rescan (parallel)和[Remark]的耗时对比,后者明显更长说明并发期间变动太剧烈 - 卡表(card table)把老年代划分为 512B 一页的卡,每个卡对应一个布尔标志位,高效定位需重扫区域
它不是孤立阶段,而是承上启下的枢纽
并发标记本身不清理任何东西,但它为后续环节铺路:产生的脏卡供预清理和重新标记使用;它识别出的存活对象,决定了最终哪些内存能被并发清除。它的质量直接决定 Remark 阶段的工作量——标记越准、变动越少,STW 时间就越短。
- 如果并发标记中途老年代占用率逼近阈值(由
-XX:CMSInitiatingOccupancyFraction控制),CMS 可能提前终止该阶段,转入 Remark,避免并发失败 - 它不处理浮动垃圾(即标记后新产生的垃圾),这部分只能等下次 GC,过多会导致 OOM
- 标记结束后,对象图状态“冻结”在那一刻,后续所有修正都基于这个快照做增量修补
不复杂但容易忽略:并发标记不是“辅助步骤”,它是 CMS 区别于其他收集器的真正技术支点——用工程妥协换取用户体验,代价是更高的 CPU 占用和更精细的状态维护。











