JVM 直接内存排障:堆没满,进程也可能被杀

JVM 直接内存排障:堆没满,进程也可能被杀
JVM 直接内存排障堆没满进程也可能被杀一、内存问题不只发生在 Java 堆线上 Java 服务被 OOMKill 时很多人第一反应是看堆内存。可是堆使用率不高进程仍然被系统杀掉这种情况并不少见。原因可能是直接内存、线程栈、Metaspace、JIT Code Cache、JNI 或本地库占用了大量进程内存。直接内存尤其容易被忽略。Netty、NIO、压缩库、序列化框架、零拷贝传输都可能使用堆外内存。堆没满不代表进程 RSS 安全。二、JVM 进程内存要按区域看进程内存由多个区域组成。flowchart TD A[JVM 进程 RSS] -- B[Java Heap] A -- C[Direct Memory] A -- D[Metaspace] A -- E[Thread Stack] A -- F[Code Cache] A -- G[Native Library]只看-Xmx和 GC 日志会漏掉大量堆外消耗。容器环境里RSS 超过 cgroup limit 就可能被杀。三、先确认直接内存上限和使用常用排查入口如下。jcmd pid VM.native_memory summary jcmd pid VM.flags jstat -gc pid 1000 cat /sys/fs/cgroup/memory.current如果开启 NMT可以更清楚地看到 native memory 分布。没有开启时也要结合 RSS、堆使用、线程数和容器内存指标推断。四、直接内存泄漏常见于缓冲区生命周期Netty ByteBuf 未释放、缓存池无限增长、文件传输缓冲未回收都可能导致直接内存上涨。堆里只保留少量对象引用真正占用在堆外普通 heap dump 不一定明显。还要检查线程数。每个线程都有栈空间线程池配置过大时线程栈会吃掉不少内存。堆外问题和线程问题经常一起出现。容器限制要和 JVM 参数对齐。MaxRAMPercentage、MaxDirectMemorySize、Xss、Metaspace 上限都要统一规划。只设置 Xmx其他区域放任增长最终仍然可能超过容器限制。最后监控要看 RSS 和堆的差值。差值持续扩大说明堆外或 native 区域在增长。等 OOMKill 后再看日志很多现场已经没了。直接内存还要结合分配速率看。总量稳定但分配释放非常频繁可能带来额外 CPU 开销和延迟抖动总量缓慢上涨则更像泄漏或池化策略失控。只看某一时刻的使用量判断会不完整。在 Netty 场景里要关注 allocator 指标。Arena 数量、chunk 使用率、active direct memory、ByteBuf 泄漏检测日志都能提供线索。开发环境可以提高 leak detector 级别生产环境则要权衡性能开销。最后压测要覆盖大包、慢客户端和连接抖动。直接内存问题经常在网络传输压力下暴露普通小请求压测可能完全看不出来。排障要复现场景不能只在低压环境里看指标。还要关注容器的内存统计口径。应用看到的 JVM 指标可能都正常但 cgroup 里的memory.current已经逼近限制。监控面板要同时展示 JVM 内部指标和容器外部指标否则容易得出“JVM 没问题”的错误结论。直接内存上限也不能盲目调大。调大可以缓解短期 OOM但如果根因是泄漏最终只是延后事故。正确顺序应该是先确认增长曲线和分配来源再决定是修复释放、调整池化还是扩大容量。JVM 直接内存监控还需要关注被误解的指标。很多团队看到free -m显示大量 free 内存就认为系统内存充足但实际上可能已经发生了 swap或者内存碎片导致无法分配连续空间。在容器环境里/proc/meminfo和 cgroup 内存统计的口径也不同需要明确监控工具读取的是哪个数据源。建议同时在三个层面采集内存指标JVM 内部NMT、堆外统计、容器层面cgroup memory.current、memory.high、节点层面node exporter 或类似工具三者对比才能发现JVM 认为没问题但容器已经快超限的情况。另一个容易被忽视的问题是内存回收的延迟性。DirectByteBuffer 的回收依赖 GC 和 Cleaner 机制如果堆 GC 不频繁堆外内存可能长时间无法释放。这种现象在堆很大、对象分配率很低的服务里尤其明显堆还很空但直接内存已经快用完。解决思路包括主动调用System.gc()不推荐但在某些场景下是实用手段、调整直接内存的 Cleaner 触发逻辑、或者在应用层实现显式的缓冲区回收机制。监控时需要同时关注堆使用率、直接内存使用量和 GC 频率才能定位这类问题。五、总结JVM 内存排障不能只盯 Java 堆。直接内存、线程栈、Metaspace 和 native 区域都会影响进程 RSS。容器环境下堆没满也可能被杀。把堆内和堆外指标一起监控才能真正解释内存事故。