JVM 调优实战:从 GC 日志到参数优化,一次完整排查

"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:+HeapDumpOnOutOfMemoryError OOM 自动 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 分析

排查路径

  1. 类实例数 Top:是否有大量同类型对象(如缓存 Map 无限增长)
  2. 保留集(Retained Set):谁持有这些对象
  3. 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%";先查泄漏、先查大对象
  • 调优必须压测验证,不是改完就完

七、总结

  1. 先看日志再动手:GC 频率、耗时、回收效果三指标
  2. 先排泄漏再调参:dump 分析对象,优先改代码
  3. 参数对症:调堆/调新老比/换 GC(G1/ZGC)
  4. 每改必测:对比验证,不玄学

一句话 :JVM 调优不是"把 -Xmx 调大",而是日志 → 定位 → 对症下药的过程。学会读日志,比背参数重要 10 倍。

下一篇:接口性能压测与优化实战,从 JMH 到生产瓶颈定位,看看怎么把接口从 200ms 压到 50ms。

相关推荐
gugucoding1 小时前
57. 【Java】日志框架:SLF4J与Logback
java·开发语言·logback
爱奥尼欧1 小时前
10.C++ string 实现详解
java·开发语言·c++
小飞学编程...1 小时前
【equals 、Comparable、Comparator 三者的区别】
java·python
骇客野人1 小时前
Java分布式任务调度方案(完整落地指南)
java·开发语言·分布式
JacksonMx1 小时前
Spring @Async 坑点 + 改造方案
java·开发语言
Dovis(誓平步青云)2 小时前
牛奶不能减成负数:冰箱库存的筛选、去重与不可变更新
android·java·开发语言
SWAGGY..2 小时前
【C++初阶】:(15)模板进阶--从非类型参数到模板特化与分离编译
java·服务器·前端
楠楠子呀3 小时前
独立站聊天转化全链路:从二维码引流到数据复盘
java·javascript·数据仓库·python·c#·自动化·etl
yurenpai(27届找实习中)3 小时前
从零读懂AI智能客服(二):Java 多模块项目如何协作:Maven 依赖、Spring 注入与运行时装配
java·spring·maven