Kubernetes Pod OOMKilled 排查:从 cgroup memory.events 到 JVM 堆外内存
一、OOMKilled 不一定是内存泄漏
Kubernetes 里 Pod 反复重启,kubectl describe pod 显示 Last State: Terminated, Reason: OOMKilled, Exit Code: 137。很多人的第一反应是"代码内存泄漏",但实际排查下来,至少一半是资源配置或 JVM/Go 运行时参数没对齐。
Exit Code 137 = 128 + 9,表示进程被 SIGKILL。容器里触发 SIGKILL 的通常是 cgroup 的 OOM killer,而不是 Linux 内核全局 OOM。区别很重要:全局 OOM 看 dmesg,cgroup OOM 要看容器的 memory.events。
二、先确认是哪种 OOM
# 节点层面
dmesg -T | grep -i "out of memory"
# cgroup v2(较新内核)
cat /sys/fs/cgroup/kubepods/.../memory.events
# oom_kill 计数增长,说明是容器级 OOM
如果 memory.events 里 oom_kill 在涨,就是容器超过 limit 被杀。这时再看 memory.max 和 memory.current 是否贴得很近。
三、常见四种原因
1)limit 设得太小
Java 应用最典型。JVM 默认堆是物理内存的 1/4,但在容器里如果没开 UseContainerSupport(JDK 8u191 之前),JVM 看到的是宿主机内存,堆会按宿主机算,直接超过 limit。
-XX:MaxRAMPercentage=70.0 -XX:InitialRAMPercentage=70.0
给堆留 20%–30% 给元空间、直接内存、线程栈和 page cache。
2)堆外内存没算进去
Netty 的 direct buffer、gRPC、大量线程栈(每线程 1MB 默认)、mmap 文件,都不在堆里。Go 程序则要注意 GOGC 和 GOMEMLIMIT:
GOMEMLIMIT=750MiB GOGC=100
3)瞬时峰值
批量任务、大文件解析、导出 Excel 这类场景会在几秒内申请大量内存。limit 按平均用量设置必然被杀。要么把任务拆批,要么给这类 Pod 单独更大的 limit,并用 HPA 按内存扩。
4)真正的泄漏
如果重启后内存单调上升、GC 后也降不下来,才是泄漏。用 jmap -histo:live 或 pprof 抓堆,看对象增长。常见来源是无界缓存、ThreadLocal 未清理、监听器注册后没反注册。
四、排查步骤
- 看
kubectl describe pod的Limits和Requests,确认 limit 数值; - 看容器内
memory.current/memory.peak与 limit 的比值; - 对比重启前后的 RSS 曲线,判断是"阶梯上升"还是"尖峰";
- 阶梯上升 → 抓 heap/profile;尖峰 → 调批量大小或提高 limit;
- 检查 JVM/Go 运行时是否感知到容器限制。
五、配置建议
- requests 按 P95 用量设,limits 按 P99 峰值再留 20% 余量;
- Java 用
MaxRAMPercentage而不是固定-Xmx; - Go 设
GOMEMLIMIT为 limit 的 80%; - 给关键服务配
PodDisruptionBudget,避免 OOM 重启时同时挂掉; - 用 VPA 的 recommend 模式观察真实用量,别凭感觉填数字。
六、小结
OOMKilled 是结果不是原因。先用 memory.events 区分全局 OOM 和 cgroup OOM,再判断是配置不匹配、堆外内存、瞬时峰值还是真泄漏。多数情况下改对运行时参数和 limit 就能解决,不要一上来就怀疑代码。
- 点赞
- 收藏
- 关注作者
评论(0)