ZGC并发标记耗时需通过日志中ConcurrentMarkStart/End时间戳差计算,须启用-Xlog:gc*,zgc=debug参数;典型1GB堆约20ms,需结合堆规模、引用复杂度及系统负载综合评估。

分析 GC 日志中并发标记阶段(Concurrent Mark)的耗时,关键在于识别日志中对应阶段的起始与结束事件,并计算时间差。ZGC 的并发标记不造成 STW,但其执行时长直接影响整体 GC 周期效率和 CPU 占用,需结合时间戳、阶段标识与上下文定位。
确认日志是否启用并发阶段详细记录
ZGC 默认可能不输出完整的并发阶段细节。必须启用足够粒度的日志参数,例如:
- -Xlog:gc*,zgc=debug:file=zgc.log:time,uptime,tags —— 同时开启 gc 标签全量、zgc 调试级,带时间戳和运行时长
- 避免仅用
-XX:+PrintGCDetails,该参数对 ZGC 支持有限,无法捕获ConcurrentMarkStart/End - 确保未误配为 G1 或 Shenandoah 参数,ZGC 日志标签必须含
zgc
从日志中精准提取并发标记起止行
ZGC 并发标记阶段在日志中表现为成对出现的事件:
- 起始行示例:
[1.234s][info][gc] GC(1) ConcurrentMarkStart - 结束行示例:
[1.237s][info][gc] GC(1) ConcurrentMarkEnd 2.912ms - 注意:
ConcurrentMarkEnd后的2.912ms是 JVM 统计的该阶段总耗时,但应以时间戳为准校验(如本例中 1.237 − 1.234 = 0.003s,即 3ms),二者应基本一致 - 若只看到
ConcurrentMarkStart没有对应End,说明 GC 周期异常中断或日志截断,需检查 JVM 是否崩溃或磁盘满
结合上下文判断耗时是否合理
单纯看数字不够,要联系堆规模、对象图复杂度和系统负载综合评估:
立即学习“Java免费学习笔记(深入)”;
- 典型参考值:1GB 堆下通常 20ms,需关注对象引用链深度或大对象分布
- 对比
Pause Mark Start到Pause Relocate Start的总间隔,若该间隔远大于并发标记耗时,说明中间存在其他瓶颈(如引用处理、类卸载) - 观察
ConcurrentMark阶段是否被频繁打断(如出现多次ConcurrentMarkStart但无End),可能因内存压力过大触发新 GC 周期抢占
自动化提取与趋势分析建议
人工翻查日志效率低,推荐结构化处理:
- 用正则匹配提取时间戳和阶段名,例如:
\[(\d+\.\d+)s\].*ConcurrentMark(Start|End) - 按 GC 编号(
GC(N))分组,计算每轮标记耗时,生成折线图观察波动 - 关联
Heap Before/After字段,若并发标记期间堆使用量突增(如因分配风暴),可能延长标记扫描范围


















