# 41. 为什么 GC 可能导致应用长时间停顿?如何降低 STW 时间?
# 标准答案
GC 可能导致应用长时间停顿(Stop-The-World, STW),主要原因是垃圾回收过程中某些阶段必须暂停所有应用线程,以保证内存一致性。例如,Full GC 需要遍历整个堆,导致长时间 STW。降低 STW 时间的关键在于优化 GC 机制,如采用并发回收(如 G1、ZGC、Shenandoah)、调整堆内存参数(如 -Xms、-Xmx)、减少晋升失败和避免频繁 Full GC。
# 答案解析
STW 发生的主要原因包括:
GC 需要遍历整个堆
例如 CMS、G1 在 Full GC 时,GC 线程必须遍历整个堆,导致应用线程长时间无法运行。Root Scanning(根对象扫描)
GC 需要扫描 GC Roots(如静态变量、线程栈等),如果对象过多,扫描时间变长,导致 STW 延长。对象移动和压缩
标记-整理(Mark-Compact)算法需要整理内存,避免碎片化,但整理过程涉及大量对象移动,可能导致 STW 增长。晋升失败(Promotion Failure)
年轻代对象晋升到老年代时,如果老年代空间不足,可能触发 Full GC,而 Full GC 需要完全暂停应用线程。Metaspace 内存管理
如果 Metaspace 过大或类加载频繁,GC 可能需要长时间清理无用类元数据,导致 STW 延长。
降低 STW 时间的方法:
选择低延迟 GC
- G1 GC:通过 Region 设计并发回收,避免长时间 Full GC。
- ZGC:基于染色指针和读屏障,最大化并发,STW <1ms。
- Shenandoah GC:采用并发压缩算法,避免 Full GC 造成的长时间停顿。
调整 JVM 参数
- 合理设置堆大小(-Xms、-Xmx):避免频繁 GC 造成的停顿。
- 优化新生代比例(-XX:NewRatio):提高吞吐量,减少晋升失败。
- 开启 GC 日志(-Xlog:gc):监控 GC 行为,分析 STW 发生原因。
减少大对象分配
- 大对象(如 1MB+ 数组)直接进入老年代,可能加速 Full GC,尽量使用对象池。
减少类加载开销
- 过多的动态类加载(如反射、字节码生成)可能导致 Metaspace OOM,建议定期回收无用类(-XX:+ClassUnloadingWithConcurrentMark)。
# 深入追问
为什么 CMS 的 Full GC 仍然会 STW?
- CMS 采用并发回收,但在 Full GC 时仍然需要 "Final Remark" 进行根对象扫描,导致 STW。
G1 的并发回收如何降低 STW?
- G1 采用 "Mixed GC",先并发回收部分老年代,减少一次性回收的压力,从而降低 STW。
ZGC 是如何做到 STW <1ms 的?
- ZGC 通过染色指针 + 读屏障,在对象访问时完成并发回收,避免 GC 过程中的集中停顿。
# 相关面试题
- Full GC 触发的原因是什么?如何避免?
- G1、ZGC、Shenandoah 适用于哪些业务场景?
- GC 日志如何分析 STW 发生的时间和原因?