最实用方法是cat /proc/[pid]/maps | grep "[heap]"定位堆段起止地址,再用printf计算字节数;VmData字段粗略反映数据段(含堆),但JVM等场景不适用;pmap -x [pid]的ANON列可精准识别堆外匿名内存泄漏。
Linux 进程的“堆内存”不是单一数值,/proc/[pid]/statm 和 /proc/[pid]/status 里没有直接叫“heap”的字段。真正反映堆使用情况的,是匿名映射中由 sbrk 或 brk 扩展的那一块——也就是 Heap 段,它在 /proc/[pid]/maps 中表现为起始地址接近 [heap] 标记、权限含 rw、且不关联任何文件的一段。
最实用的办法是:
cat /proc/[pid]/maps | grep "[heap]" 定位堆段起止地址(如 01234000-01256000 rw-p ... [heap]),再用 printf "%dn" $((0x01256000 - 0x01234000)) 算出字节数(约 139264 字节)cat /proc/[pid]/status | grep -E "VmData|VmStk|VmExe",其中 VmData 是数据段(含堆),对纯 C 程序较准;JVM 进程则意义不大,因为堆外内存走的是 mmap 匿名映射VmRSS 是全部驻留内存(含堆、栈、共享库、DirectByteBuffer 等),不能等同于堆大小可以,但必须满足两个前提:程序用 -g 编译、且没开 -O2 以上激进优化(否则变量被内联或寄存器化,堆栈信息失真)。
常用操作链:
gdb -p [pid] 或 gdb ./a.out core
(gdb) catch syscall brk 或 (gdb) catch syscall mmap,触发后用 info registers 看 rdi/rsi(size 参数)(gdb) bt 查当前堆栈;若想捕获每次 malloc,需设断点:break __libc_malloc(glibc 实现),再用 commands; bt; continue; end
malloc_stats():它只打印 arena 汇总,不带调用上下文;且多线程下输出可能错乱因为 top 的 RES 是进程所有物理内存之和,而 pmap -x [pid] 能按页、按映射类型拆解——你一眼就能看出哪类匿名映射在疯长。
重点关注这几列:
ANON 列非零且持续增大 → 典型堆外泄漏(如 DirectByteBuffer、mmap(MAP_ANONYMOUS)、C++ new)mapped 列对应文件映射,通常稳定;若它涨了,检查是否反复 mmap 同一文件却没 munmap
total 行末尾的 ANON 总和,就是当前进程所有匿名内存占用,和 RES 差值大,说明大量内存被缓存或交换了执行 pmap -x [pid] | tail -n 1 可快速抓总览;配合 watch -n 5 'pmap -x [pid] | tail -n 1' 动态观察变化。
真实泄漏往往藏在“合法但失控”的行为里,比如日志缓冲区不断 realloc、连接池未 close 导致 mmap 积累、甚至 pthread_create 后线程栈未回收。
务必确认以下三点:
LD_PRELOAD 替换 malloc(如 tcmalloc、jemalloc)?此时 __libc_malloc 断点无效,得查对应库的符号,例如 break tc_malloc
pmap 输出中的地址范围是否与 gdb 里 info proc mappings 一致?不一致说明进程内存已重映射,需重新 attachgdb 查到可疑指针后,别只看 print *ptr;要结合 info proc mappings 确认该地址落在哪个映射段,再反查 /proc/[pid]/maps 判断它是堆、栈还是 mmap 区堆外泄漏没有 GC 帮你兜底,每一块漏掉的 mmap 都会实实在在吃掉物理内存。工具只是眼睛,关键还是理解进程地址空间里谁有权申请、谁负责释放。