面试知识库
高 困难

GC调优实战#

一句话答案#

GC 调优核心:选对收集器(G1/ZGC)、合理设置堆大小、分析 GC 日志找到瓶颈、监控 GC 频率和停顿时间。

核心要点

1. 调优目标先定清楚:

  • 吞吐量:GC 时间占总运行时间的比例,一般超过 5% 就值得优化。
  • 停顿时间:单次 STW 时长(在线服务看 P99/P999 延迟是否被 GC 拖高)。
  • 内存占用:堆越大停顿间隔越长,但单次回收可能更久。三者不能同时最优。

2. 调优步骤:

  1. 开 GC 日志:JDK 8 -XX:+PrintGCDetails -Xloggc:<file>;JDK 9+ -Xlog:gc*:file=<file>;配合 GCeasy/GCViewer 统计频率、停顿、吞吐。
  2. 选收集器:JDK 8 默认 Parallel(吞吐优先);JDK 9+ 默认 G1;大堆且要求极低停顿用 ZGC(JDK 15 转正,JDK 21 起有分代模式)。
  3. 定堆大小:-Xms = -Xmx 避免堆伸缩;容器里用 -XX:MaxRAMPercentage。
  4. 针对现象调整(见下表),每次只改一个参数,改完对比前后日志。

3. 常见现象与方向:

现象可能原因方向
Young GC 频繁Eden 太小 / 分配速率高增大年轻代;G1 不要固定 -Xmn(会让 G1 无法自动调年轻代满足停顿目标)
Full GC 频繁大对象直接进老年代、对象过早晋升、内存泄漏、元空间不足查 dump 定位泄漏;调晋升阈值/Survivor;详见 Full-GC频繁排查
G1 停顿超目标MaxGCPauseMillis 设太小或 Mixed GC 回收跟不上放宽停顿目标、提前并发标记(调 IHOP)、增大堆
停顿要求 < 10msG1 难以满足换 ZGC

4. Full GC 五大触发源(老年代满 / System.gc() / 空间担保失败 / Metaspace 满 / CMS 的 CMF):Full GC 整堆 STW,调优的主要目标之一就是减少它。

面试回答(2分钟版)

GC 调优的核心原则是先分析 GC 日志定位瓶颈,再针对性调整参数,切忌盲目调参。首先选对收集器,JDK 8 默认 Parallel GC 注重吞吐量,对延迟敏感的服务建议切换到 G1 或 ZGC。然后合理设置堆大小,-Xms 和 -Xmx 设成一样避免堆动态伸缩的开销。判断 GC 是否是性能瓶颈看 GC 时间占应用总运行时间的比例,超过 5% 就需要优化。具体调优方向:如果 Minor GC 频繁说明 Eden 区太小,增大年轻代比例;如果 Full GC 频繁要分析是大对象直接进老年代、对象过早晋升还是内存泄漏。G1 收集器通过 MaxGCPauseMillis 设置目标停顿时间,但设太小会导致 G1 频繁 GC 每次只收集少量 Region,吞吐量下降。生产中必须配置 HeapDumpOnOutOfMemoryError 保留现场,同时开启 GC 日志便于事后分析。调优本质是在吞吐量和停顿时间之间找平衡。

追问与易错

追问方向:

  • “你实际做过 GC 调优吗?调了哪些参数?”→ 经验题,建议准备真实案例:如从 Parallel GC 切换到 G1、调整 -Xmx/-Xms、设置 MaxGCPauseMillis、调整 NewRatio 等,说清调优前后的 GC 频率和停顿变化
  • “怎么判断 GC 是性能瓶颈?”→ GC 时间占比 >5% 说明 GC 是瓶颈,通过 GC 日志统计 GC 总耗时与应用运行总时间的比值
  • “G1 的 MaxGCPauseMillis 设太小会怎样?”→ G1 为满足过小的停顿目标,每次只回收很少的 Region,导致 GC 频繁但每次回收量不够,老年代持续增长最终触发 Full GC,吞吐量大幅下降

易错点:

  • ❌ 盲目调参不看 GC 日志——调优前先分析日志找瓶颈
  • ❌ 只关注停顿不关注吞吐——两者需要平衡