"JVM 调优"四个字,面试人人都说,但真给一个卡顿的系统,大多数人第一反应是"加内存"。加内存不是调优,调优是看日志、找原因、对症下药。
这篇用一个真实场景串起来:线上接口偶发卡顿 3-5 秒,怎么定位、怎么调。 全程围绕 GC 日志、堆 dump、参数调整,不讲玄学。
一、症状与排查起点
症状:接口平时 TP99=120ms,高峰期偶发 2-5 秒超时。
第一步不是猜,是看 GC 日志:
bash
# 启动时带上 GC 日志参数
java -Xms4g -Xmx4g \
-Xlog:gc*:file=/logs/gc-%t.log:time,uptime,level,tags \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/logs/heap.hprof \
-jar app.jar
关键参数:
-Xlog:gc*记录所有 GC 事件(JDK9+ 统一日志)-XX:+HeapDumpOnOutOfMemoryErrorOOM 自动 dump- 日志按时间戳分割,保留历史
二、读 GC 日志:三个必看指标
GC 日志长这样(简化):
[2026-08-18 10:00:00] GC (Allocation Failure) 3.5G->2.1G(4G), 1.2s
[2026-08-18 10:00:02] GC (Allocation Failure) 2.8G->2.3G(4G), 1.8s
[2026-08-18 10:00:03] GC (Allocation Failure) 3.1G->2.5G(4G), 2.5s
三步定位:
| 指标 | 正常 | 异常 |
|---|---|---|
| GC 频率 | 几秒一次 | 几百毫秒一次(频发) |
| GC 耗时 | < 100ms | > 1s(卡顿来源) |
| 内存回收 | 回收后占用明显下降 | 回收后几乎不降(垃圾堆积) |
这份日志明显:GC 频繁且耗时 1-2 秒,说明堆接近满、对象释放不掉。
三、先查内存:是不是对象泄漏
GC 后内存几乎不降 → 疑似内存泄漏 。用 jmap dump 快照:
bash
jmap -dump:format=b,file=heap.hprof <pid>
# 再用 MAT / jvisualvm 分析
排查路径:
- 看
类实例数 Top:是否有大量同类型对象(如缓存 Map 无限增长) - 看
保留集(Retained Set):谁持有这些对象 - 看
GC Root:从哪条链被引用
常见元凶:
- 静态 Map/List 只增不减(缓存没做淘汰)
- ThreadLocal 不清理,线程池复用导致对象滞留
- 事件监听器注册后未移除
修法:改代码(缓存加 TTL、资源释放),比调参有用。
四、再看业务:GC 太快说明什么
如果 dump 发现对象都活着(不是泄漏),那就是堆太小或对象生命周期太长。业务层常见:
- 一次查询加载全表(百万级对象)
- 大 JSON 解析放堆
- 大量临时对象(循环里 new 大数组)
优先优化代码 (减少临时对象、分页、流式处理),调参只是辅助。
五、调参:对症下药
排除了泄漏和业务问题后,才动参数。调参顺序:
bash
# 场景1:频繁 Full GC,堆不够
-Xmx 调大(如 4g→8g)
# 场景2:Young GC 频繁,但对象存活短
调大年轻代:-XX:NewRatio=2(年轻:老 = 1:2)
或 -Xmn2g(年轻代 2G)
# 场景3:GC 停顿敏感
换 G1:-XX:+UseG1GC -XX:MaxGCPauseMillis=200
或 ZGC:-XX:+UseZGC(超大堆、超低延迟)
# 场景4:大对象多
调大:-XX:PretenureSizeThreshold(对象>阈值直接进老年代)
注意 :调参不是越大越好。堆太大 → GC 单次更久;调参每改一个都压测对比,不要一把梭。
六、调优方法论:三步走
第1步:观察 → 看 GC 日志、监控(频率/耗时/堆水位)
第2步:定位 → 内存 dump / 代码排查(泄漏 or 业务)
第3步:对症 → 代码优化 > 参数调整 > 换 GC
关键认知:
- 70% 的 JVM 问题是代码问题,不是参数问题
- 参数调优只是"最后的 30%";先查泄漏、先查大对象
- 调优必须压测验证,不是改完就完
七、总结
- 先看日志再动手:GC 频率、耗时、回收效果三指标
- 先排泄漏再调参:dump 分析对象,优先改代码
- 参数对症:调堆/调新老比/换 GC(G1/ZGC)
- 每改必测:对比验证,不玄学
一句话 :JVM 调优不是"把 -Xmx 调大",而是日志 → 定位 → 对症下药的过程。学会读日志,比背参数重要 10 倍。
下一篇:接口性能压测与优化实战,从 JMH 到生产瓶颈定位,看看怎么把接口从 200ms 压到 50ms。