核心原因在于JVM OOM只关心Java堆而系统OOM关心的是进程占用的所有物理内存。一句话回答系统OOM发生时Java堆可能远没满但进程的总物理内存堆堆外线程栈元空间JNI等超过了系统可用内存内核直接杀进程JVM根本没机会抛出OOM。详细原因分析JVM的内存构成远不止堆┌─────────────────────────────────────────────┐ │ Java进程总物理内存(RSS) │ ├─────────────────────────────────────────────┤ │ ┌─────────────────────────────────────┐ │ │ │ Java堆-Xms/-Xmx控制 │ │ ← JVM OOM只监控这块 │ │ 这部分满了才会抛OOM │ │ │ └─────────────────────────────────────┘ │ │ ┌─────────────────────────────────────┐ │ │ │ 堆外内存不受-Xmx控制 │ │ │ │ - DirectByteBuffer │ │ │ │ - NIO buffer │ │ │ │ - Unsafe.allocateMemory() │ │ │ └─────────────────────────────────────┘ │ │ ┌─────────────────────────────────────┐ │ │ │ 线程栈-Xss控制 │ │ │ │ 1000线程 × 1MB 1GB │ │ │ └─────────────────────────────────────┘ │ │ ┌─────────────────────────────────────┐ │ │ │ 元空间-XX:MaxMetaspaceSize│ │ │ └─────────────────────────────────────┘ │ │ ┌─────────────────────────────────────┐ │ │ │ JNI代码中的malloc内存 │ │ │ └─────────────────────────────────────┘ │ │ ┌─────────────────────────────────────┐ │ │ │ CodeCache、GC开销等 │ │ │ └─────────────────────────────────────┘ │ └─────────────────────────────────────────────┘典型场景堆没满但系统OOM了场景Java堆使用进程总RSS结果堆外内存泄漏2GB/4GB (50%)7.5GB/8GB系统OOM Kill无JVM OOM线程爆炸3GB/4GB (75%)7.8GB/8GB系统OOM Kill正常情况3GB/4GB (75%)4.5GB/8GB正常具体案例案例1堆外内存泄漏// 问题代码不断分配堆外内存不释放while(true){ByteBufferbufferByteBuffer.allocateDirect(1024*1024);// 1MB堆外// 没有调用DirectBuffer.cleaner().clean()}过程Java堆一直正常只是分配了一个ByteBuffer对象很小堆外内存每分钟泄漏1GB2小时后系统物理内存耗尽 → 内核杀Java进程JVM根本没发现堆有问题不会抛OOM案例2线程爆炸// 问题代码使用无界线程池ExecutorServicepoolExecutors.newCachedThreadPool();while(true){pool.submit(()-{Thread.sleep(3600000);// 线程不退出});}过程每个线程占用栈内存默认1MB创建5000个线程 5GB堆外内存线程栈Java堆可能只有几百MB系统内存耗尽 → 杀进程JVM堆正常不抛OOM案例3JVM参数设置过大# 物理内存只有8GB的机器-Xmx7g# 堆占7GB过程JVM启动时堆分配7GB虚拟内存实际物理内存堆用5GB 线程栈 元空间 OS 7.5GB其他进程监控、Agent等需要1GB总需求超过8GB → 系统OOM Kill图解为什么JVM不抛OOMJVM判断是否抛OOM的逻辑 if (堆剩余空间 需要分配的对象大小) { // 尝试Full GC if (GC后仍不够) { 抛出 OutOfMemoryError: Java heap space } } 问题堆外内存、线程栈等根本不在这个判断逻辑里快速自查命令# 1. 查看Java进程的内存构成pmap-xPID|tail-1# 输出total xxx K# 2. 查看各部分的详细分布需要NMTjcmdPIDVM.native_memory summary# 3. 查看系统内存free-h# 如果available充足但进程被杀了说明是单个进程内存超限# 如果available很少说明整体内存不足一句话总结JVM OOM监控的是堆这个小水池系统OOM监控的是整个Java进程这个大水池。堆没满但线程栈、堆外内存、元空间等其他部分加起来把系统内存耗尽了内核就会直接杀进程JVM根本来不及反应。预防方案# 1. 堆外内存限制-XX:MaxDirectMemorySize512m# 2. 线程栈限制-Xss256k# 减小单线程栈大小# 3. 元空间限制-XX:MaxMetaspaceSize256m# 4. 堆内存不要超过物理内存的70%-Xmx: 物理内存 *0.7# 5. 监控进程RSS而非只监控堆ps-pPID-orss,vsz,comm