导读:本期聚焦于天马创作的《如何用pmap命令分析和定位Linux进程内存泄漏问题?》,敬请观看详情。进程运行一段时间后内存持续上涨却迟迟不下降,这是典型的内存泄漏症状,排查起来往往让人头疼。本文围绕pmap这一Linux自带工具展开,讲解如何通过pmap查看进程的内存映射布局,读懂每一段地址区间的含义,区分匿名内存、堆区、共享库和文件映射,并结合重复采样的方式判断到底是哪块区域在持续增长。文中还给出常见泄漏原因的分析思路,配合valgrind、gdb等工具的联动验证方法,帮助你把泄漏范围缩小到具体的模块或代码位置,适合需要在生产环境快速定位内存问题的开发者和运维人员参考。

内存泄漏是最让服务端开发者头疼的问题之一。进程启动时只占几十MB内存,跑了一周之后RSS涨到好几个GB,重启后一切正常,然后又周而复始。这种情况基本可以断定存在内存泄漏,但难点在于:泄漏发生在哪里?是堆上的对象没释放,还是某个全局容器无限增长,或者是线程栈没有回收?本文介绍如何利用Linux自带的pmap工具逐步缩小泄漏范围,并结合其他工具最终定位到代码位置。

如何用pmap命令分析和定位Linux进程内存泄漏问题?

先搞清楚进程的内存布局

在用pmap分析之前,需要先了解一个进程的虚拟地址空间大概由哪些部分组成。Linux下用pmap -x pid可以看到进程的所有内存映射区间,每一行代表一段连续的虚拟地址区域,典型的输出类似下面这样:

pmap -x 12345
12345:   ./my_server
Address           Kbytes     RSS   Dirty Mode  Mapping
0000000000400000     916     916       0 r-x--  my_server
00000000012be000      16      16       8 rw---  my_server
00007f3a9c000000    1320     820     820 rw---    [ anon ]
00007f3aa8000000     132         4       0 r----  libc-2.31.so
00007f3aa821d000    1444     956       0 r-x--  libc-2.31.so
00007f3aac000000     132      16      16 rw---    [ anon ]
00007ffd1a9e1000     132     132     132 rw---    [ stack ]
0000000000000000       4       4       0 rw---    [ anon ]
total kB            18968    7548    1120</p>

输出中的Mapping列非常关键。显示为具体文件名的行是文件映射,比如可执行文件本身和各种共享库;显示[ anon ]的是匿名映射,不与任何文件关联,通常是malloc从操作系统申请的大块内存、mmap直接分配的区域或者线程栈;显示[ heap ]的是进程堆区,小块内存分配默认走这里。RSS列表示实际驻留物理内存的大小,Dirty列表示脏页大小,分析泄漏时主要盯住RSS持续增长的匿名区间。

还有一个常见误区要提前说明:VIRT很高并不代表泄漏。虚拟地址空间占用几十GB可能是正常的,比如glibc的malloc会预保留大片地址空间。真正需要关注的是RSS,也就是实际占用的物理内存。判断泄漏的标准是:在负载平稳的前提下,RSS随时间单调增长且从不回落。

用重复采样定位泄漏区域

pmap的一次快照说明不了问题,泄漏分析的核心手法是间隔采样对比。写一个简单的脚本,每隔一段时间记录一次pmap输出,观察哪些区间在增长:

#!/bin/bash
pid=$1
while [ $# -gt 0 ] && kill -0 $pid 2>/dev/null; do
    echo "===== $(date +%T) ====="
    pmap -x $pid | grep -v "r-x--\|r----\|-----" | tail -n +3
    sleep 60
done >> /tmp/pmap_log.txt

跑上一两个小时后,对比日志中相同Mapping的Kbytes变化。不同区域增长对应不同的泄漏类型,判断逻辑如下表:

增长区域典型原因
[ heap ]持续增长小对象泄漏,malloc分配后未free,如忘释放的字符串、链表节点
匿名映射数量增加大块内存泄漏(超过128KB走mmap),或频繁mmap未munmap
132KB左右的rw匿名块增多线程栈泄漏,线程退出后未join导致栈未回收
某个so的rw段增长该库的全局数据或静态容器在增长

其中线程栈泄漏非常常见又容易被忽视。每创建一个线程,pmap里就会多出一个132KB左右的可读写匿名块,如果线程退出后这块内存没有消失,多半是代码里没有调用pthread_join,或者没有设置线程分离属性PTHREAD_CREATE_DETACHED,导致线程资源滞留。如果看到匿名块以每次132KB的节奏稳定增长,基本可以按这个方向排查。

堆区增长则需要进一步区分两种情况:一种是真正的泄漏(分配后指针丢失,永远无法释放),另一种是内存池碎片(对象已释放但内存没有归还操作系统)。glibc的malloc对小内存做了缓存,free后的内存往往留在堆里不还给内核,表现为heap缓慢增长但增速逐渐放缓趋于平稳。而真泄漏的增速通常和请求量成正比,不会收敛。观察增长曲线的形态,可以先把这两种情况区分开。

结合其他工具精确到代码位置

pmap只能告诉泄漏发生在哪类内存,要定位到具体代码行,还需要动态分析工具配合。如果环境允许重现问题,valgrind是首选:

valgrind --leak-check=full --show-leak-kinds=all ./my_server

valgrind会拦截每一次malloc和free,退出时报告所有没有释放且不可达的内存块,包括分配时的调用栈。需要注意valgrind会让程序慢10到30倍,不适合直接跑生产流量,通常在测试环境用压力工具模拟请求来触发。

生产环境上不了valgrind时,可以用glibc自带的mtrace,或者更轻量的tcmalloc、jemalloc。以jemalloc为例,编译时链接-ljemalloc,运行时通过MALLOC_CONF=prof:true开启采样,之后用jeprof导出堆剖面,直接看到哪条调用路径分配的内存最多且没释放,开销只有百分之几,完全可以在线上使用。

如果怀疑是某个全局容器无限增长,比如缓存没有淘汰策略、map里的key越积越多,还可以用gdb attach到进程直接检查:gdb -p pid之后打印可疑全局变量的size,多次采样对比。再配合pmap确认增长的就是该模块对应的内存,证据链就完整了。实际排查中,建议先用pmap把泄漏归类到堆、线程栈或某个库,再选择合适的工具深入,这样比上来就盲目跑valgrind效率高得多。

最后提醒一点:内存问题修复后,要建立长期观测手段,比如通过/proc/pid/status里的VmRSS做定时采集并绘制曲线,确保泄漏真正修复,而不是因为重启周期变短而掩盖了问题。

内存泄漏pmap内存分析修改时间:2026-09-11 03:14:33

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/0911/54422.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。