容器里 Java 进程被 OOMKill,为什么堆 dump 里看不出内存溢出

举报
yd_216394844 发表于 2026/11/01 00:45:41 2026/11/01
【摘要】 线上容器里的 Java 应用被 OOMKill 杀掉,第一反应是导堆 dump 分析,结果 dump 里堆内存只用了 60%,完全看不出问题。这种情况几乎每个跑 K8s 的团队都会遇到一次,原因往往是下面几种。第一,被杀的不是 Java 堆,而是整个容器。K8s 的 OOMKill 依据的是容器 cgroup 的内存使用量,包含堆、元空间、线程栈、直接内存(DirectByteBuffer)...

线上容器里的 Java 应用被 OOMKill 杀掉,第一反应是导堆 dump 分析,结果 dump 里堆内存只用了 60%,完全看不出问题。这种情况几乎每个跑 K8s 的团队都会遇到一次,原因往往是下面几种。

第一,被杀的不是 Java 堆,而是整个容器。K8s 的 OOMKill 依据的是容器 cgroup 的内存使用量,包含堆、元空间、线程栈、直接内存(DirectByteBuffer)、Code Cache、以及 mmap 映射的文件页。堆用 60% 但容器超了 limit,说明内存花在堆外。

第二,堆外内存三大户。DirectByteBuffer 靠 Netty、NIO、文件映射用得多,默认上限等于堆大小(-XX:MaxDirectMemorySize 不设的话)。元空间默认无上限,动态生成类多(大量反射、CGLIB 代理、Groovy 脚本)会持续涨。线程栈每个线程默认 1MB,开几千个线程就是几个 G。

第三,RSS 里有大量 page cache。容器里读大文件、写日志,页缓存算进 cgroup 内存。这部分可以被内核回收,但在回收前会顶到 limit 触发 OOM。JVM 还有个坑:它按宿主机内存算默认堆大小,没设 -XX:MaxRAMPercentage 或 -Xmx 时,容器 limit 4G 但 JVM 以为有 64G,堆直接开到 16G。

怎么定位,按这个顺序。

第一步看内核日志。dmesg 或 /var/log/kern.log 里搜 oom-kill,会打印被杀进程的 rss、以及各 cgroup 的用量。这一步能确认是 cgroup OOM 还是系统 OOM。

第二步看容器指标。container_memory_working_set_bytes 是 K8s 判 OOM 用的指标,和 limit 比。再看 container_memory_rss 和 container_memory_cache 的拆分,能看出是堆外还是页缓存。

第三步在 JVM 内部确认。开 Native Memory Tracking:

-XX:NativeMemoryTracking=summary

然后 jcmd <pid> VM.native_memory summary,输出会按 Java Heap、Class、Thread、Code、GC、Internal、Other 分类列出。堆用 60% 而 total 接近 limit,答案就在 Other 或 Thread 那一栏。

第四步,堆 dump 要看,但别只看用量。用 MAT 看 dominator tree 和 leak suspects,同时留意是否存在大量未释放的 DirectByteBuffer(java.nio.DirectByteBuffer 的 cleaner 队列)。

预防措施比排查更值钱。给容器设置 -XX:MaxRAMPercentage=70,留出堆外和页缓存空间。显式设 -XX:MaxDirectMemorySize。用 -XX:+ExitOnOutOfMemoryError 让 JVM 在堆内 OOM 时主动退出,比被内核杀掉更容易留下现场。给容器加 memory 的 request 和 limit,request 等于实际需要量,避免被调度到内存紧张的节点。

最后一条经验:OOMKill 的频率和排查成本成反比。第一次发生时把 NMT、dmesg、容器指标三样都留档,后面再出问题,五分钟就能定位。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。