Java服务被OOM Killer杀掉但堆内存正常,大概率是Direct Buffer耗尽导致系统级OOM;关键判断点是dmesg显示“Killed process java”且free -h中available接近0、Swap用满,而Private_Dirty远超JVM堆大小。

Java服务被Linux OOM Killer杀掉,但堆内存正常?大概率是直接内存(Direct Buffer)耗尽导致的系统级OOM,不是JVM堆问题。
怎么看是不是Direct Buffer导致的系统OOM
关键判断点:JVM没报java.lang.OutOfMemoryError: Direct buffer memory,但dmesg里有Killed process java,且free -h显示available接近0、Swap几乎用满——这说明内核层面内存已枯竭,而JVM堆可能只占一小部分。
必须立刻确认两点:
- 查
dmesg -T | grep -i "killed process",看被杀进程是否为java,以及时间点是否匹配服务崩溃时刻 - 查
cat /proc/<pid>/smaps_rollup | grep -E "(Rss|Pss|Private_Dirty)"</pid>,如果Private_Dirty远高于JVM堆大小(比如堆设了512M,但Private_Dirty达3G),基本锁定非堆内存失控 - 检查JVM是否启用了
-XX:NativeMemoryTracking=detail,若启用,可用jcmd <pid> VM.native_memory summary</pid>看直接内存分配量
为什么jmap和MAT对这类OOM完全无效
jmap、heap dump、Eclipse MAT只分析JVM堆内对象,而ByteBuffer.allocateDirect()分配的是操作系统原生内存,绕过GC管理,也不在堆里。你dump出来的文件里根本找不到这些字节缓冲区。
立即学习“Java免费学习笔记(深入)”;
所以:
- 别再跑
jmap -dump,浪费时间 - 别指望
histo输出里看到java.nio.DirectByteBuffer占多大——它只显示对象头,不反映背后几MB甚至几GB的native memory - 真正要盯的是
/proc/<pid>/maps</pid>里那些大块anon_inode:[memfd:java]或无名anon映射段,它们才是直接内存的实体
怎么定位哪段代码在狂刷Direct Buffer
核心思路:用strace捕获mmap系统调用,结合堆栈回溯。
操作步骤:
- 重启Java服务时加上
-XX:NativeMemoryTracking=detail(如已线上运行,可先用jcmd <pid> VM.native_memory baseline</pid>再等几分钟采样) - 复现问题前,执行:
strace -f -e trace=mmap,munmap -p <pid> 2>&1 | grep -E "mmap.*MAP_ANONYMOUS" | head -20</pid>,观察每次mmap的size是否固定且巨大(如10MB+) - 更准的方式是用
perf record -e syscalls:sys_enter_mmap -p <pid></pid>+perf script,能关联到Java线程和方法栈(需开启-XX:+PreserveFramePointer) - 常见高危点:
Netty PooledByteBufAllocator未配置maxOrder、spring-webflux响应体过大未流式处理、自定义NIO客户端反复allocateDirect后不cleaner或free
临时缓解和长期规避的关键配置
系统级OOM不能只靠“加内存”,得从资源边界入手:
- JVM侧必须设死上限:
-XX:MaxDirectMemorySize=512m(不设等于堆大小,极易失控) - Linux侧限制进程总内存:
systemd服务加MemoryLimit=4G,或cgroup v2配memory.max,让OOM Killer在Java进程内部触发,而非干掉整个JVM - 禁用危险的overcommit:
echo 2 > /proc/sys/vm/overcommit_memory,避免内核盲目承诺内存,让mmap失败得更早、更明确 - 监控项必须补上:
cat /proc/<pid>/status | grep VmRSS</pid>(实际物理内存)、jstat -gc <pid></pid>(堆行为对比),二者增速严重不匹配就是信号
最易被忽略的一点:很多团队只监控JVM堆GC,却从不采集/proc/<pid>/smaps_rollup</pid>里的Pss指标。而正是这个值,才真实反映Java进程对物理内存的“净占用”。


















