提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
做C、C++开发都知道,内存泄漏问题的确是让人最头疼的,因为一个疏忽就会造成new与delete不成对等,都会造成内存泄漏的问题,而且很难分析到具体泄漏点在哪里,通过查询各种资料,通常我们可以使用 代码review、tcmalloc、assan 等工具进行分析。发现tcmalloc定位项目上一些很棘手的内存泄漏问题,通过本文的例子,让你能够快速定位内存泄漏点。希望能分享给大家。
一、tcmalloc、pprof是什么?
1、准备号tcmalloc与pprof
编译tcmalloc.so与pprof后,分别放到/usr/lib/、/usr/bin/下(编译tcmalloc与pprof过程如下)
首先在环境上安装gperftools,gperftools中包含了tcmalloc和pprof,这两个都是内存泄露检测和查看所需要的。请下载最新版本的gperftools。
wget https://gperftools.googlecode.com/files/gperftools-2.1.tar.gz
tar -zxvf google-perftools-2.1.tar.gz
cd google-perftools-2.1
./configure -h
./configure
make install
2、代码中使用tcmalloc替换malloc
我们如何使用tcmalloc来替换glibc的malloc呢?在链接tcmalloc的时候我们可以使用以下任意一种方式:
a)启动程序之前,预先加载tcmalloc动态库的环境变量设置: export LD_PRELOAD=“/usr/local/lib/libtcmalloc.so”
b)在你的动态库链接的地方加入:-ltcmalloc
3、编译程序
a)g++ -o tt test_asan.cpp -g -O0
b)g++ -o tt test_asan.cpp -ltcmalloc -g -O0
4、运行程序与pprof定位问题
a)env PPROF_PATH=/usr/bin/pprof LD_PRELOAD=“/usr/lib/libtcmalloc.so” HEAPCHECK=normal ./tt
b)env PPROF_PATH=/usr/bin/pprof HEAPCHECK=normal ./tt
如果定位不到问题,可以通过下面命令继续深层次分析(上面运行后会提示下面command,只需要将 gv替换成text即可)如例子
pprof ./tt “/tmp/tt.17394.main-end.heap” --inuse_objects --lines --heapcheck --edgefraction=1e-10 --nodefraction=1e-10 --text
二、基本问题分析
案例
#include<stdio.h>
#include<stdlib.h>
#include <unistd.h>
#include <iostream>
void test()
{
int* arr = (int*)malloc(sizeof(int) * 10);
}
int main()
{
test();
for(int i=0;i<1;i++){
sleep(1);
}
std::cout<<"hello world"<<std::endl;
return 0;
}
编译
g++ -o tt test_asan.cpp -g -O0
运行代码
env PPROF_PATH=/usr/bin/pprof LD_PRELOAD="/usr/lib/libtcmalloc.so" HEAPCHECK=normal ./tt
结果如下:
大家注意,这里有关键字Leak,你就得当心这里可能存在内存泄漏,提示 Leak of 4 bytes in 1 objects allocated from
通过main-》test定位到函数test存在泄漏
如果堆栈分析觉得还不够充分,可以通过pprof命令来深层次分析
[root@MiWiFi-R4AC-srv code]# env PPROF_PATH=/usr/bin/pprof LD_PRELOAD="/usr/lib/libtcmalloc.so" HEAPCHECK=normal ./tt
WARNING: Perftools heap leak checker is active -- Performance may suffer
hello
Have memory regions w/o callers: might report false leaks
Leak check _main_ detected leaks of 40 bytes in 1 objects
The 1 largest leaks:
Using local file ./tt.
Leak of 40 bytes in 1 objects allocated from:
@ 400882 test
@ 400895 main
@ 7f31cb2c5555 __libc_start_main
@ 4007a9 _start
If the preceding stack traces are not enough to find the leaks, try running THIS shell command:
pprof ./tt "/tmp/tt.21850._main_-end.heap" --inuse_objects --lines --heapcheck --edgefraction=1e-10 --nodefraction=1e-10 --gv
If you are still puzzled about why the leaks are there, try rerunning this program with HEAP_CHECK_TEST_POINTER_ALIGNMENT=1 and/or with HEAP_CHECK_MAX_POINTER_OFFSET=-1
If the leak report occurs in a small fraction of runs, try running with TCMALLOC_MAX_FREE_QUEUE_SIZE of few hundred MB or with TCMALLOC_RECLAIM_MEMORY=false, it might help find leaks more repeatably
Exiting with error code (instead of crashing) because of whole-program memory leaks
打印堆栈分析
查看错误信息,通过addr2line查看堆栈
dmesg |grep tt
打印函数堆栈
addr2line -e tt -a 000000000040092d
dmesg |grep tt
[ 0.000000] Command line: BOOT_IMAGE=/boot/vmlinuz-3.10.0-1160.11.1.el7.x86_64 root=UUID=1c419d6c-5064-4a2b-953c-05b2c67edb15 ro no_timer_check console=tty0 console=ttyS0,115200n8 net.ifnames=0 biosdevname=0 elevator=noop crashkernel=auto LANG=en_US.UTF-8
[ 0.000000] Kernel command line: BOOT_IMAGE=/boot/vmlinuz-3.10.0-1160.11.1.el7.x86_64 root=UUID=1c419d6c-5064-4a2b-953c-05b2c67edb15 ro no_timer_check console=tty0 console=ttyS0,115200n8 net.ifnames=0 biosdevname=0 elevator=noop crashkernel=auto LANG=en_US.UTF-8
[ 0.000000] console [tty0] enabled
[ 0.000000] console [ttyS0] enabled
[ 1.354325] input: Power Button as /devices/LNXSYSTM:00/LNXPWRBN:00/input/input0
[ 1.355335] ACPI: Power Button [PWRF]
[ 1.356041] input: Sleep Button as /devices/LNXSYSTM:00/LNXSLPBN:00/input/input1
[ 1.357113] ACPI: Sleep Button [SLPF]
[ 1.358524] ACPI: Battery Slot [BAT0] (battery present)
[ 1.403730] rtc_cmos rtc_cmos: setting system clock to 2022-12-26 02:52:18 UTC (1672023138)
[ 2.896749] sr 0:0:1:0: Attached scsi CD-ROM sr0
[ 2.906910] sd 1:0:0:0: [sdb] Attached SCSI disk
[ 2.925278] sd 0:0:0:0: [sda] Attached SCSI disk
[ 3.967952] SGI XFS with ACLs, security attributes, no debug enabled
[ 9.046218] sd 0:0:0:0: Attached scsi generic sg0 type 0
[ 9.051582] sr 0:0:1:0: Attached scsi generic sg1 type 5
[ 9.057517] sd 1:0:0:0: Attached scsi generic sg2 type 0
[ 9.057538] vgdrvHeartbeatInit: Setting up heartbeat to trigger every 2000 milliseconds
[ 233.378442] tt[3329]: segfault at 0 ip 000000000040092d sp 00007ffec758ac70 error 6 in tt[400000+1000]
[code]$ addr2line -e tt -a 000000000040092d
0x000000000040092d
/home/vagrant/code/test_asan.cpp:22
深层次分析
google-perftool提供了一个叫pprof的工具,它是一个perl的脚本,通过这个工具,可以将google-perftool的输出结果分析得更为直观,输出为text、图片、pdf等格式。
这里我们把结果通过text的方式输出:你只需要把刚才的–gv换成–text
pprof ./tt "/tmp/tt.21850._main_-end.heap" --inuse_objects --lines --heapcheck --edgefraction=1e-10 --nodefraction=1e-10 --text
通过下面的打印,可以定位到堆栈中test_asan.cpp第7行,即test中malloc函数
Using local file ./tt.
Using local file /tmp/tt.21850._main_-end.heap.
Total: 1 objects
1 100.0% 100.0% 1 100.0% test /home/vagrant/code/test_asan.cpp:7
0 0.0% 100.0% 1 100.0% __libc_start_main ??:0
0 0.0% 100.0% 1 100.0% _start ??:0
0 0.0% 100.0% 1 100.0% main /home/vagrant/code/test_asan.cpp:11
三、深入问题分析
这里简单的介绍下把,等有时间了把这块详细补全。
实际上项目中遇到的内存泄漏问题是异常复杂的,上面示例只是小试牛刀。项目常见的内存泄漏点大家都清楚,new了但是没有得到delete,但是要根据pprof工具对应的函数,代码行找到对应的泄漏点你可能需要花费点功夫。
实际上你的大多数应用都是以服务的方式启动,长时间处于运行状态。你需要定期来检测下内存泄漏情况,那么这时你需要显示的调用接口来输出leak情况。
将下面的代码加到你的定时检测逻辑里,或者需要观察的点,那么他就会输出示例1中的内容,动态的帮助你分析内存泄漏点。
bool memory_check(void* arg)
{
HeapLeakChecker::NoGlobalLeaks();
return TRUE;
}
不仅仅要关注tcmalloc申请大小内存块,还要关注内存块的在合适的时间及时回收,否则造成内存占用过高。通过MallocExtension::instance()->ReleaseFreeMemory()设置频率,如果设置为0,代表永远不交回。数字越大代表交回的频率越大。一般合理的值就是设置一个0 - 10 之间的一个数。也可以通过设置环境变量 TCMALLOC_RELEASE_RATE来设置这个rate。官网通常设定为 MallocExtension::instance()->SetMemoryReleaseRate(7.0);
总结
通过本文的学习,可以轻松定位到内存泄露的问题,希望对你有所帮助把。如果觉得还不错,请点赞收藏!
转载:https://blog.csdn.net/weixin_44834554/article/details/128401596