concurrent mark 耗时过长会延长 gc 总耗时、增大内存压力并可能引发 concurrent mode failure 从而触发 full gc,需从对象图规模、堆结构、gc 参数和系统资源四方面排查。

Concurrent Mark 耗时过长,说明并发标记阶段拖慢了整个 GC 周期,虽然不造成 STW,但会延长 GC 总耗时、增加内存压力,甚至诱发 concurrent mode failure(并发模式失败),最终触发 Full GC。排查需从对象图规模、堆结构、GC 参数和系统资源四方面入手。
看日志确认是否真“过长”
先区分是单次耗时异常,还是持续偏高:
- 对比基线:ZGC/G1 的 Concurrent Mark 通常应控制在几百毫秒内;CMS 的 concurrent-mark 阶段若 >1s,就值得警惕
- 查日志关键词:
CMS-concurrent-mark(CMS)、Concurrent Mark(G1)、Concurrent Mark(ZGC 中对应Concurrent Mark阶段,非 Pause) - 注意时间单位:日志中如
[CMS-concurrent-mark: 0.340/0.340 secs],前者是用户态耗时,后者是真实耗时;若 real time 显著大于 user time,可能受 CPU 或内存带宽争抢影响
检查老年代对象图是否过大或太深
Concurrent Mark 的工作量取决于从 GC Roots 可达的老年代活跃对象数量与引用链深度:
- 对象存活率高:用
jstat -gc <pid></pid>查看O(老年代使用率)长期 >75%,说明大量对象晋升且未被回收 - 存在大缓存或长生命周期对象:比如静态 Map 缓存了上万条业务数据、未清理的监听器、未关闭的连接池引用等
- 引用关系复杂:如对象图存在多层嵌套、循环引用(虽不影响可达性判定,但增加遍历开销)或大量弱/软引用待处理
排查 CMS/G1/ZGC 各自的典型诱因
不同收集器机制不同,原因有差异:
-
CMS:Concurrent Mark 阶段依赖初始标记(CMS-initial-mark)结果;若初始标记遗漏太多对象(如 Survivor 晋升激增),后续并发标记负担加重;也常见于
-XX:CMSInitiatingOccupancyFraction设置过低,导致过早启动并发周期 - G1:Concurrent Mark 启动时机由堆占用率触发;若 Region 碎片化严重、Remembered Set(RSet)过大,标记过程需频繁跨 Region 查找,拖慢速度
-
ZGC:Concurrent Mark 是纯并发阶段,但若堆极大(>100GB)且活跃对象超亿级,或启用
-XX:+ZVerifyViews等调试选项,也会明显拉长耗时
验证系统资源与配置是否受限
并发标记虽不 STW,但仍消耗 CPU 和内存带宽:
- CPU 不足:标记线程数默认为 CPU 核心数 × 0.6(G1/ZGC);可通过
-XX:ParallelGCThreads或-XX:ConcGCThreads适当调高(但不宜超过物理核数) - 发生 swap:用
free -h和vmstat 1观察 si/so 列;swap 会导致内存访问延迟飙升,直接拖垮并发标记吞吐 - 元空间或直接内存干扰:Metaspace 持续增长(类加载泄漏)会间接增加 GC Roots 数量;DirectByteBuffer 过多也会扩大扫描范围










