解析ZGC并发标记日志需区分真实标记耗时与安全点延迟:Pause Initial Mark含线程停等安全点时间,须结合safepoint日志判断;Concurrent Mark Start/End间隔反映真正标记压力;Pause Final Update References持续>1.5ms表明引用队列积压。

解析 ZGC 的并发标记日志,核心是区分“真正标记耗时”和“被安全点拖慢的表观停顿”,不能只看 Pause Initial Mark 那个毫秒数。
先确认日志是否启用结构化输出
ZGC 默认日志不带足够上下文,必须显式配置才能准确定位并发标记行为:
- 用
-Xlog:gc*,safepoint=info:file=zgc.log:time,uptime,tags同时捕获 GC 阶段与安全点事件 - 禁用干扰项,如
-XX:+ZUncommit或-XX:+ZFragmentationLimit,避免日志混入非核心行为 - 确保没漏掉关键阶段:
Pause Initial Mark、Concurrent Mark Start/End、Pause Final Mark、Pause Final Update References
从日志里抓出并发标记的关键行
用 grep 提取真实标记周期线索:
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
grep "Pause Initial\|Pause Final\|Concurrent Mark" zgc.log- 典型组合示例:
[2026-09-15T14:22:01.887+0800] GC(127) Pause Initial Mark 0.942ms[2026-09-15T14:22:01.912+0800] GC(127) Concurrent Mark Start[2026-09-15T14:22:02.231+0800] GC(127) Concurrent Mark End 319ms[2026-09-15T14:22:02.311+0800] GC(128) Pause Final Mark 0.681ms
重点看三组关系,不是单看一个数字
并发标记是否健康,得交叉比对:
立即学习“Java免费学习笔记(深入)”;
- Pause Initial Mark 时间高(>1ms)+ safepoint block 时间占比大 → 线程卡在 native 调用或长循环里,不是 ZGC 问题,要查 JNI 或业务代码
- Concurrent Mark 耗时突增(比如从 30ms 跳到 400ms) → 根集膨胀(如大量线程本地变量、静态引用、JNI 全局引用),或对象图深度异常
- Pause Final Update References 持续 >1.5ms → 引用队列积压,WeakReference / PhantomReference 没及时清理,或应用频繁注册大量引用
分代 ZGC(JDK 21+)要分开看年轻代和老年代
启用 -XX:+ZGenerational 后,标记不再统一:
- 出现
Pause Young Initial Mark和Pause Old Initial Mark两条独立记录 - 年轻代标记应稳定在 0.3–0.6ms;若老年代标记明显更长,说明长期存活对象过多,或跨代引用 RSet 更新压力大
- 观察两者触发频率是否失衡:比如年轻代每秒标 5 次,老年代却隔几十秒才标一次,可能晋升策略不合理

















