内存泄漏分析的利器——gperftools的Heap Checker
介绍
gperftools是Google开源的一款非常使用的性能分析工具集。主要由四个组件组成
-
Tcmalloc内存分析器
Tcmalloc是Thread Cache malloc的缩写,号称比ptmalloc2更快的内存管理库。
Tcmalloc原理是为每个线程单独分配一个线程本地的Cache,少量的地址分配就直接从Cache中分配,并且定期做垃圾回收,将线程本地Cache中的空闲内存返回给全局控制堆;Tcmalloc认为小于等于32K的对象是小对象,大对象直接从全局控制堆以页为单位进行分配,所以大对象总是页对齐的;Tcmalloc中一个页可以存入一些相同大小的小对象,小对象从本地内存链表中分配,大对象从中心内存堆分配。
优势:- 快速:相比ptmalloc2,Tcmalloc的性能成倍提升。尤其是Tcmalloc可以减少多线程之间锁的竞争问题,在小对象(32K)上能达到零竞争。
- 占用空间小:相比ptmalloc2,tcmalloc对小对象占用空间进行了优化。例如:分配N个8字节对象只需要占用8N1.01字节的空间。即,只需要多使用1%的空间。而ptmalloc2中每个对象都需要使用一个4字节的头信息,最后占用的字节可能达到8N8。
- 不易出现内存暴涨(ptmalloc2使用内存池,长时间没有将内存还给系统就会造成内存暴涨,tcmalloc可以通过MallocExtension::instance()->ReleaseFreeMemory()类设置内存还给系统的速度)。
-
基于tcmalloc的堆内存检测工具Heap-profiler
Heap-profiler是内存监控器,可以随时知道内存的使用情况。 -
基于tcmalloc的内存泄漏分析工具Heap-checker
Heap-checker是专门检测内存泄漏的工具,heap-checker是常用的用法是检测程序整个进程生命周期的内存泄露. -
基于tcmalloc实现的程序CPU性能监测工具Cpu-profiler
Cpu-profiler主要是通过采样的的方式,给出一段时间内程序实际占用cpu时间偏进行统计和分析。
安装
- 从git上获取libunwind,并编译安装
git clone https://github.com/libunwind/libunwind.git
cd libunwind
sh ./autogen.sh
./configure
make
sudo make install
- 编译安装gperftools了
git clone https://github.com/gperftools/gperftools.git
sh autogen.sh
./configure
make
sudo make install
- 可视化分析工具kcachegrind
sudo apt install kcachegrind
示例
#include <stdlib.h>
int main() {
const int array_count = 4;
int* p = new int[array_count];
return 0;
}
第5行在堆上申请了空间,但是没有释放。为了包含更多调试信息,我们使用-g方式编译该文件
g++ leak.cpp -ltcmalloc -g -o leak
注意
此处链接了tcmalloc库,它是gperftools内存问题分析方案的基础。一般来说,比较推荐链接该库,它的效率比编译器自带的内存管理库要高。
gperftools运行方式比较独特,对于heap checker工具,需要这样调用
HEAPCHECK=normal /home/fangliang/gperftools_test/heap_checker/leak
或
设置相关的环境变量
# which pprof
/usr/local/bin/pprof
# export PPROF_PATH=/usr/local/bin/pprof
# env HEAPCHECK=normal LD_PRELOAD="/usr/local/lib/libtcmalloc.so" ./test
HEAPCHECK除了nomal外,还可以选择strict或者draconian。最后一个参数就是我们程序的路径。其分析结果展现如下
WARNING: Perftools heap leak checker is active -- Performance may suffer
Have memory regions w/o callers: might report false leaks
Leak check _main_ detected leaks of 16 bytes in 1 objects
The 1 largest leaks:
Using local file /home/fangliang/gperftools_test/heap_checker/leak.
Leak of 16 bytes in 1 objects allocated from:
@ 55f9903b8723 main
@ 7fed6bbc9b97 __libc_start_main
@ 55f9903b862a _start
If the preceding stack traces are not enough to find the leaks, try running THIS shell command:
pprof /home/fangliang/gperftools_test/heap_checker/leak "/tmp/leak.3384._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
Exiting with error code (instead of crashing) because of whole-program memory leaks
第6行显示我们程序有1块16字节的空间泄漏。第7到第9行,展示了泄漏处的调用堆栈。但是这个信息展现方式并没有直接指出问题产生的行数。我们可以使用其提示的第14行指令,调用可视化工具
如此,我们便可以清晰的知道,leak.cpp第5行代码申请的空间存在内存泄漏。
如果我们项目中不可以链接tcmalloc,我们还可以使用如下方式调用heap checker,其结果和上面描述的一致
LD_PRELOAD="/usr/local/lib/libtcmalloc.so" HEAPCHECK=normal /home/fangliang/gperftools_test/heap_checker/leak
检测的方式有两种,
一种是需要把tcmalloc库编译进工程,
另一种不需要编译进工程,只需要在运行时通过LD_PRELOAD命令加载tcmalloc库。
官方推荐第一种方法,第二种方式存在安全问题。
第一种方式,在makefile文件中编译进tcmalloc库,运行如下命令
env PPROF_PATH=./objs/pprof HEAPCHECK=normal ./application
第二种方法,
envLD_PRELOAD="/home/zhengbin/gperf/lib/libtcmalloc.so" PPROF_PATH=../../gperf/bin/pprof HEAPCHECK=normal ./application
PPROF_PATH为检测工具路径,application为可执行程序
强杀进程会使工具失效,程序需要正常退出才会打印出相应的内存泄露信息。
HEAPCHECK有四个等级minimal,normal,strick,draconian。一般来说normal能满足绝大部分使用场景了。下面是官方文档中关于四个等级的介绍
原理
valgrind是基于模拟器的技术实现,而本文介绍的gperftools则是通过在用户代码中嵌入检测代码实现的
因为工具实现原理的差异,在解决问题时也各有特长:
Valgrind:适合解决非法读写,无主内存泄露等问题定位。
gperftools:长期运行进程内存使用量应该是稳定的,gperftools适合发现运行过程中内存差异,提供泄露的函数范围。而且对影响影响小,适合高压力快速复现。
参考资料
http://gperftools.googlecode.com/svn/trunk/doc/heap_checker.html
https://github.com/gperftools/gperftools
https://blog.csdn.net/oujiangping/article/details/77172802