容器限了四 G,Java 服务堆只给了两 G,跑一段时间就被 OOMKilled,容器日志里没有任何 OutOfMemoryError,jmap 看堆只用了六成。
一开始怀疑是「容器的 memory.max 只统计整体 RSS,堆外也计入其中」,查了一圈,方向不对。
排查方向其实有限,先把可能性排一排。Netty 和 NIO 用的直接内存不受堆限制,默认上限接近物理内存。也可能是MaxDirectMemorySize 没显式设置,堆外可以一路涨到容器配额。
另外,堆外内存释放依赖 GC,堆没压力时 DirectByteBuffer 不会被回收。
先在容器里看整体内存构成
cat /sys/fs/cgroup/memory.current && jcmd 1 VM.native_memory summary 2>/dev/null | head
接着确认直接内存的用量
jcmd 1 VM.info | grep -i direct || jstat -gc 1 | tail -1
显式限制堆外并留出余量
java -Xmx2g -XX:MaxDirectMemorySize=512m -jar app.jar
剩下的就是把堆外上限写进启动参数并重启验证
grep -n 'MaxDirectMemorySize' /opt/app/bin/start.sh
别只看报错消失,验证要多做一步。容器运行一天后 memory.current 稳定在三 G 出头,不再触发 OOMKilled,jcmd 里直接内存的统计停在设定值以内。
容器里的内存要按堆、堆外、元空间、线程栈加总来算,只配 -Xmx 一定会超;堆外上限设小之后要观察 Netty 的分配失败日志。拖得越久,排查成本越高,因为现场状态会被后续操作覆盖。
