
服务通过 - 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堆内存泄露的可能性。
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整体思路是:通过多次收集,进行比较,查看明显的差异
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
PRETTY_NAME="Ubuntu 22.04.4 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.4 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=jammyldd --version
ldd (Ubuntu GLIBC 2.35-0ubuntu3.6) 2.35
Copyright (C) 2022 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Written by Roland McGrath and Ulrich Drepper.RSS 持续增长的几种可能情况:
1.正常情况(常见):你的程序频繁地分配和释放大小不一的内存。这会导致堆内存出现“空洞”(外部碎片),即使有大量空闲内存,也因为不连续而无法从堆顶归还给 OS。RSS 会稳定在一个较高的水平,这是 ptmalloc2 的设计特点,目的是提高分配效率。2.内存泄漏(需要警惕):你的程序持续分配内存,但忘记释放。这会导致堆不断增长,即使通过 brk 或 mmap 向 OS 索要更多内存,RSS 会无限制地增长,直到被系统 OOM Killer 杀死。3.峰值使用后的稳定期:程序在某个阶段需要大量内存,之后虽然释放了,但 glibc 没有立即归还给 OS(堆未收缩)。这部分内存成为了进程的“缓存”,如果程序后续再次需要分配内存,就可以快速重用,避免了系统调用的开销。
•RSS 增长不一定是内存泄漏,很可能是 glibc 的内存池策略。•glibc 不会因系统内存紧张而主动释放内存,它的行为是预定义的。•真正的回收保障来自 Linux 内核,当系统内存不足时,它会强制回收,可能包括将你的进程内存换出(Swap)。•如果 RSS 增长是无界的、持续的,并且与你的业务逻辑预期不符,那么首先应该怀疑和排查内存泄漏。
Jemalloc简介及使用方法[1]
使用jemalloc解决JVM内存泄露问题[2]
Java 进程内存占用及可观测性调研&内存异常排查最佳实践[3]
[1] Jemalloc简介及使用方法: https://www.cnblogs.com/yubo-guan/p/19150502
[2] 使用jemalloc解决JVM内存泄露问题: https://bbs.huaweicloud.com/forum/thread-169523-1-1.html
[3] Java 进程内存占用及可观测性调研&内存异常排查最佳实践: https://www.pengzna.top/article/Java-Memory/