知识模块
☕ Java 知识模块
四、JVM 深入理解
JVM 调优

JVM 调优

面试提问

"线上服务出现 Full GC 频繁,CPU 飙高,你如何排查和调优?"


核心概念

JVM 调优的本质是在有限资源下,让应用获得最佳性能。调优不是盲目调整参数,而是基于监控数据做出的科学决策。

调优目标

指标说明目标值
吞吐量单位时间处理的请求数越高越好
延迟请求响应时间越低越好
内存占用JVM 使用内存在可控范围内
GC 停顿STW 时间尽量短

⚠️ 吞吐量和延迟往往不可兼得,需要根据业务场景取舍。


JVM 参数分类

1. 内存参数

-Xms        初始堆大小(建议与 Xmx 相同)
-Xmx        最大堆大小
-Xmn        年轻代大小
-Xss        线程栈大小
-XX:NewRatio        年轻代与老年代比例(默认 1:2)
-XX:SurvivorRatio   Eden 与 Survivor 比例(默认 8:1:1)
-XX:MetaspaceSize   元空间初始大小
-XX:MaxMetaspaceSize 元空间最大大小

2. GC 参数

-XX:+UseSerialGC        使用 Serial 收集器
-XX:+UseParallelGC      使用 Parallel 收集器(JDK8 默认)
-XX:+UseConcMarkSweepGC 使用 CMS 收集器
-XX:+UseG1GC            使用 G1 收集器(JDK9+ 默认)
-XX:+UseZGC             使用 ZGC(JDK15+)

3. GC 日志参数

# JDK8 及之前
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-Xloggc:gc.log

# JDK9+
-Xlog:gc*:file=gc.log:time,level,tags

调优实战步骤

第一步:监控发现问题

监控指标

  • GC 频率(Young GC / Full GC 次数)
  • GC 耗时(单次 STW 时间)
  • 内存使用趋势(是否泄漏)
  • CPU 使用率

工具

  • jstat -gcutil <pid> 1000 - 实时 GC 统计
  • jmap -heap <pid> - 堆内存信息
  • jvisualvm / Arthas - 可视化监控
  • GCViewer - GC 日志分析

第二步:分析定位原因

常见问题

现象可能原因排查方向
Young GC 频繁年轻代太小增大年轻代
Full GC 频繁老年代填满 / 元空间满检查内存泄漏
GC 耗时长堆太大 / 大对象多调整堆大小
CPU 飙高GC 线程竞争 / 业务问题分析线程栈

第三步:调整优化

调优原则

  1. 先调整内存分配,再更换 GC 收集器
  2. 一次只调一个参数,对比效果
  3. 记录基准数据,量化优化效果

典型场景调优

场景一:Young GC 频繁

现象:每秒多次 Young GC,但 Full GC 很少

分析:年轻代太小,对象过早晋升到老年代

调优

# 增大年轻代(直接指定或调整比例)
-Xmn512m
# 或
-XX:NewRatio=1  # 年轻代:老年代 = 1:1

场景二:Full GC 频繁

现象:Full GC 间隔很短,系统卡顿

分析

  1. 老年代空间不足
  2. 元空间满(加载太多类)
  3. 内存泄漏

调优

# 增大堆内存
-Xms2g -Xmx2g
 
# 增大元空间
-XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m
 
# 如果是 G1,降低触发阈值
-XX:InitiatingHeapOccupancyPercent=35  # 默认45

场景三:大对象导致 GC 慢

现象:每次 GC 耗时很长,有大量大对象

分析:大对象直接进入老年代,或 Humongous 区域

调优

# 设置大对象阈值(Serial/Parallel)
-XX:PretenureSizeThreshold=1m
 
# G1 的 Humongous 对象阈值(Region 的 50%)
# G1 默认 Region 大小 = 堆大小 / 2048
-XX:G1HeapRegionSize=16m

场景四:延迟敏感型应用

要求:GC 停顿控制在 50ms 以内

调优

# 使用 G1,设置停顿目标
-XX:+UseG1GC
-XX:MaxGCPauseMillis=50
 
# 或使用 ZGC(JDK15+)
-XX:+UseZGC
-XX:ZCollectionInterval=5  # 主动 GC 间隔

常用调优参数速查

堆内存配置

参数默认值建议
-Xms物理内存 1/64-Xmx 相同,避免动态扩容
-Xmx物理内存 1/4不超过物理内存 80%
-Xmn堆的 1/3高频创建对象可增大

GC 收集器选择

JDK 版本默认 GC推荐场景
JDK 8Parallel批处理、计算密集
JDK 9+G1通用、延迟敏感
JDK 15+ZGC超低延迟(<10ms)

G1 关键参数

-XX:+UseG1GC                      # 启用 G1
-XX:MaxGCPauseMillis=200          # 最大停顿时间目标
-XX:G1HeapRegionSize=16m          # Region 大小
-XX:InitiatingHeapOccupancyPercent=45  # 触发并发 GC 的堆占用率
-XX:G1NewSizePercent=5            # 年轻代最小占比
-XX:G1MaxNewSizePercent=60        # 年轻代最大占比

调优案例分析

案例:电商服务 Full GC 频繁

现象

  • 4G 堆内存,每 5 分钟一次 Full GC
  • 每次 Full GC 停顿 2-3 秒
  • 系统响应时间抖动

排查过程

# 1. 查看 GC 统计
jstat -gcutil 12345 1000 10
# 发现老年代使用率接近 100%
 
# 2. 生成堆转储
jmap -dump:format=b,file=heap.hprof 12345
 
# 3. MAT 分析,发现大量 Order 对象无法回收

问题根因

  • 订单缓存未设置过期时间
  • 订单对象持续累积

解决方案

  1. 业务层面:给缓存设置过期时间
  2. JVM 层面:增大堆内存 + 切换 G1
# 调整后参数
-Xms4g -Xmx4g
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200

优化效果

  • Full GC 频率:5 分钟 → 2 小时
  • GC 停顿:2-3 秒 → 100ms 以内

调优误区

❌ 误区一:调优能解决所有问题

调优只是锦上添花,代码问题才是根本

  • 内存泄漏 → 排查代码
  • 大对象频繁创建 → 优化业务逻辑
  • 死循环/阻塞 → 定位具体代码

❌ 误区二:堆越大越好

堆过大会导致:

  • GC 扫描时间变长
  • 内存浪费
  • 可能触发 swap,性能骤降

建议:堆大小不超过物理内存的 80%

❌ 误区三:盲目复制参数

不同应用的特点不同:

  • 计算密集 vs IO 密集
  • 吞吐量优先 vs 延迟优先
  • 小堆 vs 大堆

建议:基于监控数据,针对性调优


面试要点总结

问题答案要点
如何发现 GC 问题?jstat 监控、GC 日志分析、可视化工具
Young GC 频繁怎么办?增大年轻代、调整 SurvivorRatio
Full GC 频繁怎么办?排查内存泄漏、增大堆/元空间、调整 G1 阈值
如何选择 GC 收集器?JDK8 用 Parallel/G1,JDK9+ 优先 G1,超低延迟用 ZGC
调优的核心原则?先监控后调优、一次一个参数、量化效果

相关题目

  1. 说一说常用的 JVM 调优参数?
  2. 如何排查线上 CPU 飙高问题?
  3. G1 和 CMS 的调优有什么区别?
  4. 线上服务 OOM 如何排查?

参考资料