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。

相关推荐
leikooo2 小时前
用餐厅和盘子理解栈和堆
java·数据结构·链表·rust
Ivanqhz3 小时前
激活函数在 Transformer 中的作用及各种变体简述
java·linux·数据库·人工智能·深度学习
沙漠之主4 小时前
C++编程教学设计资料:从入门到实战的完整课程方案
java·前端·c++
FPGA小徐4 小时前
PA1 基础设施(2)深度解析:NEMU 中 ITRACE、MTRACE、FTRACE、IRINGBUF、ETRACE、DIFFTEST 全解析
java·服务器·网络
YOLO数据集集合4 小时前
EvoAgent:面向PR研发治理的自进化Multi-Agent Harness系统
java·开发语言·目标检测·agent·自进化
知行EDI4 小时前
知行之桥 MaBang 端口使用指南——Create Order 订单创建篇
java·服务器·数据库
笑鸿的学习笔记5 小时前
C++笔记之大块顺序写
java·c++·笔记
打工仔折腾 AI5 小时前
从 Demo 到生产级 Agent:8 个关键设计机制与 Python 实现拆解
java·jvm·人工智能·后端·python·langchain·ai agent 实战
殷色玫瑰6 小时前
C/C++ 内存管理详解:从内存分布到 new/delete 底层原理
java·linux·c语言·c++
青山木7 小时前
Hot 100 --- 编辑距离
java·数据结构·算法·leetcode·动态规划