高 困难
GC调优实战#
一句话答案#
GC 调优核心:选对收集器(G1/ZGC)、合理设置堆大小、分析 GC 日志找到瓶颈、监控 GC 频率和停顿时间。
核心要点
1. 调优目标先定清楚:
- 吞吐量:GC 时间占总运行时间的比例,一般超过 5% 就值得优化。
- 停顿时间:单次 STW 时长(在线服务看 P99/P999 延迟是否被 GC 拖高)。
- 内存占用:堆越大停顿间隔越长,但单次回收可能更久。三者不能同时最优。
2. 调优步骤:
- 开 GC 日志:JDK 8
-XX:+PrintGCDetails -Xloggc:<file>;JDK 9+-Xlog:gc*:file=<file>;配合 GCeasy/GCViewer 统计频率、停顿、吞吐。 - 选收集器:JDK 8 默认 Parallel(吞吐优先);JDK 9+ 默认 G1;大堆且要求极低停顿用 ZGC(JDK 15 转正,JDK 21 起有分代模式)。
- 定堆大小:
-Xms=-Xmx避免堆伸缩;容器里用-XX:MaxRAMPercentage。 - 针对现象调整(见下表),每次只改一个参数,改完对比前后日志。
3. 常见现象与方向:
| 现象 | 可能原因 | 方向 |
|---|---|---|
| Young GC 频繁 | Eden 太小 / 分配速率高 | 增大年轻代;G1 不要固定 -Xmn(会让 G1 无法自动调年轻代满足停顿目标) |
| Full GC 频繁 | 大对象直接进老年代、对象过早晋升、内存泄漏、元空间不足 | 查 dump 定位泄漏;调晋升阈值/Survivor;详见 Full-GC频繁排查 |
| G1 停顿超目标 | MaxGCPauseMillis 设太小或 Mixed GC 回收跟不上 | 放宽停顿目标、提前并发标记(调 IHOP)、增大堆 |
| 停顿要求 < 10ms | G1 难以满足 | 换 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 日志——调优前先分析日志找瓶颈
- ❌ 只关注停顿不关注吞吐——两者需要平衡