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

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


前言

做C、C++开发都知道,内存泄漏问题的确是让人最头疼的,因为一个疏忽就会造成new与delete不成对等,都会造成内存泄漏的问题,而且很难分析到具体泄漏点在哪里,通过查询各种资料,通常我们可以使用 代码review、tcmalloc、assan 等工具进行分析。发现tcmalloc定位项目上一些很棘手的内存泄漏问题,通过本文的例子,让你能够快速定位内存泄漏点。希望能分享给大家。
gperf/gperftools 是google用作内存和CPU分析的工具,基于tcmalloc(也是google内存分配库,替换glibc的malloc和free)。
gperf主要有三个应用:
gmc: gperf memory check, 内存检查(泄漏,错误等),参考: heap_checker
gmp: gperf memory profile, 内存性能分析(哪个函数内存分配多),参考:heapprofile
gcp: gperf cpu profile, CPU性能分析(函数消耗CPU多),参考:cpuprofile


一、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(依赖libunwind,注意安装的版本)


如果需要,在线安装也可以
yum search libunwind # 查找,然后选择需要的安装  
yum install libunwind-devel.x86_64  

源码安装
wget https://github.com/libunwind/libunwind/archive/v0.99.tar.gz  
tar -xvf v0.99.tar.gz  
cd libunwind-0.99  
autoreconf --force -v --install  
./configure --prefix=gperftools-tutorial/output   
make   
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
其中:HEAPCHECK=args有四种
minimal:忽略在进入main()函数之前的所有内存分配
normal:检查在程序运行时的所有内存分配的情况,报告在程序结束时没有释放的内存。在google中最常用的模式就是这种模式。
strict:与normal类似,但是也报告在全局析构中忘记析构导致发生的内存泄露
draconian:属于一种更精确的内存检查,如果申请的内存没有被释放,他就会报告内存泄露

二、基本问题分析

案例

#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);


总结

通过本文的学习,可以轻松定位到内存泄露的问题,希望对你有所帮助把。如果觉得还不错,请点赞收藏!

  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

c+猿辅导

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值