服务通过 - Xmx=6G 指定最大堆分配为 6G,但实际 RSS 已达到 11G,开始怀疑堆外内存是否有内存泄露。
RSS从启动之后,就是一路增长,由于是测试机,业务量小,以53M/小时的增量增长。

JVM 的进程总内存 ≈
Java Heap + Metaspace + CodeCache + Thread Stack + JNI/Direct Buffer + GC 结构 + 内部 allocator fragment + C 库分配 + mmap 区域 + …
由于现象是RES比较高,先看一下java堆是否有异常。把java堆dump下来仔细排查一下,jmap -histo:live pid,发现整个堆回收完也才几百兆,远不到8G的Xmx的上限值,GC日志看着也没啥异常。基本排查java堆内存泄露的可能性。
1 | jcmd 1 VM.native_memory detail scale=MB > nmt-`date +%F-%H-%M-%S`.log & pmap -x 1 > pmap-`date +%F-%H-%M-%S`.log |
整体思路是:通过多次收集,进行比较,查看明显的差异
1 | icdiff pmap-2023-07-27-09-46-36.log pmap-2023-07-28-09-29-55.log | less -SR |


排查顺序

1️⃣ 先开 -XX:NativeMemoryTracking=detail
2️⃣ jcmd VM.native_memory summary 定位哪一类区域增长
3️⃣ 如果是 Internal / Arena Chunk → JNI / DirectBuffer 泄漏
4️⃣ 用 heaptrack / tcmalloc 进一步追踪分配源
5️⃣ 如果是 Thread 区域 → 检查线程数
6️⃣ 若都是正常,考虑 libc allocator fragment,可尝试使用 -XX:+UseG1GC、-XX:MaxDirectMemorySize 或更换 allocator(jemalloc)
这一次排查,先通过NMT,发现Java能追踪到的内存区域一切正常
再通过pmap工具,发现了JNI调用C++有内存泄漏
C++修复后,内存增长大幅降低,但RSS依然会增长
最后,只能使用jemalloc替换原生的glibc来排查一下
替换后,内存还真的不再增长。
cat /etc/os-release
1 | PRETTY_NAME="Ubuntu 22.04.4 LTS" |
ldd –version
1 | ldd (Ubuntu GLIBC 2.35-0ubuntu3.6) 2.35 |
RSS 持续增长的几种可能情况:
- 正常情况(常见):你的程序频繁地分配和释放大小不一的内存。这会导致堆内存出现“空洞”(外部碎片),即使有大量空闲内存,也因为不连续而无法从堆顶归还给 OS。RSS 会稳定在一个较高的水平,这是 ptmalloc2 的设计特点,目的是提高分配效率。
- 内存泄漏(需要警惕):你的程序持续分配内存,但忘记释放。这会导致堆不断增长,即使通过
brk或mmap向 OS 索要更多内存,RSS 会无限制地增长,直到被系统 OOM Killer 杀死。 - 峰值使用后的稳定期:程序在某个阶段需要大量内存,之后虽然释放了,但 glibc 没有立即归还给 OS(堆未收缩)。这部分内存成为了进程的“缓存”,如果程序后续再次需要分配内存,就可以快速重用,避免了系统调用的开销。
- RSS 增长不一定是内存泄漏,很可能是 glibc 的内存池策略。
- glibc 不会因系统内存紧张而主动释放内存,它的行为是预定义的。
- 真正的回收保障来自 Linux 内核,当系统内存不足时,它会强制回收,可能包括将你的进程内存换出(Swap)。
- 如果 RSS 增长是无界的、持续的,并且与你的业务逻辑预期不符,那么首先应该怀疑和排查内存泄漏。