1.什么是内存泄漏
1.1内存泄漏
malloc等类似函数,申请内存后, 使用完毕,没有用free释放; 造成内存持续被占用着,不能被后续流程
重复使用; 大量内存泄漏后,造成系统内存紧张, 直直程序或系统崩溃.
1.2大概分类
1.单次泄漏
a. 函数有泄漏,但程序只会运行它一次, 如init函数
b.申请的全局内存,程序整个过程确实需要使用它,但是程序退出时没有主动释放(free)
单次泄漏问题不大,程序退出时,系统会全部释放,但还是写规范为秒.
2.持续泄漏
a.如while循环内持续申请内存,没有释放;这种比较容易发现;
b.不易发现的是复杂流程中,某个函数有内存泄漏,且这个函数频繁调用到
c.c语言中通过传递内存指针,传递内存块到程序流程很远很间接的函数中,
且有失败流程, 这个是最复杂且难以发现.
3.隐性泄漏
例如有一个指针链表, 程序申请了1M内存10000个,放到链表中,但是程序只需要最近的10个, 其他老内存都是一定不会再使用的.
但是程序退出时,根据链表释放了所有内存. 此时无法直接检测到内存泄漏,但是却有极多本应该释放的内存没有计时释放,侵占大量系统内存.
valgrind安装
yum install valgrind
程序正常退出的泄漏检测
运行
nohup valgrind --trace-children=yes --time-stamp=yes --leak-check=full --vgdb=full --num-callers=25 --log-file=memory03.log /opt/program aaa &
/opt/program 待检测程序,程序前面的参数是valgrind 的
aaa 程序的参数
num-callers=25 这个参数很有用, 是打印出内存泄漏位置的堆栈25层,默认15层,可能不够
让程序正常退出
kill -15 $(pidof valgrind)
注意这里信号15会通过valgrind转发给我们测试的程序,程序需要注册响应信号,让程序正常退出;
也可以注册其他信号如信号2.
不能发送信号9, 信号9会直接杀死valgrind,造成无法检测.
还有测试的程序不能强制退出,如果强制退出无法检测到正常的流程.
报告示例
==00:01:31:00.465 37477==
==00:01:31:00.465 37477== 389,790 (9,360 direct, 380,430 indirect) bytes in 90 blocks are definitely lost in loss record 16 of 16
==00:01:31:00.469 37477== at 0x4C29F73: malloc (xxx.c:309)
==00:01:31:00.469 37477== by 0x667AECD: xxx(unsigned long) (in xxx.so.2)
==00:01:31:00.469 37477== by 0x6681C37: xxx::operator new(unsigned long) (in xxx.so.2)
==00:01:31:00.469 37477== by 0x672E383: xxx (in xxx.so.2)
==00:01:31:00.471 37477== by 0x410FD8: xxx(xxx.c:119)
==00:01:31:00.471 37477== by 0x411AB6: xxx(xxx.c:375)
==00:01:31:00.471 37477== by 0x5042DD4: start_thread (in /usr/lib64/libpthread-2.17.so)
==00:01:31:00.471 37477== by 0x7FE1EAC: clone (in /usr/lib64/libc-2.17.so)
==00:01:31:00.471 37477==
==00:01:31:00.471 37477== LEAK SUMMARY:
==00:01:31:00.471 37477== definitely lost: 9,360 bytes in 90 blocks
==00:01:31:00.471 37477== indirectly lost: 380,430 bytes in 990 blocks
==00:01:31:00.471 37477== possibly lost: 4,480 bytes in 7 blocks
==00:01:31:00.471 37477== still reachable: 0 bytes in 0 blocks
==00:01:31:00.471 37477== suppressed: 0 bytes in 0 blocks
==00:01:31:00.471 37477==
==00:01:31:00.471 37477== Use --track-origins=yes to see where uninitialised values come from
==00:01:31:00.471 37477== For lists of detected and suppressed errors, rerun with: -s
==00:01:31:00.471 37477== ERROR SUMMARY: 4618 errors from 39 contexts (suppressed: 0 from 0)
重点查看definitely(检测到一定发生泄漏的位置) 泄漏的位置,然后查看stack,看是哪里申请的内存,去修改代码.
运行过程中检测
上面介绍了,程序退出后,检测没有释放的内存.
这里介绍不退出程序,在程序运行中检测内存;但是在程序运行中检测内存的缺点是,确实会有很多内存没有释放,
但是这些内存有是程序运行中需要用到的,这个有点难以区分,需要自己把握.
例如,检测内存时,运行到"do something with a ",此时a内存没有释放, 会被认为内存泄漏.
void add(){
char* a = malloc(100);
//do something with a
free(a);
}
操作
首先已经在运行valgrind ..... + 程序
然后在另一个shell窗口执行命令,vgdb会向valgrind 通信,发送操作请求
增量变化
vgdb --pid=$(pidof valgrind) leak_check full reachable increased
所有变化量,包括增加和减少
vgdb --pid=$(pidof valgrind) leak_check full reachable changed
运行这个命令后会重置对比基点,再次运行后会对比变化量,以上次运行此命令时的内存为基点,计算变化量.
这个命令会把上面举例的报告直接打印到终端.
最后还是可以正常退出valgrind检测最终泄漏结果.
隐性泄漏
需要运行中检测,并且分析.