cms通过拆解标记过程,仅初始标记和重新标记需stw,其余耗时阶段并发执行,将总停顿压缩至10–100ms;初始标记仅扫描gc roots直连对象,耗时几毫秒;并发标记与用户线程并行,不中断服务;重新标记快速修补漏标,依赖card table优化扫描范围。

CMS 通过把原本需要全程暂停用户线程的“标记”过程拆解为多个阶段,并让其中耗时最长的部分与应用程序线程**并发执行**,从而显著压缩 STW(Stop-The-World)时间。
初始标记阶段:快进快出,只做最小必要停顿
这个阶段仅标记 GC Roots 直接关联的老年代对象(比如静态变量、栈中引用指向的老年代实例),不递归追踪引用链。由于范围极小,STW 时间通常只有几毫秒。JDK 8 起默认启用多线程(-XX:+CMSParallelInitialMarkEnabled),进一步缩短停顿。
并发标记阶段:GC 线程和业务线程一起跑
从初始标记的对象出发,沿着引用链向下遍历并标记所有可达对象。这一过程由专门的 GC 工作线程完成,**用户线程持续运行**,不中断服务。虽然会占用部分 CPU 资源,但避免了长时间卡顿。
- 使用 Card Table 记录跨代引用,避免扫描整个年轻代
- 允许对象在标记过程中被修改,后续靠“重新标记”兜底
- 标记过程可能持续几十到几百毫秒,但用户无感知
重新标记阶段:修补并发导致的漏标
由于并发标记期间用户线程仍在修改对象图(如赋值、断开引用),可能导致部分对象被漏标。重新标记阶段会 STW,快速扫描:
- 年轻代中存活对象(因为它们可能引用老年代)
- Card Table 中被标记为“脏”的内存页(记录了跨代写操作)
- GC Roots 本身(如栈帧、JNI 引用等)
这个 STW 比初始标记稍长,但远短于传统串行标记的全堆扫描。
为什么这样能减少停顿?
传统老年代收集器(如 Serial Old)需 STW 完成整个标记+清除+整理,停顿常达数百毫秒甚至秒级。CMS 把最耗时的标记主干(90%+)放到并发阶段,只保留两个轻量级 STW 阶段(初始标记 + 重新标记),把总停顿控制在 10–100ms 量级,特别适合 Web 服务、实时接口等对响应延迟敏感的场景。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











