4.JVM调优
1. 调优目标
核心目标:减少 Full GC 频率,缩短 GC 停顿时间。
最常见的问题是内存分配不合理,对象频繁晋升老年代,导致频繁 Full GC,系统每隔几分钟卡顿几秒。分代模型和参数含义见 2.JVM内存划分,GC 触发条件见 3.垃圾回收,G 1 参数调优见 6.G1垃圾回收器。
调优思路:让每次 Young GC 后的存活对象小于 Survivor 的 50%,尽量留在年轻代,减少进入老年代的对象。
2. 常用诊断工具
# 查看 GC 整体情况,每 1000ms 打印一次,共 10 次
jstat -gc <PID> 1000 10
# 年轻代 GC 分析(TT=存活时间,MTT=最大存活时间)
jstat -gcnew <PID>
# 老年代 GC 分析
jstat -gcold <PID>
# 查看堆内存分布
jmap -heap <PID>
# 按对象大小排序,找内存占用大的类
jmap -histo <PID>
# 导出内存快照
jmap -dump:live,format=b,file=dump.hprof <PID>
# 分析内存快照(浏览器访问 localhost:7000)
jhat dump.hprof -port 7000

线上推荐用 Arthas,比 jmap 更安全,不会触发 Full GC。更完整的调优参数参考 JVM性能调优方法。
3. 压测观察指标
压测时重点关注:
- Eden 区对象增长速率
- Young GC 触发频率和耗时
- 每次 Young GC 后存活对象大小
- 每次 Young GC 有多少对象进入老年代
- 老年代增长速率
- Full GC 触发频率和耗时
4. 频繁 Full GC 的常见原因
| 原因 | 排查方式 |
|---|---|
| Survivor 太小,对象频繁溢出到老年代 | jstat -gcnew 看 Survivor 使用率 |
| 一次性加载大量数据(大对象) | jmap -histo 看大对象 |
| 内存泄漏,对象无法被回收 | MAT 分析 dump 文件 |
| MetaSpace 不够,类加载过多 | jstat -gcmetacapacity |
代码中显式调用 System.gc() |
加 -XX:+DisableExplicitGC 禁止 |
5. 调优示例
问题:每次 Young GC 后有 20~30 MB 对象进入老年代,频繁触发 Full GC。
原因:Survivor 只有 10 MB,装不下存活对象,直接溢出到老年代。
调整前:
-XX:NewSize=100m -XX:MaxNewSize=100m
-XX:InitialHeapSize=200m -XX:MaxHeapSize=200m
-XX:SurvivorRatio=8 # Survivor 只有 10MB
调整后:扩大年轻代,调整 Survivor 比例:
-XX:NewSize=200m -XX:MaxNewSize=200m
-XX:InitialHeapSize=300m -XX:MaxHeapSize=300m
-XX:SurvivorRatio=2 # Survivor 扩大到 50MB

6. 新系统 JVM 参数设置流程
- 压测,收集 Eden 增长速率、Young GC 频率和耗时、存活对象大小
- 根据存活对象大小,确保 Survivor 能装下(存活对象 < Survivor 50%)
- 根据老年代增长速率,估算 Full GC 频率是否可接受
- 上线后持续监控,根据实际数据微调