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。

相关推荐
Percep_gan9 小时前
调用天翼云的对象存储ZOS,很详细
java·1024程序员节
若亦_9 小时前
数据结构>>优先级队列(堆)
java·数据结构
Wang's Blog9 小时前
Java 项目实战: 交友项目 - 查询圈子个人动态:查询我的动态需求分析
java·开发语言·交友
denlaku9 小时前
JDK 23 新特性详解
java
code2cat9 小时前
Java高级篇之Hibernate:让对象映射到表,理解持久化与事务
java·后端·hibernate·1024程序员节
骑着蜗牛撵大象32710 小时前
飞牛fnOS部署Damselfly:Compose编排、照片目录与7100固定访问
java·docker·rpc·dubbo·fnos·照片管理·damselfly
金銀銅鐵11 小时前
[Java] 用 CLI 展示 EnumSet 增减元素时的内部变化(下)
java·后端·ai编程
Nuanyt11 小时前
Hot 100 刷题笔记 · 数组与哈希专题(3 题)
java·笔记·算法·leetcode·哈希算法·1024程序员节
xieliyu.11 小时前
【JavaEE进阶】Spring Boot 日志:从 System.out 到 @Slf4j,6 个级别 + 4 类配置一篇讲透
java·spring boot·spring·java-ee·springboot·logback·slf4j
IT_Octopus12 小时前
Apollo 配置中心入门:一所“小学校“读懂 Portal、Meta Server、Config Service
java·后端·spring