|导语 内存泄漏是c/c++老生常谈的问题了,也有一些比较成熟的工具卸载我们定位内存泄漏问题典型的就有valgrind。该工具需要定位内存泄漏是需要cover对应的业务逻辑才会把内存泄漏地方暴露出来。 这里介绍一种基于运行时进程内存镜像定位内存泄漏问题。
利用进程运行时的内存镜像分析程序是哪个类的实例没有释放,虽然不能做到一针见血找到引起内存泄漏的代码,但是可以利用该方法,缩小的范围。这种方法,不需要触发特定的业务逻辑,只要你的程序已经存在内存泄漏,且内存镜像还存在就可以进行分析。
1)先来模拟内存泄漏的场景。
测试程序:(发现头条不适合做长篇代码的分享,在移动端开长篇代码体验极差,也很难达到预想的效果)
#include #include class T1 { public: T1(){} virtual ~T1(){} }; int main(int argc,char* argv[]) { for(int i = 0;i < 1000000;i++) { T1 *t = new T1; char *p = (char*)malloc(sizeof(char)); } pause(); //目的是为了能够dump 进程内存镜像 return 0; }
2)编译运行查看内存、内存分布情况:
top -p 5250
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
5250 user_00 25 5 77404 61m 756 S 0.0 0.8 0:00.08 a.out
$pmap 5250
下面是该进程的内存分布情况
0000000001e06000 62568K rw--- [ anon ]00007f7022466000 20K rw--- [ anon ]00007f7022bf6000 84K rw--- [ anon ]00007f7022d0b000 20K rw--- [ anon ]00007f7022e26000 16K rw--- [ anon ]00007f7022e2c000 4K rw--- [ anon ]00007fff0a5a3000 84K rw--- [ stack ]00007fff0a5ff000 4K r-x-- [ anon ]ffffffffff600000 4K r-x-- [ anon ]
通过上图的内存分布可以看到,这块匿名「anon】的内存区域比较大,其他小的[anon]不要考虑(抓住主要矛盾)
0000000001e06000 62568K rw--- [ anon ]
3)分析内存镜像
接下来就是借助万能的gdb把这块内存中存储的信息输出来。通过gdb 生成core:(生成core文件主要是防止进程的内存比较大的时候,后续的分析操作会导致进程长时间没有响应,如果你的进程有单独的父进程管理很有可能会因为超时被kill掉)
$gdb attach 5250
(gdb)gcore
执行以上命令,在当前目录下生成core.5250(具体看系统core文件名配置的规则)。
用gdb 打开core文件,将0000000001e06000 起始的内存内容输出到文件中:
gdb -c core.5250 mem_leak(gdb) set height 0(gdb) set logging onCopying output to gdb.txt.(gdb) x/8008704a 0x0000000001e06000
set logging on 将gdb操作输出到日志文件中,方便后续分析。
x/8008704a
格式说明:
62568=1e06000H
8008704=62568*1024/8 (因为demo是64 bit所以要/8byte)
a为gdb输出其中一种格式
最后就是分析上面生成的gdb.txt(如果这块内存比较大的话,输出需要很长时间),直接看的话有点懵逼:
![9d8f1fdb081a66dd688e85a88ef33271.png](https://i-blog.csdnimg.cn/blog_migrate/aaef9c35c60993a75e7de9e2837a9430.jpeg)
Fig1 转换前
所以需要做一下转换:
cat gdb.txt|c++filt >demo.txt
![6c2919dc7ea56033303dfeac2cd3c809.png](https://i-blog.csdnimg.cn/blog_migrate/36c7ee6794bbe735bdefb0edaa461dc6.jpeg)
Fig 2 转换后
通过统计demo.txt 中各个对象的个数来判断是否是该对象存在内存泄漏,当然也有可能实际上你的程序就是这么多正常的对象,所以需要理智的判断。
cat demo.txt|awk 'BEGIN{stat[""]=0}{stat[$5]++}END{for(i in stat) print i"="stat[i]}'
=2295782
T1+16>=765261
测试程序中是这个类T1的对象存在内存泄漏,剩下的就是体力活了,看这个类哪里new了。。
这种方法的不足之处:
他依赖于类的实例中的虚函数表,如果是原生类型(int、char)这种那么这种方法也就无能为力,毕竟每种方案都不是十全十美。