堆外内存泄漏需从进程RSS入手,NMT(-XX:NativeMemoryTracking=detail)定位增长区域,结合pmap查匿名映射、JMX监控DirectBuffer、jemalloc排查JNI泄漏。

堆外内存泄漏排查不能只盯着堆,得从进程整体内存使用入手。RSS 持续上涨但堆内回收正常(比如 jmap -histo 显示才几百 MB),基本就能锁定是堆外问题。
看 NMT 报告,快速定位增长区域
启动时必须加参数:-XX:NativeMemoryTracking=detail。运行一段时间后执行:
- jcmd VM.native_memory summary scale=MB —— 看哪类内存(如 Internal、Thread、Direct、Mapped)在涨
- jcmd VM.native_memory detail scale=MB > nmt-$(date +%F-%H).log —— 保存快照,多次对比
如果 Direct 明显增长,重点查 ByteBuffer 或 Netty;如果 Thread 区飙升,检查线程数是否失控;Internal 持续涨,大概率是 JNI 或 C++ 层分配未释放。
用 pmap 对比内存映射变化
NMT 看不到的部分(比如 libc malloc 分配、mmap 区域),pmap 能补上:
- pmap -x
> pmap-$(date +%F-%H-%M).log - 隔几小时再抓一次,用 icdiff 或 diff 对比,找新增的大块匿名内存(anon)或 .so 映射
常见线索:某段地址反复增长、出现大量 64MB/128MB 的 mmap 区域、或某个第三方 native 库的映射持续扩大。
聚焦高频嫌疑点:DirectBuffer 和 JNI
这两类占了生产环境堆外泄漏的八成以上:
-
DirectBuffer:Netty、NIO 通道、文件映射都可能用到。查 JMX:
java.nio:type=BufferPool,name=direct的MemoryUsed和Count;或者用 jstat -gc看 CCSC(Compressed Class Space)和M(Metaspace)是否异常,间接反映类加载压力 -
JNI/C++ 层:Java 代码没动,但 RSS 还在涨,十有八九是 native 侧 malloc 了没 free。可尝试用 heaptrack 或替换 jemalloc(Ubuntu 上 apt install libjemalloc2,启动加
LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2),观察是否止住增长
辅助验证与收口
排除法同样关键:
- 确认线程数:jstack
| grep 'java.lang.Thread.State' | wc -l ,对比系统限制 ulimit -u - 限制 Direct 内存:-XX:MaxDirectMemorySize=512m,看 OOM 是否提前暴露
- 换 GC 器试一试:-XX:+UseG1GC 有时能缓解 allocator 碎片问题
- 若所有 Java 层工具都无异常,且 pmap 显示大量小块 anon 内存,优先怀疑 glibc malloc 碎片,jemalloc 替换往往是见效最快的手段


















