stop-the-world(stw)是jvm执行gc等操作时强制暂停所有应用线程的机制,主要由gc触发(占90%以上),但也可能源于偏向锁撤销、类卸载、安全点等待等;需结合-xx:+printgcapplicationstoppedtime、-xlog:gc*及safepoint统计精准定位根因。

Stop-The-World(STW)问题一旦由垃圾收集器(GC)引发,通常表现为应用线程全部暂停、响应延迟突增、监控图表出现尖刺。定位这类问题不能只看GC日志的“pause”字样,关键在于把GC行为与业务现象对齐,并确认是否真由收集器主导——而非JVM参数误配、堆外内存泄漏或系统资源争用等干扰项。
看懂GC日志里的真实停顿信号
启用详细GC日志(如 -Xlog:gc*:file=gc.log:time,uptime,level,tags 或旧版 -XX:+PrintGCDetails -XX:+PrintGCTimeStamps),重点抓三类字段:
- “Pause”明确标出STW开始和结束时间:例如 [GC pause (G1 Evacuation Pause) (young), 0.1234567 secs] 中的秒数是真实STW时长,不是GC总耗时
- 对比“user”、“sys”、“real”三时间:若 real ≈ user + sys,说明停顿基本发生在JVM内;若 real 远大于二者之和,可能卡在OS调度、锁竞争或IO等待
- 关注触发原因:如 Allocation Failure 表示因内存分配失败触发;System.gc() 则是代码主动调用,需排查是否有显式GC
排除非GC类STW干扰源
很多“疑似GC停顿”实际与收集器无关:
- Full GC未必等于STW最重:ZGC/Shenandoah 的 Full GC 可并发执行,而 G1 的 Mixed GC 也可能只暂停部分区域;反过来,CMS 的 Concurrent Mode Failure 会退化为 Serial GC 式 STW
- 检查 safepoint 机制:JVM所有线程必须到达安全点才能进入STW。用 -XX:+PrintGCApplicationStoppedTime 查看每次停顿是否都对应 safepoint 达成;若某次停顿远长于GC日志记录,可能是线程卡在长时间计算、IO或native调用中无法进入safepoint
- 警惕元空间/CodeCache满导致的STW:Metaspace扩容失败或CodeCache耗尽也会触发全局停顿,日志中体现为 Metadata GC Threshold 或 CodeCache is full
用工具交叉验证GC行为与业务毛刺
单靠日志易误判,需结合运行时观测:
-
jstat 实时盯住GC频率与停顿趋势:执行 jstat -gc -h3
1000 ,观察 GCT(GC总耗时) 和 GCT/GC次数 比值是否异常升高;YGC频繁但YGCT不涨?可能是对象存活率高、晋升快,触发更多Mixed GC - Arthas trace + watch 定位触发点:对高频分配对象(如String、ByteBuf)做 trace,看是否某接口调用后立即发生Young GC;用 watch 监控 java.lang.System::gc 调用来源
- 火焰图辅助识别safepoint瓶颈:用 async-profiler 采集 --event cpu --safe 火焰图,若大量线程堆栈卡在 SafepointSynchronize::block 下方,说明有线程迟迟无法进入安全点
针对性调优前先确认根本动因
别急着改XX:MaxGCPauseMillis或换GC算法——先回答三个问题:
- 停顿是否集中在特定时段?比如定时任务跑完后立刻发生 → 检查该任务是否生成大量临时对象或调用System.gc()
- 停顿是否随QPS上升线性增长?→ 更可能是分配速率过高,而非GC策略问题;考虑对象复用或减少中间对象
- 停顿前后堆内存使用是否陡升?→ 查JFR事件中的 Object Allocation In New TLAB 和 Object Allocation Outside TLAB,确认是否存在TLAB过小或大对象直接进老年代











