Skip to content

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

用 EasyGC 分析日志 →

报告会告诉你,你面对的是下面四种典型问题中的哪一种。

日志不能外传、或者要批量/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=200G1 停顿目标(别设得脱离现实)
-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:+UseCMSInitiatingOccupancyOnlyremark 停顿、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 的区别,再回来继续。

相关文章 ​