先把当时的情况摆出来。K8s 1.26 集群里一个 Java Pod 的 limit 写着 4Gi,容器里跑着跑着就被 OOMKilled,docker inspect 显示 Memory 是 4294967296,但容器内 free -m 看到的是宿主机的 64G。
真正麻烦的地方在于,几个原因的现象几乎一样。limit 只限制了 cgroup,容器里的 JVM 仍按宿主机内存算堆。也可能是cgroup v1 和 v2 混用,memory.max 写到了不生效的那一层。
还有一种情况,子 cgroup 的限额比父级大,实际生效的是更小的那个。另外,内存统计把 page cache 也算进去,回收不及时就触顶。
上来先确认容器实际生效的限额
cat /sys/fs/cgroup/memory.max 2>/dev/null || cat /sys/fs/cgroup/memory/memory.limit_in_bytes
再往下对比 JVM 看到的可用内存
java -XX:+PrintFlagsFinal -version | grep -i maxheap
显式设置堆上限并留出堆外空间
kubectl set env deploy/app JAVA_OPTS='-Xmx3g -XX:MaxDirectMemorySize=256m'
剩下的就是观察容器的内存使用曲线
kubectl top pod -l app=order --containers
恢复的判定其实很直接。Pod 连续运行 24 小时没有重启,kubectl describe 里 Last State 不再是 OOMKilled,堆内存使用稳定在 3G 以内。
这里还有个坑。JVM 的堆外内存、线程栈和元空间都不在 -Xmx 里,limit 要留出至少 25% 的余量;用容器感知参数让 JVM 自动读取 cgroup 更省心。同一份配置在测试环境跑通,不代表线上没事,两边的默认值经常不一样。
