小言_互联网的博客

c/c++ 之tcmalloc pprof分析解决内存泄漏和内存暴涨问题

666人阅读  评论(0)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

做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
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场