CMS日志核心是区分并发阶段(CMS-concurrent-xxx)与两次STW阶段(Initial Mark、Remark),识别Concurrent Mode Failure退化为Serial Old,结合ParNew日志分析晋升压力,并关注元空间GC对CMS的干扰。

CMS(Concurrent Mark-Sweep)收集器的日志结构清晰但阶段多,重点在于区分“并发阶段”和“STW阶段”,并识别是否发生退化。它不追求吞吐量,而是尽量减少停顿,因此日志中大量出现 CMS-concurrent-xxx 类型行,而真正暂停应用的只有 Initial Mark 和 Remark 两个环节。
看懂 CMS 日志的三个关键阶段
CMS GC 分为初始标记、并发标记、预清理、可中断预清理、重新标记、并发清除六个主要阶段,其中只有 Initial Mark 和 Remark 是 STW 的:
-
Initial Mark(初始标记):日志形如
[GC (CMS Initial Mark) [1 CMS-initial-mark: 342870K(349568K)] ...]。这是第一次 STW,仅扫描 GC Roots 直接关联的对象,耗时极短(通常 -
Remark(重新标记):日志含
CMS: abort preclean due to time或直接[GC (CMS Final Remark) ...]。这是第二次 STW,修正并发期间变动的对象引用,耗时比 Initial Mark 长;若耗时 > 100ms 或频繁发生,常因并发标记未完成就被打断,或业务线程产生大量写屏障更新 -
CMS-concurrent-xxx 阶段:如
CMS-concurrent-mark-start、CMS-concurrent-sweep等。这些是并发执行、不暂停应用的阶段,日志中会带具体耗时(如[CMS-concurrent-mark: 0.001/0.001 secs])。注意其“real”时间(墙钟时间)应远大于 “user+sys”,否则说明 CPU 资源紧张,影响并发效率
识别 CMS 是否退化为 Serial Old
一旦 CMS 并发失败(如并发标记未完成老年代就满了),就会触发 Concurrent Mode Failure,此时 JVM 会立即启动一次 Full GC,并回退到单线程的 Serial Old 收集器 —— 这是 CMS 最严重的性能事故:
- 日志中明确出现:
[Full GC (Concurrent Mode Failure) - 后续紧跟
[Tenured: ...]或[ParOldGen: ...]字样消失,取而代之的是类似[Tenured: 349568K->349568K(349568K)](即未回收成功)或长时间 STW(> 1s) - 退化后老年代使用率往往持续攀升,很快再次触发 Full GC,形成恶性循环
结合新生代 ParNew 日志判断晋升压力
CMS 通常搭配 ParNew 使用,所以日志中一定包含 ParNew 行。它的变化直接反映对象是否过早进入老年代:
- 看每次 ParNew 后老年代使用量是否明显上升:例如
[ParNew: 136418K->17311K(157248K)], 442378K->360181K(506816K)中,整体堆从 442378K → 360181K,但老年代部分(= 整体 − 新生代剩余)从 ~305K → ~343K,说明约 38K 对象晋升 - 若连续多次 ParNew 后老年代增长 > 20K,且 Survivor 区未满(日志中无
Desired survivor size或new threshold降低提示),大概率是对象生命周期长或分配速率过高 - 配合
-XX:+PrintTenuringDistribution可看到每轮 GC 后各年龄对象分布,若age 1: 123456B占比极高,说明大量对象熬过第一次 GC 就晋升,Survivor 空间可能偏小
关注 CMS 触发原因与元空间行为
CMS 自身不管理元空间(Metaspace),但元空间不足会强制触发 Full GC,掩盖真实问题:
- 日志中出现
Full GC (Metadata GC Threshold)且伴随[Metaspace: xxxK->yyyK(zzzK)]增长,说明类加载过多或未及时卸载;应检查是否动态生成类(如 Spring AOP、Groovy 脚本)、是否遗漏-XX:MaxMetaspaceSize - CMS 默认在老年代使用率达 92% 时启动并发周期,可通过
-XX:CMSInitiatingOccupancyFraction=75提前触发,避免临界点并发失败;但设太低会导致频繁并发周期,浪费 CPU


















