JVM之实战案例
一、Web 服务 GC 频繁调优(最常见)
1. 现象
- 接口偶尔超时、抖动
- 监控:Minor GC 频繁、老年代缓慢上涨、定时 Full GC
- CPU 偶尔飙升,STW 导致响应变慢
2. 根因
- 新生代过小,对象快速进老年代
- 业务短期小对象过多(JSON 序列化、临时集合、日志对象)
- Survivor 区太小,对象存活一轮直接晋升
- 未使用低延迟收集器,GC 停顿过长
3. 优化方案
调大新生代
-Xmn,提升短期对象回收效率调整
SurvivorRatio,增大存活区,避免过早晋升启用 G1,设置合理停顿时间
1
2-XX:+UseG1GC
-XX:MaxGCPauseMillis=150优化代码:复用对象、减少循环内创建集合、减少临时字符串
关闭不必要大日志、减少序列化频繁创建
4. 优化效果
- Minor GC 频率大幅下降
- 老年代增长平缓,Full GC 基本消失
- 接口响应稳定,无定时抖动
二、大数据任务 内存泄漏排查
1. 现象
- 批处理任务越跑越慢
- 运行几小时后 OOM
- 堆内存持续走高,GC 回收效率越来越低
2. 典型泄漏根因
- 大数据中间件连接(Hive/ES/Redis)连接未关闭
- 全局静态集合、缓存只新增不清理
- 迭代器、大数据分页流未关闭,引用堆积
- 线程池线程长期持有任务对象
- 第三方工具类非托管堆外内存不释放
3. 排查流程
- 定时拉取
jmap -dump堆文件 - MAT 分析:
- 查看大对象、集合占用
- 支配树定位:长期持有引用的类
- 查找无法回收的批次数据、连接对象
- 对比前后 dump,找出持续增长的实例
4. 解决手段
- try-with-resources 自动关闭流、连接
- 批量处理后手动清空集合、置空引用
- 局部化变量,避免长生命周期容器持有数据
- 分批读取、限制单次加载数据量
- 定期清理缓存、添加过期淘汰策略
三、微服务容器化 JVM 优化(避免 OOMKilled)
1. 现象
- K8s 环境服务莫名重启
- 日志无 OOM 异常,容器被系统杀死:OOMKilled
- JVM 限制小于容器限制,堆外内存、元空间、线程内存超限
2. 核心原因
容器给内存 2G,JVM 只设堆 1.5G
剩余内存:元空间 + 堆外内存 + 线程栈 + 缓冲区 无上限
未开启容器感知,JVM 读取宿主机内存,堆设置过大
直接内存、Netty 堆外内存无限制
线程数量过多,栈内存叠加占用高
3. 容器专属优化参数(重点)
1 | |
4. 优化思路
- JVM 堆 + 堆外内存总和 < 容器 limit 的 80%
- 必须限制:元空间、直接内存、线程数
- 开启容器感知,防止 JVM 误判内存
- 微服务轻量部署:选用低延迟收集器 ZGC/G1
- 合理隔离:业务堆内存、框架堆外内存、组件缓存
5. 最终效果
- 不再被 OOMKilled 杀死
- 内存占用平稳,容器资源利用率可控
- 微服务弹性扩缩容更稳定
JVM之实战案例
http://hanqichuan.com/2026/04/17/jvm/JVM之实战案例/