容器里堆只用 60% 却被 OOMKilled:JVM 少算的内存去哪了
本文摘要堆只用六成就被杀掉缺口来自元数据、线程栈与直接内存这些非堆项。用NMT把内存拆成八个类别逐项归因再按总账给各类显式设上限。该路径限JDK17容器环境NMT常开多占内存内存贴着限额的服务须谨慎。一、问题与结论kubectl describe pod给出OOMKilledjstat -gc里used约 720MB-Xmx1200m只用到六成Pod 重启后两小时又被杀。差别在口径cgroup 的limits.memory统计的是进程 RSS堆只是子项Metaspace、线程栈、Code Cache 与 Direct Buffer 合计约 800MB 就足以越过 2Gi。排查用-XX:NativeMemoryTracking逐类归因治理是给 Non-Heap 显式设上限并留余量。触发者与业务代码无关堆外分配偏多的服务都会复现常见的是 Netty/gRPC 的零拷贝缓冲区与规模偏大的线程池。二、排查与选择依据两个口径要分清容器侧看docker stats的USAGE与/proc/1/status的VmRSSJVM 侧看GC.heap_info的used差值就是待解释的部分。诊断顺序docker stats --no-stream my-oom-app看总占用与 limitdocker exec my-oom-app cat /proc/1/status | grep VmRSS拿到 RSSdocker exec my-oom-app jcmd 1 VM.native_memory summary拆出八类占用jcmd 1 VM.native_memory baseline建基线隔一段时间用VM.native_memory detail.diff看增量是否回落。判断标准Java Heap的 used 稳定而Internal或Class持续增长且不回落属于 Direct Buffer 泄漏或元数据膨胀Netty 异常路径漏 release、CGLIB 热部署旧类未释放都归此类各类都稳定只是总量偏大属预算不合理改启动参数即可不必动业务代码。替代方案与取舍做法选择条件代价不该用的情况NMT summary 常开定期detail.diff需要逐类归因能改启动参数summary 约增加 5%–10% RSS内存贴着 limit没有余量吃开销显式设MaxMetaspaceSize等上限MaxRAMPercentage60留白只求稳定运行堆利用率偏低上限过紧会抛OutOfMemoryError: Metaspace堆峰值波动大无法预估各类上限JFR 采集jdk.NativeMemoryUsage已有 JDK 11 与分析流程采集与事后分析成本高只想判断是否越限的短命任务K8scontainer_memory_working_set_bytes告警拿不到 JVM 内部指标只能预警不能定位已知根因、要量化修复收益上线不到一周就OOMKilled时先调limits.memory与-Xmx的比例通常更快收敛。三、关键原理NMT 把 JVM native memory 划成八类堆只是第一项NMT 类别内容典型量级Java Heap对象存储-Xmx的 60% 左右ClassMetaspace Compressed Class Space数十到数百 MBThread线程栈线程数 ×-XssCodeJIT Code Cache24–240 MBGC标记位图等 GC 数据结构堆的 5%–20%Internal/Symbol内部结构、符号表数十 MBOtherDirect Buffer、JNI、Unsafe不透明最容易失控三个机制决定预算写法。其一cgroup 记的是 RSS-Xmx并未为其余类别预留空间。其二-XX:UseContainerSupport在 JDK 10 默认开启按容器 limit 修正-XX:MaxRAMPercentage与处理器数量但不会自动收紧 Metaspace、Code Cache 与线程栈。其三-XX:MaxDirectMemorySize默认跟随-XmxDirect Buffer 的回收依赖 Cleaner 与 GC 触发释放时机不受调用方控制。总账按“堆 Metaspace Code Cache 线程数 ×-Xss Direct Buffer GC 开销 NMT 自身开销 ≤limits.memory”来写。四、可运行示例环境为 Docker 与eclipse-temurin:17-jdk镜像cgroup v2 环境同样适用容器内存限额 2GB。先构建镜像再带限额运行最后执行诊断。MemoryEater.java模拟堆内 720MB、Direct Buffer 300MB 与 200 个阻塞线程importjava.nio.ByteBuffer;importjava.util.ArrayList;importjava.util.List;importjava.util.concurrent.CountDownLatch;publicclassMemoryEater{publicstaticvoidmain(String[]args)throwsException{Listbyte[]heapnewArrayList();for(inti0;i720;i){heap.add(newbyte[1024*1024]);// 堆内约 720MB}ListByteBufferdirectnewArrayList();for(inti0;i300;i){direct.add(ByteBuffer.allocateDirect(1024*1024));// 堆外 300MB}CountDownLatchlatchnewCountDownLatch(1);for(inti0;i200;i){// 200 个阻塞线程栈默认 512KB–1MBnewThread(()-{try{latch.await();}catch(InterruptedExceptionignored){}}).start();}System.out.println(ready pidProcessHandle.current().pid());Thread.sleep(Long.MAX_VALUE);}}Dockerfile为各类 Non-Heap 显式设上限并开启 NMTFROM eclipse-temurin:17-jdk WORKDIR /app COPY MemoryEater.java . RUN javac MemoryEater.java # MaxDirectMemorySize 留出余量避免恰好贴 300MB 上限抛 OutOfMemoryError: Direct buffer memory ENTRYPOINT [java,-Xmx1200m,-XX:MaxMetaspaceSize128m,-XX:ReservedCodeCacheSize128m,-XX:MaxDirectMemorySize512m,-XX:NativeMemoryTrackingsummary,-XX:UseContainerSupport,MemoryEater]构建、运行与诊断dockerbuild-toom-diagnosis.dockerrun-d--namemy-oom-app--memory2g oom-diagnosissleep5dockerstats --no-stream my-oom-appdockerexecmy-oom-appcat/proc/1/status|grepVmRSSdockerexecmy-oom-app jcmd1VM.native_memory summarydockerexecmy-oom-app jcmd1GC.heap_infodockerexecmy-oom-appcat/sys/fs/cgroup/memory.max2/dev/null\||dockerexecmy-oom-appcat/sys/fs/cgroup/memory/memory.limit_in_bytes预期输出Java Heapused 约 720MBThread约 200MBInternal/Other约 300MBClassCodeGC约 300MBcommitted 合计约 2GB叠加 NMT 开销后VmRSS贴近 2Gi随后出现OOMKilled。上述数值为未验证示意值。实际输出在终端比对VmRSS与GC.heap_info的 used 差值并确认memory.max为2147483648若差值接近 NMT 各类之和即可确认根因是 Non-Heap 越限。失败处理启动参数里漏掉-XX:NativeMemoryTrackingsummary时jcmd 1 VM.native_memory summary会返回未开启追踪的提示。NMT 只能在 JVM 启动时开启补进ENTRYPOINT后重启 Pod或改用 JFR 事后采样。五、验证结果与边界验证分两步先用示例把容器打到 limit 并拿到OOMKilled事件再逐项设上限重跑观察同一时段detail.diff是否收敛。文中内存分布数字均为未验证示意值。边界四条。NMT 有开销summary 约 5%–10%、detail 约 10%–20% 额外 RSS贴限服务不要长期开 detail。Other里的 Direct Buffer 释放时机不可控-XX:MaxDirectMemorySize只挡越界不保证回收。cgroup v2 需 JDK 15 完整支持JDK 8/11 的 update 版本需单独确认。虚拟线程JDK 21不占平台线程栈Thread一类会明显变小仍要为 pinning 场景预留平台线程预算。参考资料java 命令手册JDK 17jcmd 命令手册JDK 17

相关新闻

RCP 4.5 和 8.5 情景下 1901-2299 年永久冻土融化和碳释放的预测

RCP 4.5 和 8.5 情景下 1901-2299 年永久冻土融化和碳释放的预测

Projections of Permafrost Thaw and Carbon Release for RCP 4.5 and 8.5, 1901-2299简介该数据集包含 1901 年至 2299 年间北半球多年冻土区域的一系列模型预测结果。这些模型预测结果包括一组通用诊断输出的月平均值,空间分辨率为 0.50.5经纬度。这些模型模拟结果…

2026/11/14 9:43:06 阅读更多 →
课程论文堆成山?大学生必备的AI写作效率神器

课程论文堆成山?大学生必备的AI写作效率神器

如果你问大学生最头疼的事情是什么?除了期末周考试,估计就是各种课程论文了。一个学期下来,少则五六篇,多则十几篇——马克思主义基本原理要写论文,大学语文要写论文,专业选修课每门都要交课程论文&#xf…

2026/11/16 0:50:09 阅读更多 →
【大数据毕设项目】

【大数据毕设项目】

文章目录 一、项目开发背景意义 二、项目开发技术 三、项目开发内容 四、项目展示 五、项目相关代码 六、最后 一、项目开发背景意义 生成式AI软件服务快速迭代,用户规模与交互频次激增,算力消耗成为运营成本的核心组成部分。为精准掌握用户行为特…

2026/11/15 9:39:01 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/11/15 5:13:35 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/11/15 5:13:35 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/11/15 5:13:35 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/11/15 3:37:41 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/11/15 4:18:38 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/11/15 5:13:35 阅读更多 →

日新闻

周新闻

月新闻