已采纳
回答正文
回复
讨论正文
该问题的正文与回答。
背景
我在一个 12 节点(16C64G,Kubernetes 1.28.4,containerd 1.7.13,cgroup v2,内核 5.15)的生产集群上跑一套 Java 17(OpenJDK 17.0.9)的无状态订单服务。每个 Pod 规格是 8C32G(request=8C/32G,limit=8C/32G),容器内没有 sidecar,单容器。最近两周频繁出现 Pod 被 OOMKilled(kubectl describe pod 显示 Reason: OOMKilled, Exit Code: 137),但诡异的是:JVM 自己的 GC 日志里 heap 使用率最高只到 78%,-Xmx 设的是 18G,jmap -heap 在 OOM 前一刻抓的 dump 也显示 heap 还有 4G+ 余量。
现象
/sys/fs/cgroup/memory.current 在崩溃前几分钟能爬到 31.5G+,逼近 limit;-Xmx 18G)只用了 ~14G,堆内还有 4G 没被 GC 回收(因为还没到 GC 触发点);已尝试的排查与结果
-Xmx 从 22G 降到 18G,-XX:MaxMetaspaceSize=1G、-XX:MaxDirectMemorySize=2G、-XX:ReservedCodeCacheSize=512m 都显式设了,跑了三天还是 OOMKill,频率从每天 2 次降到每天 1 次,没根治;-XX:NativeMemoryTracking=detail,OOM 前一刻 jcmd <pid> VM.native_memory summary 抓到的输出:Java Heap 13.8G、Metaspace 0.9G、Thread 1.2G(~600 个线程 × 1M 栈)、Code 0.4G、GC 0.8G、Internal 0.3G、Symbol 0.1G、Other 0.2G,合计 ~17.7G——和 cgroup 看到的 31.5G 还差 ~13G,NMT 没把这部分算进去;pmap -x <pid> | awk '{s+=$2} END {print s}' 看 RSS,确实比 NMT 合计大不少,怀疑 glibc malloc arena 碎片 + DirectByteBuffer 的 off-heap 部分没被 NMT 覆盖;-XX:+UseContainerSupport(Java 17 默认开启),jcmd VM.info 里 MaxHeapSize 识别正确(18G),所以不是容器感知失效的问题;dmesg,OOMKill 的调用栈是 memory.oom_group 触发,不是内核 OOM killer 单进程 kill,说明是 cgroup 级别整体超限,不是某个进程单独爆的。期望得到的答案形态
memory.stat 的 file / anon / kernel 字段拆分)-XX:MaxDirectMemorySize 在 Java 17 下到底约束的是 sun.nio.ch.DirectByteBuffer 的 allocated 还是 reserved?如果业务用了 Netty 的 PlatformDependent 绕过了 DirectByteBuffer,这个参数还生效吗?memory.high(soft limit)和 memory.max(hard limit)对 JVM 的行为差异?有没有人试过把 memory.high 设成 limit 的 90%,让内核在 OOM 前就把 page cache 压回去,避免直接 OOMKill?