JVM之实战案例

一、Web 服务 GC 频繁调优(最常见)

1. 现象

  • 接口偶尔超时、抖动
  • 监控:Minor GC 频繁、老年代缓慢上涨、定时 Full GC
  • CPU 偶尔飙升,STW 导致响应变慢

2. 根因

  1. 新生代过小,对象快速进老年代
  2. 业务短期小对象过多(JSON 序列化、临时集合、日志对象)
  3. Survivor 区太小,对象存活一轮直接晋升
  4. 未使用低延迟收集器,GC 停顿过长

3. 优化方案

  1. 调大新生代 -Xmn,提升短期对象回收效率

  2. 调整 SurvivorRatio,增大存活区,避免过早晋升

  3. 启用 G1,设置合理停顿时间

    1
    2
    -XX:+UseG1GC
    -XX:MaxGCPauseMillis=150
  4. 优化代码:复用对象、减少循环内创建集合、减少临时字符串

  5. 关闭不必要大日志、减少序列化频繁创建

4. 优化效果

  • Minor GC 频率大幅下降
  • 老年代增长平缓,Full GC 基本消失
  • 接口响应稳定,无定时抖动

二、大数据任务 内存泄漏排查

1. 现象

  • 批处理任务越跑越慢
  • 运行几小时后 OOM
  • 堆内存持续走高,GC 回收效率越来越低

2. 典型泄漏根因

  1. 大数据中间件连接(Hive/ES/Redis)连接未关闭
  2. 全局静态集合、缓存只新增不清理
  3. 迭代器、大数据分页流未关闭,引用堆积
  4. 线程池线程长期持有任务对象
  5. 第三方工具类非托管堆外内存不释放

3. 排查流程

  1. 定时拉取 jmap -dump 堆文件
  2. MAT 分析:
    • 查看大对象、集合占用
    • 支配树定位:长期持有引用的类
    • 查找无法回收的批次数据、连接对象
  3. 对比前后 dump,找出持续增长的实例

4. 解决手段

  1. try-with-resources 自动关闭流、连接
  2. 批量处理后手动清空集合、置空引用
  3. 局部化变量,避免长生命周期容器持有数据
  4. 分批读取、限制单次加载数据量
  5. 定期清理缓存、添加过期淘汰策略

三、微服务容器化 JVM 优化(避免 OOMKilled)

1. 现象

  • K8s 环境服务莫名重启
  • 日志无 OOM 异常,容器被系统杀死:OOMKilled
  • JVM 限制小于容器限制,堆外内存、元空间、线程内存超限

2. 核心原因

  1. 容器给内存 2G,JVM 只设堆 1.5G

    剩余内存:元空间 + 堆外内存 + 线程栈 + 缓冲区 无上限

  2. 未开启容器感知,JVM 读取宿主机内存,堆设置过大

  3. 直接内存、Netty 堆外内存无限制

  4. 线程数量过多,栈内存叠加占用高

3. 容器专属优化参数(重点)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 开启容器感知(JDK10+自带,JDK8需手动)
-XX:+UseContainerSupport

# 固定堆,不超容器配额
-Xms1g -Xmx1g

# 限制元空间
-XX:MetaspaceSize=128m
-XX:MaxMetaspaceSize=256m

# 限制堆外直接内存
-XX:MaxDirectMemorySize=256m

# 低延迟 ZGC / G1
-XX:+UseZGC

4. 优化思路

  1. JVM 堆 + 堆外内存总和 < 容器 limit 的 80%
  2. 必须限制:元空间、直接内存、线程数
  3. 开启容器感知,防止 JVM 误判内存
  4. 微服务轻量部署:选用低延迟收集器 ZGC/G1
  5. 合理隔离:业务堆内存、框架堆外内存、组件缓存

5. 最终效果

  • 不再被 OOMKilled 杀死
  • 内存占用平稳,容器资源利用率可控
  • 微服务弹性扩缩容更稳定

JVM之实战案例
http://hanqichuan.com/2026/04/17/jvm/JVM之实战案例/
作者
韩启川
发布于
2026年4月17日
许可协议