线上出现的OOM是如何排查的?
线上出现的OOM是如何排查的?
月伴飞鱼看是哪种OOM?
看报错信息/监控/容器事件,区分类型,不同解法完全不一样。
Java heap
java.lang.OutOfMemoryError: Java heap space
GC overhead limit exceeded
(一直 GC 但回收极少)Direct/Off-heap
java.lang.OutOfMemoryError: Direct buffer memory
(NIO/Netty/ByteBuffer)- 原生内存耗尽(JNA、压缩、TLS、线程栈等)
Metaspace
java.lang.OutOfMemoryError: Metaspace
(类加载泄漏/频繁重载)系统/容器把进程杀了
- K8s:
OOMKilled
;Linux:dmesg
里Out of memory: Kill process
- JVM 日志未必有 OOM 栈
判定方法:应用日志、K8s 事件 (
kubectl describe pod
)、系统日志 (dmesg
)、JVM 日志(GC/错误日志)。
快速止血(把服务活下来)
扩容/降流/限并发:临时缩小线程池或限流;必要时扩副本。
增配但留余量:容器
memoryLimit
↑,同时 Xmx 要明显小于 limit(给 off-heap/线程/代码缓存留 30% 余量)。降级:关闭大对象功能(一次性加载、导出、聚合)、降低批量大小。
打开取证开关(见下一步),重启服务。
取证与快速体检(上线就该常备)
JVM 启动参数(长期建议)
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/dump
(自动导出 heap dump)-Xlog:gc*:file=/data/logs/gc.log:time,uptime,level,tags
(JDK9+;JDK8 用-Xloggc
)- (可选)
-XX:NativeMemoryTracking=summary
(开销小)或detail
线上排查常用命令
- 线程/堆:
jcmd <pid> GC.heap_info
、jcmd <pid> GC.class_histogram
jmap -histo:live <pid> | head -50
(大对象分布)jmap -dump:live,format=b,file=heap.hprof <pid>
jstack -l <pid>
(看看是否卡某些线程/ThreadLocal 泄漏)
- 原生内存:
jcmd <pid> VM.native_memory summary
(NMT:看 Direct / Internal / Thread 等占用)
- 容器/系统:
kubectl top pod
/describe pod
(OOMKilled
)ps -o pid,rss,command -p <pid>
、pmap -x <pid>
、dmesg | tail
对症下药(不同 OOM 的根因与修复)
Heap OOM / GC Overhead
症状:堆占用持续上升或 Full GC 频繁。
定位:用 Dump 在 MAT/VisualVM 看 Dominator Tree、GC Roots 路径,找最大保留集。
常见根因 & 修复
- 无限制缓存/Map(没有 TTL/最大容量)→ 上 Caffeine/Guava Size/Weight + TTL,监控命中率与大小
- 聚合/拼装大对象(一次性
toList()
、大StringBuilder
、无分页)→ 分页/分块处理,流式读写 - ThreadLocal 泄漏(未
remove
)→ try/finally 清理;线程池复用时尤需注意 - 反序列化/JSON 组装大对象 → 换流式解析(Jackson streaming)、限制字段
- 序列化队列堆积(MQ/队列消费不过来)→ 限流/背压,调小批量
调参兜底:
- 合理设置 Xms=Xmx(避免频繁扩容),用 G1/GenZ。
- 检查
-XX:MaxRAMPercentage
(容器内)与 Limit 的匹配,确保 Xmx < Limit × 0.7 左右。
Direct buffer / Off-heap OOM
症状:报
Direct buffer memory
或 RSS 高而堆不高。定位:NMT(
VM.native_memory summary
)、应用指标(Netty PooledArena)、-XX:MaxDirectMemorySize
。
根因 & 修复
- 未释放 ByteBuffer/Netty ByteBuf → 确保
release()
:用 Try-With-Resources 包装,开启 Netty 泄漏检测-Dio.netty.leakDetectionLevel=paranoid
(只在测试/预发) - 无上限的直接内存 → 配置
-XX:MaxDirectMemorySize=256m~1g
;Netty 配对象池且限制最大并发 - 大响应/压缩/SSL 原生内存 → 减小批量、分块,确认本地/Native 库版本
Metaspace OOM
症状:OutOfMemoryError: Metaspace
。
根因:类加载器泄漏(反复热加载/动态代理生成类、URLClassLoader 未关闭)。
修复:
- 修正热加载逻辑;减少频繁生成新 Class。
- 调整
-XX:MaxMetaspaceSize
但重点是修复泄漏。 jcmd <pid> VM.classloaders
、GC.class_stats
辅助判断。
unable to create new native thread
症状:线程数爆表或系统 ulimit -u
限制。
修复:
- 控线程池并发、合并池;合理
queue
与拒绝策略。 - 调整容器/系统线程数限制。
- 用
jstack
/top -H
找线程源头(哪个池在疯狂建线程)。
容器 OOMKilled(非 JVM 报 OOM)
症状:Pod 被杀,JVM没栈。
修复:
- JVM 堆(Xmx)+ 原生内存之和 必须显著小于 K8s Limit。
- JDK11+ 使用
-XX:MaxRAMPercentage
(如 50)配合 Limit。 - 预留线程栈/Direct/Metaspace/CodeCache 等空间(通常 ≥ 30% 预留)。
防复发(工程化治理)
- 监控告警:堆使用率、Young/Old GC 次数与时间、Full GC、RSS、Direct 内存、线程数、类加载数、对象分配速率。
- 容量/压测:关键接口做 3× 峰值压测,观察 P95/P99 与 RSS;设置自动化内存回归测试。
- 限流与背压:线程池有界队列 + 合理拒绝策略;对外部依赖设置超时与舱壁。
- 数据/批量:所有批处理/导出/聚合都限制批大小并流式处理。
- 开关与熔断:配置化关闭大对象功能;遇压自动降级。