stw是jvm执行gc等关键操作时强制暂停线程的机制,其停顿受应用层对象分配、引用变化等“变量级行为”间接影响;需通过分阶段gc日志、业务打点对齐、高风险代码模式识别及jstack排查伪停顿来精准定位根因。

启用带阶段信息的GC日志,锁定STW耗时来源
只看总停顿时长无法定位根因,必须拆解各子阶段开销:
- 用 -Xlog:gc*,gc+phases=debug:file=gc.log:time,uptime,tags(JDK 10+)或 -XX:+PrintGCDetails -XX:+PrintGCTimeStamps(JDK 8)开启详细日志
- 重点关注 [Ext Root Scanning]、[Update RS]、[Evacuation]、[Reference Processing] 这几项耗时占比
- 若 [Update RS] 占比超40%,说明跨Region引用密集——可能由大对象频繁创建、集合类长期持有引用等“变量级行为”导致
- 若 [Reference Processing] 异常高,检查是否滥用软/弱引用,或ThreadLocal未清理造成引用链过长
把业务打点和STW时间窗口对齐验证
GC日志里的毫秒级停顿,是否真影响了用户?必须叠加业务侧视角:
- 在网关或核心服务入口统一埋点,记录 requestId + startTime + endTime + gcWindowHit(布尔值)
- 用Prometheus采集 jvm_gc_pause_seconds_count{action="endOfMajorGC",cause="Metadata GC Threshold"} 和 http_server_requests_seconds_p95
- 在Grafana中做双Y轴时间序列对比:观察某次127ms的Mixed GC发生时刻,是否同步出现P95延迟从80ms跳升至250ms
- 命中率 >15% 就说明该类STW已传导至业务层,需优先优化对应代码路径
识别三类高风险“变量操作”,它们不写GC日志但拉长STW
有些行为不会直接触发GC,却让每次STW更重、更频繁:
- 静态集合无节制put:static Map/ConcurrentHashMap持续增长 → Metaspace持续扩容 → 触发 Metaspace GC(需单独加 -Xlog:metaspace* 才能捕获)
- StringBuilder反复.toString():生成大量短命大字符串 → Eden区秒级填满 → Young GC间隔从2s缩至200ms → STW频次×10
- ThreadLocal存放大缓存对象且未remove:GC时需遍历整个ThreadLocalMap → 延长根扫描和引用处理阶段 → STW单次延长3–8ms(线程数越多越明显)
用jstack排除“伪STW”,聚焦真实变量干扰
业务线程卡在锁、IO或外部调用上,容易被误认为是GC停顿:
- 在STW高发时段执行 jstack -l
,检查线程状态分布:若多数为 BLOCKED 或 WAITING on java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject,说明是锁竞争,不是GC问题 - 对比 jstat -gc
中FGC次数 与 jstack输出中 java.lang.ref.ReferenceHandler 线程堆栈出现频次:若前者远高于后者,说明存在非JVM主动触发的长时间停顿 - 发现大量线程停在 sun.nio.ch.EPollArrayWrapper.epollWait(Native Method),应排查网络IO瓶颈,而非调优GC参数










