记一次傻逼一样的 OOM 异常

问题发现

年前,公司一个傻逼运维找到我,说我负责的两个服务经常出现 OOM kill 的情况,让我尽快溯源解决问题,维护系统稳定性。 于是乎,我打开了系统稳定性监控大盘,惊讶的发现,内存利用率高达 95% ,我心思拉一下之前的看看啥样吧,结果一看,一年内都是这么高(一年是监控大盘的极限),基本每两天就会 OOM 一次,emmm,系统能跑

排查过程

  1. 检查日志,查看是否有明显 OOM 异常抛出。但是没有任何异常日志
  2. 重启服务,发现系统内存利用率稳定增长,最终达到临界值 95% 左右,趋势平缓,几乎停止增长,略有波动
  3. 打印 dump 文件,并未发现可疑的由代码产生的 OOM 可疑点 事已至此,略微有些懵逼,不知道从何查起,毕竟第一次遇到 OOM 异常,没有任何经验,之前在简历中写过类似的经验,不过无非是其他同事排查到后,我抄的别人的,根本没有任何头绪,没办法,求助 AI,以下内容,由 AI 总结:

一、第一步:确认 OOM 类型

1️⃣ 看 Pod 状态

如果是 K8s:

sql 复制代码
kubectl describe pod xxx

如果看到:

vbnet 复制代码
Reason: OOMKilled
Exit Code: 137

那说明:

❗ 是容器被 cgroup 强制干掉

JVM 根本来不及抛异常

这种情况下,日志里没有 OOM 是正常的。


二、搞清楚 JVM 内存构成

很多人误以为:

ini 复制代码
容器 1G
-Xmx512m
=> 没问题

这是典型误区。

JVM 内存 = 不只是堆。

你当时的启动参数:

diff 复制代码
-Xms512m
-Xmx512m
-XX:MaxDirectMemorySize=256m
-XX:MaxMetaspaceSize=256m
-Xss512k

我们算一笔账:

内存区域 大小
Heap 512m
Direct 256m
Metaspace 256m
线程栈(假设 300 线程) 300 × 512k ≈ 150m
CodeCache ~50m
JVM Native ~50m
GC 结构 ~几十 MB

👉 实际可能接近 1.2G+

而你的容器之前是 1G。

结果?

💥 还没等堆 OOM,容器先被杀了。


三、如何判断是不是堆泄漏?

jstat -gc

ini 复制代码
OU = 331540.3
OC = 349568.0

👉 Old 区使用率 ≈ 95%

并且:

  • FGC 次数很少(只有 4 次)
  • Old 使用量不下降

这很明显:

⚠️ 老年代对象回收不掉

这才是真正的堆泄漏特征。


四、标准排查流程(建议保存)


🧭 第一步:确认容器总内存

bash 复制代码
cat /sys/fs/cgroup/memory/memory.limit_in_bytes

确认 cgroup 限制。


🧭 第二步:看 JVM 真实内存占用

bash 复制代码
ps -eo pid,rss,vsz,comm | grep java

看 RSS。

或者:

css 复制代码
jcmd <pid> VM.native_memory summary

(前提开启 -XX:NativeMemoryTracking=summary


🧭 第三步:判断是不是堆问题

方法一:jstat 连续观察

xml 复制代码
jstat -gc <pid> 1000

看:

  • Old 是否持续上涨
  • FGC 后是否下降

如果:

sql 复制代码
FGC 后 Old 不降

👉 基本确定是堆泄漏。


🧭 第四步:导出堆快照

这是关键步骤。

perl 复制代码
jmap -dump:format=b,file=/tmp/heap.hprof <pid>

然后下载到本地。


🧭 第五步:用 MAT 分析

用:

  • Eclipse MAT
  • VisualVM

看:

  • Dominator Tree
  • Retained Size
  • 哪个类占用最多

一般常见元凶:

类型 场景
HashMap 缓存没清
ThreadLocal 忘 remove
List 不断 add
静态变量 永远不释放
业务缓存 没有上限
MQ 未消费队列 堆积

五、除了堆泄漏,还有哪些坑?

这次是堆老年代满,扩展一下全场景。


1️⃣ DirectMemory 泄漏

特征:

  • 堆不满
  • 容器内存上涨
  • 无 OOM 日志

常见场景:

  • Netty
  • NIO
  • ByteBuffer.allocateDirect
  • RocketMQ 客户端

排查:

ini 复制代码
-XX:NativeMemoryTracking=summary
jcmd VM.native_memory summary

看 Direct 是否暴涨。


2️⃣ 线程爆炸

特征:

  • 线程数持续上涨
  • RSS 上涨
  • Heap 正常

查看:

bash 复制代码
jstack | grep "nid=" | wc -l

每个线程 512k ~ 1M。

3000 线程就 3G。

很多人死在这里。


3️⃣ Metaspace 泄漏

特征:

  • 动态代理
  • 频繁加载类
  • 热部署框架

查看:

复制代码
jstat -gcmetacapacity

4️⃣ GC 算法问题

比如:

  • CMS 碎片
  • G1 region 不均衡
  • 晋升失败

可加:

ruby 复制代码
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps

或者 JDK11:

ruby 复制代码
-Xlog:gc*

5️⃣ 内存抖动(流量型上涨)

有些情况不是泄漏。

比如:

  • 瞬时大流量
  • 批量任务
  • 大对象 JSON 解析

特征:

  • Old 会涨
  • 但 FGC 后会降

这种不是泄漏。


六、根因推断

根据 jstat:

sql 复制代码
Old 使用率 95%
FGC 不下降

我判断:

✅ 是典型老年代对象泄漏

❌ 不是 Direct

❌ 不是线程爆炸

❌ 不是 Metaspace

你升级到 2G 只是延缓爆炸时间。

本质问题还在。


七、工程级防御建议

这是我个人强烈建议做的:


✅ 1. 永远预留 30% 容器内存

容器 2G:

diff 复制代码
-Xmx 1200m ~ 1400m

不要顶满。


✅ 2. 限制缓存大小

所有缓存必须:

  • 有上限
  • 有淘汰策略

推荐:

  • Caffeine
  • Guava Cache

✅ 3. 打开 OOM dump

ruby 复制代码
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/data/dump/

让问题可追溯。


✅ 4. 开启 NMT(生产可开 summary)

ini 复制代码
-XX:NativeMemoryTracking=summary

成本极低。


✅ 5. 做线程池审计

  • 不允许 newCachedThreadPool
  • 所有线程池必须有界

以上就是 AI 帮我总结的这一部分经验 总结起来,这一次的经历中,有一些 AI 提供的 Linux 命令还是挺好的,比如以下几条:

diff 复制代码
查看 Pod 状态(K8s)
kubectl describe pod <pod-name>
例如:
Last State:  
Terminated  
Reason: OOMKilled  
Exit Code: 137
说明:
-137 = 被 cgroup 杀
-JVM 来不及抛 OutOfMemoryError
-属于容器内存超限,不一定是堆 OOM
bash 复制代码
查看容器真实内存限制
cat /sys/fs/cgroup/memory/memory.limit_in_bytes
作用:查看容器 cgroup 实际限制(单位字节)
diff 复制代码
查看 Java 进程实际占用内存
ps -eo pid,rss,vsz,comm | grep java
字段说明:
-RSS:真实物理内存(单位 KB) ← 重点
-VSZ:虚拟内存
判断逻辑:
如果 RSS 接近容器限制 → 是整体内存超限

查看 JVM 堆使用情况

sql 复制代码
实时监控 GC
jstat -gc <pid> 1000
每秒打印一次
重要字段说明:
| 字段      | 含义           |
| -------  | ------------   |
| EC / EU  | Eden 容量 / 使用|
| OC / OU  | Old 容量 / 使用 |
| YGC      | Young GC 次数  |
| FGC      | Full GC 次数   |
| FGCT     | Full GC 总耗时 |
判断堆泄漏方法:
如果出现:
-OU 持续上涨
-FGC 后 OU 不下降
基本确认老年代泄漏

导出堆快照(关键步骤)

perl 复制代码
jmap -dump:format=b,file=/tmp/heap.hprof <pid>
作用:
生成堆内存快照文件
后续用:
-Eclipse MAT
-VisualVM
分析 Dominator Tree / Retained Size
diff 复制代码
查看 JVM Native 内存(堆外)
前提:启动时加

-XX:NativeMemoryTracking=summary

查看命令:

jcmd <pid> VM.native_memory summary
输出会看到:
Heap
Class
Thread
Code
GC
Compiler
Internal
Symbol
Native Memory Tracking
Arena Chunk
Direct
重点看:
-Direct(堆外内存)
-Thread(线程栈)
-Class(Metaspace)

查看线程数量

bash 复制代码
jstack <pid> | grep "nid=" | wc -l
或
top -H -p <pid>
说明:
-每个线程默认 512k ~ 1M
-1000 线程 ≈ 1G 内存
如果线程持续增长 → 线程泄漏

查看 Metaspace 使用情况

ini 复制代码
jstat -gcmetacapacity <pid>
或者看:
jstat -gc <pid>
字段:
-MC / MU = Metaspace 容量 / 使用
如果 MU 持续上涨 → 类加载异常

查看对象统计(快速定位大类)

diff 复制代码
jmap -histo <pid> | head -20
作用:
显示占内存最多的类
字段说明:
-instances:实例数量
-bytes:总占用字节
如果看到:
-HashMap 数量异常
-byte[] 特别大
-自定义类特别多
基本锁定方向

查看是否是 DirectMemory 泄漏

ini 复制代码
jcmd <pid> VM.native_memory summary
看:
- Direct
      (reserved=XXXMB, committed=XXXMB)
如果持续上涨 → Netty/NIO 问题

日志(强烈推荐) JDK8:

ruby 复制代码
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintHeapAtGC
-Xloggc:/data/gc.log

JDK11+:

-Xlog:gc*:file=/data/gc.log:time,uptime,level
作用:
判断:
-晋升失败
-老年代碎片
-Full GC 是否有效

自动生成 OOM Dump 生产必开:

ruby 复制代码
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/data/dump/
作用:
OOM 时自动生成堆文件

查看 JVM 参数

diff 复制代码
jcmd <pid> VM.flags
或
jinfo -flags <pid>
作用:
确认:
--Xmx
-MaxDirectMemorySize
-MetaspaceSize
-GC 算法

查看 JVM 各区域配置

diff 复制代码
jcmd <pid> GC.heap_info
作用:
查看:
-   新生代大小
-   老年代大小
-   Region 情况(G1)

查看系统层内存

sql 复制代码
free -m
top
cat /proc/meminfo
排除:
-其他进程占用
-系统缓存问题
常见问题定位对照表

| 现象                 | 大概率原因        |
| ----------------    | ------------     |
| Old 持续上涨          | 堆泄漏           |
| Heap 正常,RSS 上涨   | DirectMemory    |
| 线程数持续增加         | 线程泄漏         |
| Metaspace 涨         | 类加载异常       |
| GC 频繁但不释放        | 大对象/缓存失控   |
| 容器 OOMKilled 无异常  | cgroup 限制
相关推荐
花花无缺1 小时前
搞懂@Autowired 与@Resuorce
java·spring boot·后端
初次攀爬者1 小时前
RocketMQ 基础学习
后端·消息队列·rocketmq
重庆穿山甲1 小时前
Java开发者的大模型入门:LangChain4j组件全攻略(二)
后端
重庆穿山甲2 小时前
Java开发者的大模型入门:LangChain4j组件全攻略(一)
后端
颜酱2 小时前
单调队列:滑动窗口极值问题的最优解(通用模板版)
javascript·后端·算法
Java水解2 小时前
Rust嵌入式开发实战——从ARM裸机编程到RTOS应用
后端·rust
AI探索者2 小时前
LangGraph 条件路由:构建支持工具调用的智能 Agent
后端
苍何2 小时前
终于,我把 Openclaw 加 Seed2.0 Skills 做 AI 漫剧搞定了
后端
苍何3 小时前
阿里出手,最强Coding Plan出炉,OpenClaw可以痛快玩了
后端