cms的并行行为仅出现在初始标记、重新标记的rescan子阶段及并发预清理等局部环节,用于加速stw任务;核心依赖卡表机制调度多线程处理脏卡,监控需重点关注stw耗时、rescan时间、脏卡数量及并发模式失败频率。

CMS本身不是以“并行收集”为设计目标的收集器,它的核心定位是并发(Concurrent)——即GC线程与用户线程同时运行(可能在同一个CPU上交替执行),而非多GC线程并行处理任务(如ParNew那样利用多核加速)。但需注意:CMS在部分阶段支持并行化子任务,这是常被混淆的关键点。
以下聚焦你关心的两个方面:哪些阶段有并行行为?实际执行细节如何?监控时应重点关注哪些指标?
一、CMS中真正存在并行行为的阶段
CMS整体是并发收集器,但其内部某些STW阶段或子任务会启用多线程并行执行,以缩短停顿时间。主要体现在:
-
初始标记(Initial Mark)
- JDK 8+ 默认开启并行(可通过
-XX:+CMSParallelInitialMarkEnabled控制); - 多线程扫描 GC Roots 直接引用的对象(包括:系统类加载器引用、JVM内部引用、栈帧中的局部变量等);
- 同时扫描新生代中存活对象对老年代的引用(因CMS需识别跨代引用,避免漏标);
- 耗时极短(通常
- JDK 8+ 默认开启并行(可通过
-
重新标记(Remark)中的 Rescan 子阶段
- 这是Remark中最耗时的部分,必须STW;
- 默认采用并行方式(
-XX:+CMSParallelRemarkEnabled,JDK7u4+默认启用); - 并行扫描三类区域:
• 新生代(查找晋升到老年代的引用);
• 老年代中被标记为“脏卡(Dirty Card)”的区域(由卡表记录,标识可能被修改的引用关系);
• GC Roots(线程栈、JNI引用、全局变量等); - 若新生代过大或脏卡过多,Rescan时间会显著上升——这是Remark停顿变长的主因。
-
并发预清理(Concurrent Preclean)和可中止预清理(Abortable Preclean)
- 虽然名义上“并发”,但部分内部遍历(如卡表扫描、引用更新检查)也会使用多个工作线程;
- 不触发STW,但依赖并行能力提升处理效率,为Remark减负。
二、关键执行细节:卡表(Card Table)与脏卡驱动并行调度
CMS高效依赖卡表机制,这是并行优化的基础:
- 老年代被划分为512字节为单位的“卡(Card)”,对应卡表中一个byte;
- 当新生代对象引用老年代,或老年代内引用发生变更时,JVM将对应卡标记为“dirty”;
- 初始标记和Remark阶段不扫描整个老年代,只并行处理所有dirty卡及其关联对象;
- 卡表本身是共享结构,CMS通过细粒度锁或无锁算法(如CAS)协调多线程访问,避免竞争瓶颈。
三、监控CMS并行行为是否生效的核心指标
仅看GC日志和堆内存不够,需结合以下指标判断并行是否起效、是否存在瓶颈:
-
初始标记与Remark的实际耗时(STW时间)
- 日志示例:
[CMS-initial-mark: 123456K(2097152K), 0.0042150 secs] - 若
initial-mark> 5ms 或remark> 50ms,说明并行未充分起效,或新生代/脏卡压力过大; - 对比开启/关闭并行参数(如
-XX:-CMSParallelInitialMarkEnabled)的日志变化,可验证效果。
- 日志示例:
-
并发阶段的CPU占用与吞吐影响
- 使用
top -H -p <pid></pid>观察CmsThreads线程数及单核占用率; - 若并发标记阶段CPU持续满载但应用RT(响应时间)明显升高,说明GC线程争抢资源严重,需调低
-XX:ParallelGCThreads或增加CPU配额。
- 使用
-
卡表相关计数(需开启详细GC日志)
- 添加参数:
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps; - 日志中关注:
[YG occupancy: xxx K (xxx K)](新生代占用)、[Preclean: xxx cards]、[Rescan (parallel): xxx ms]; -
Rescan (parallel)时间占比高 → 新生代过小或晋升频繁; -
Preclean扫描卡数持续增长 → 并发期间引用变更剧烈,需检查业务逻辑(如高频缓存更新、大对象反复创建)。
- 添加参数:
-
浮动垃圾与并发模式失败(Concurrent Mode Failure)频率
- 日志出现
concurrent mode failure→ CMS来不及在老年代填满前完成并发清理; - 常因Remark耗时过长、并发线程数不足或老年代增长过快导致;
- 此时会退化为Serial Old单线程Full GC,STW可达秒级——是并行调度失效的严重信号。
- 日志出现
四、调优建议:让并行真正发挥作用
- 确保新生代足够大且稳定:避免频繁Young GC导致大量对象晋升,减少Remark时需扫描的跨代引用;
- 设置合理并发线程数:
-XX:ParallelGCThreads=n(n一般设为CPU核心数,但CMS默认值已较优,不建议盲目调大); - 启用增量更新优化(JDK7+默认):
-XX:+CMSScavengeBeforeRemark,在Remark前强制一次Young GC,清空新生代引用压力; - 监控脏卡数量趋势:若
[Preclean]阶段日志中cards processed持续超过10万/次,考虑增大-XX:CMSInitiatingOccupancyFraction(如设为75),提前启动CMS,留出更多并发时间窗口。
CMS的“并行”是服务于“并发低停顿”目标的底层加速手段,不在主流程,而在关键STW子阶段。看清它在哪发力、为何有时失效,才能把监控数据转化为真实调优依据。











