简体中文
JVM 性能优化指南(GC 调优)
JVM 调优不是玄学,而是一个循环:测量 → 只改一件事 → 验证。本篇给你工作流、"症状 → 对策"对照表,以及真正重要的那几个参数。
第一步:动手之前先测量
永远不要盲调。先开启 GC 日志(生产安全,开销 < 3%),并分析一个覆盖业务高峰的时间窗口:
bash
# Java 9+
java -Xlog:gc*:file=gc.log:time,uptime -jar app.jar
# Java 8
java -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log -jar app.jar报告会告诉你,你面对的是下面四种典型问题中的哪一种。
日志不能外传、或者要批量/CI 分析时,用 GCViewer 在本地做同样的事:
bash
# 导出"每事件一行"的时间序列,前后两次调优可以直接 diff
java -jar gcviewer-1.37.jar gc.log before.csv -t CSV_TS第二步:症状对对策
| 报告中的症状 | 可能原因 | 尝试方案 |
|---|---|---|
| Full GC 频繁 | 老年代过小 / 内存泄漏 / 缓存过大 | 调大 -Xmx;排查泄漏(GC 后堆逐级抬升);审视缓存容量 |
| 分配速率过高 | 短命对象风暴 | 热点路径对象池化 / 缓存;减少每请求垃圾 |
| 最大停顿超过 SLO | 回收器不对或堆太大 | 换 G1/ZGC;G1 设置 -XX:MaxGCPauseMillis |
| 吞吐量 < 90% | 回收频率过高 | 加大年轻代(-Xmn / -XX:NewRatio);加大堆 |
| 元空间触发 GC | 类加载风暴 | 调大 -XX:MaxMetaspaceSize;排查动态代理 |
原因里有 System.gc() | 显式调用(RMI/NIO) | -XX:+DisableExplicitGC——先确认无 Direct ByteBuffer 依赖 |
| G1 巨型对象分配 | 对象超过分区一半 | 调大 -XX:G1HeapRegionSize;优化大数组设计 |
第三步:一次只改一个参数
调一项、重新分析、对比报告——这是确认有效性的唯一方式。覆盖 90% 场景的参数:
| 参数 | 作用 |
|---|---|
-Xms = -Xmx | 固定堆大小——消除伸缩停顿,行为更可预测 |
-Xmn / -XX:NewRatio | 年轻代大小——控制 GC 频率的主杠杆 |
-XX:+UseG1GC / -XX:+UseZGC | 回收器选择——控制停顿长度的主杠杆 |
-XX:MaxGCPauseMillis=200 | G1 停顿目标(别设得脱离现实) |
-XX:MaxMetaspaceSize | 类元数据上限 |
-XX:+DisableExplicitGC | 忽略 System.gc() 调用 |
按回收器选杠杆
同样的症状,不同回收器的旋钮完全不同。先确认线上用的是哪个(java -XX:+PrintFlagsFinal -version | grep Use.*GC):
| 回收器 | 适用场景 | 关键参数 | 报告里重点看 |
|---|---|---|---|
| G1(JDK 9+ 默认) | 堆 4 GB 以上、吞吐与延迟兼顾 | -XX:MaxGCPauseMillis=200;巨型对象 → -XX:G1HeapRegionSize;并发周期太晚 → -XX:InitiatingHeapOccupancyPercent | 最大停顿、巨型对象分配、Mixed GC 占比 |
| ZGC | 延迟优先,堆可到 TB 级 | -XX:+UseZGC;JDK 21+ 加 -XX:+ZGenerational | 停顿是否稳定在毫秒级、有无 Allocation Stall(回收跟不上分配) |
| Shenandoah | 低延迟,堆中等 | -XX:+UseShenandoahGC | 停顿、并发周期是否被提前打断 |
| Parallel | 吞吐优先,批处理 | -XX:+UseParallelGC、-XX:GCTimeRatio=19(≈95% 吞吐)、-XX:MaxGCPauseMillis | 吞吐量、Full GC 次数 |
| Serial | 容器/小堆(百 MB 级) | -XX:+UseSerialGC | 单次停顿 |
| CMS | 仅存于 JDK 8(JDK 9 弃用、JDK 14 移除) | -XX:CMSInitiatingOccupancyFraction=70、-XX:+UseCMSInitiatingOccupancyOnly | remark 停顿、Concurrent Mode Failure |
三份可以直接抄的参数模板
bash
# ① 在线服务(G1,4C8G 容器)——延迟与吞吐平衡
-Xms4g -Xmx4g
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/dump/
-Xlog:gc*:file=/var/log/app/gc.log:time,uptime:filecount=5,filesize=50M
# ② 批处理 / 离线任务(吞吐优先)
-Xms8g -Xmx8g
-XX:+UseParallelGC
-XX:GCTimeRatio=19 # 目标 1/(1+19) = 5% 时间用于 GC,即 95% 吞吐
-Xlog:gc*:file=/var/log/app/gc.log:time,uptime
# ③ 低延迟服务(ZGC,JDK 21+)
-Xms8g -Xmx8g
-XX:+UseZGC
-XX:+ZGenerational
-Xlog:gc*:file=/var/log/app/gc.log:time,uptime:filecount=5,filesize=50M-Xms 与 -Xmx 相等是在去掉堆伸缩带来的额外停顿;日志带 filecount/filesize 是为了长期开着也不撑爆磁盘——分析时把轮转出来的几个文件当一份连续日志读即可。
第四步:验证并留下基线
重新分析新的 GC 日志,与旧报告对比:吞吐量升了吗?最大停顿降了吗?Full GC 消失了吗?把两份报告(PDF/JSON)存档作为基线——下次发版后分析一次,就知道 GC 行为有没有回退。
检查清单
TIP
术语还不熟?先看 JVM、JRE、JDK 的区别,再回来继续。
相关文章
- GCViewer 使用教程 — 内网 / 批量场景下的离线分析与命令行导出
- JVM 排查工具箱 — 按场景讲解 jmap、jstack、jstat 与 Arthas
- JVM 性能监控指标 — 本篇工作流所测量的数字
- 开启 GC 日志 — 一切分析的基础