# 41. 为什么 GC 可能导致应用长时间停顿?如何降低 STW 时间?

# 标准答案

GC 可能导致应用长时间停顿(Stop-The-World, STW),主要原因是垃圾回收过程中某些阶段必须暂停所有应用线程,以保证内存一致性。例如,Full GC 需要遍历整个堆,导致长时间 STW。降低 STW 时间的关键在于优化 GC 机制,如采用并发回收(如 G1、ZGC、Shenandoah)、调整堆内存参数(如 -Xms、-Xmx)、减少晋升失败和避免频繁 Full GC。

# 答案解析

STW 发生的主要原因包括:

  1. GC 需要遍历整个堆
    例如 CMS、G1 在 Full GC 时,GC 线程必须遍历整个堆,导致应用线程长时间无法运行。

  2. Root Scanning(根对象扫描)
    GC 需要扫描 GC Roots(如静态变量、线程栈等),如果对象过多,扫描时间变长,导致 STW 延长。

  3. 对象移动和压缩
    标记-整理(Mark-Compact)算法需要整理内存,避免碎片化,但整理过程涉及大量对象移动,可能导致 STW 增长。

  4. 晋升失败(Promotion Failure)
    年轻代对象晋升到老年代时,如果老年代空间不足,可能触发 Full GC,而 Full GC 需要完全暂停应用线程。

  5. Metaspace 内存管理
    如果 Metaspace 过大或类加载频繁,GC 可能需要长时间清理无用类元数据,导致 STW 延长。

降低 STW 时间的方法:

  1. 选择低延迟 GC

    • G1 GC:通过 Region 设计并发回收,避免长时间 Full GC。
    • ZGC:基于染色指针和读屏障,最大化并发,STW <1ms。
    • Shenandoah GC:采用并发压缩算法,避免 Full GC 造成的长时间停顿。
  2. 调整 JVM 参数

    • 合理设置堆大小(-Xms、-Xmx):避免频繁 GC 造成的停顿。
    • 优化新生代比例(-XX:NewRatio):提高吞吐量,减少晋升失败。
    • 开启 GC 日志(-Xlog:gc):监控 GC 行为,分析 STW 发生原因。
  3. 减少大对象分配

    • 大对象(如 1MB+ 数组)直接进入老年代,可能加速 Full GC,尽量使用对象池。
  4. 减少类加载开销

    • 过多的动态类加载(如反射、字节码生成)可能导致 Metaspace OOM,建议定期回收无用类(-XX:+ClassUnloadingWithConcurrentMark)。

# 深入追问

  1. 为什么 CMS 的 Full GC 仍然会 STW?

    • CMS 采用并发回收,但在 Full GC 时仍然需要 "Final Remark" 进行根对象扫描,导致 STW。
  2. G1 的并发回收如何降低 STW?

    • G1 采用 "Mixed GC",先并发回收部分老年代,减少一次性回收的压力,从而降低 STW。
  3. ZGC 是如何做到 STW <1ms 的?

    • ZGC 通过染色指针 + 读屏障,在对象访问时完成并发回收,避免 GC 过程中的集中停顿。

# 相关面试题

  • Full GC 触发的原因是什么?如何避免?
  • G1、ZGC、Shenandoah 适用于哪些业务场景?
  • GC 日志如何分析 STW 发生的时间和原因?