C++内存泄漏排查
对于C++程序员来说,内存问题是不可避免的。除了日常code的时候多注意,在遇到线上的内存泄漏问题我们多使用已有的工具对其进行排查。下面主要介绍三个常用的内存泄漏的检测工具。
valgrind
valgrind是大名鼎鼎的Linux上的开源工具包,其中有多个组件包括 Memcheck、Cachegrind、Callgrind、Helgrind、Massif等。我们使用valgrind的Memcheck 去检测内存泄漏。( valgrind资源占用很大,对于性能的影响很大 )
安装
-
下载
wget https://sourceware.org/pub/valgrind/valgrind-3.18.1.tar.bz2 -
解压
tar -jxvf valgrind-3.18.1.tar.bz2 -
编译并安装
cd valgrind-3.18.1/ ./configure make make install
使用
-
源代码(valgrind_leak.cpp)
#include <map> void leak() { std::map<int, int *> m_; m_[1] = new int(1); m_.erase(1); } int main() { leak(); return 0; } -
编译
g++ -g -o valgrind_leak valgrind_leak.cpp -
运行
valgrind --leak-check=yes --track-origins=yes ./valgrind_leak -
结果
可以看出报告上给出了具体的内存泄漏位置:在valgrind_leak.cpp的第五行出现了内存泄漏。
这只是一个简单的例子,线上的内存泄漏可能远比这复杂,而且valgrind只能在程序结束的时候才能给出报告(也可结合gdb等 使其不用退出程序,但是会更复杂些valgrind.org/docs/manual…),再加上资源消耗过大,所以有些程序不适合用valgrind。
AddressSanitizer(Asan)
Address Sanitizer是google的一个内存错误检测工具。它非常快,平均拖慢程序大概2倍。从LLVM 3.1版本开始Address Sanitizer是其中一部分,从gcc 4.8开始 Address Sanitizer也是gcc的一部分。
安装
Address Sanitizer作为gcc的一部分,所以我们只需要一个libasan.so动态库;如果没有 使用yum安装即可。
yum install libasan
使用
-
源代码(asan.cpp)
#include <map> void leak() { std::map<int, int *> m_; m_[1] = new int(1); m_.erase(1); } int main() { leak(); return 0; } -
编译
g++ -g -o asan asan.cpp -fsanitize=address -fno-omit-frame-pointer -
运行
./asan -
结果
gperftools
gperftools是google的性能分析工具,包括HEAP PROFILER、HEAP CHECKER、CPU PROFILER
安装
yum install gperftools
使用
-
源代码(gperf.cpp)
#include <map> void leak() { std::map<int, int *> m_; m_[1] = new int(1); m_.erase(1); } int main() { leak(); return 0; } -
编译
g++ -o gperf -g gperf.cpp -
运行
LD_PRELOAD=/usr/lib64/libtcmalloc.so HEAPCHECK=draconian ./gperfWARNING: Perftools heap leak checker is active -- Performance may suffer Not looking for thread stacks; objects reachable only from there will be reported as leaks Leak check _main_ detected leaks of 463 bytes in 11 objects The 11 largest leaks: *** WARNING: Cannot convert addresses to symbols in output below. *** Reason: Cannot find 'pprof' (is PPROF_PATH set correctly?) *** If you cannot fix this, try running pprof directly. Leak of 232 bytes in 1 objects allocated from: @ 7ff140431468 @ 7ff14111b446 @ 7ff141104d3e @ 7ff1414f78ba @ 7ff1414f79ba @ 7ff1414e8fda Leak of 56 bytes in 1 objects allocated from: @ 7ff14110f982 @ 7ff141104b92 @ 7ff1414f78ba @ 7ff1414f79ba @ 7ff1414e8fda Leak of 32 bytes in 1 objects allocated from: @ 7ff14110f36b @ 7ff141104b92 @ 7ff1414f78ba @ 7ff1414f79ba @ 7ff1414e8fda Leak of 31 bytes in 1 objects allocated from: @ 7ff141110b83 @ 7ff14110f4bc @ 7ff141104b92 @ 7ff1414f78ba @ 7ff1414f79ba @ 7ff1414e8fda Leak of 24 bytes in 1 objects allocated from: @ 7ff1404f0bd1 @ 7ff140431671 @ 7ff14111b42c @ 7ff141104d3e @ 7ff1414f78ba @ 7ff1414f79ba @ 7ff1414e8fda Leak of 24 bytes in 1 objects allocated from: @ 7ff1404f0bd1 @ 7ff140431671 @ 7ff14111b446 @ 7ff141104d3e @ 7ff1414f78ba @ 7ff1414f79ba @ 7ff1414e8fda Leak of 20 bytes in 1 objects allocated from: @ 7ff140431642 @ 7ff14111b42c @ 7ff141104d3e @ 7ff1414f78ba @ 7ff1414f79ba @ 7ff1414e8fda Leak of 20 bytes in 1 objects allocated from: @ 7ff140431642 @ 7ff14111b446 @ 7ff141104d3e @ 7ff1414f78ba @ 7ff1414f79ba @ 7ff1414e8fda Leak of 16 bytes in 1 objects allocated from: @ 7ff14110789a @ 7ff141104813 @ 7ff1414f78ba @ 7ff1414f79ba @ 7ff1414e8fda Leak of 4 bytes in 1 objects allocated from: @ 7ff14110bf82 @ 7ff14110f98d @ 7ff141104b92 @ 7ff1414f78ba @ 7ff1414f79ba @ 7ff1414e8fda Leak of 4 bytes in 1 objects allocated from: @ 400ae5 @ 400b5d @ 7ff14041b493 @ 400a0e If the preceding stack traces are not enough to find the leaks, try running THIS shell command: pprof ./gperf "/tmp/gperf.150872._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 repeata Exiting with error code (instead of crashing) because of whole-program memory leaks可以看到报告中给出的
Leak check _main_ detected leaks of 463 bytes in 11 objects。gperftools中包含tcmalloc,在执行完检查后 会在/tmp目录下生成一个heap文件,可以使用pprof来解析。pprof ./gperf "/tmp/gperf.150872._main_-end.heap" --lines --text --stackUsing local file ./gperf. Using local file /tmp/gperf.150872._main_-end.heap. Total: 0.0 MB Stacks: 32 (00007ff14110f36b) ??:0:HeapLeakChecker_InternalInitStart (00007ff1414f78b9) dl-init.c:0:call_init.part.0 (00007ff1414f79b9) ??:0:_dl_init (00007ff1414e8fd9) ??:0:_dl_start_user 20 (00007ff140431642) ??:0:__add_to_environ (00007ff14111b445) ??:0:MallocExtension::Initialize (00007ff1414f78b9) dl-init.c:0:call_init.part.0 (00007ff1414f79b9) ??:0:_dl_init (00007ff1414e8fd9) ??:0:_dl_start_user 20 (00007ff140431642) ??:0:__add_to_environ (00007ff14111b42b) ??:0:MallocExtension::Initialize (00007ff1414f78b9) dl-init.c:0:call_init.part.0 (00007ff1414f79b9) ??:0:_dl_init (00007ff1414e8fd9) ??:0:_dl_start_user 16 (00007ff1414f78b9) dl-init.c:0:call_init.part.0 (00007ff1414f79b9) ??:0:_dl_init (00007ff1414e8fd9) ??:0:_dl_start_user 24 (00007ff1404f0bd1) ??:0:__GI___tsearch (00007ff140431670) ??:0:__add_to_environ (00007ff14111b445) ??:0:MallocExtension::Initialize (00007ff1414f78b9) dl-init.c:0:call_init.part.0 (00007ff1414f79b9) ??:0:_dl_init (00007ff1414e8fd9) ??:0:_dl_start_user 56 (00007ff14110f982) ??:0:HeapLeakChecker_InternalInitStart (00007ff1414f78b9) dl-init.c:0:call_init.part.0 (00007ff1414f79b9) ??:0:_dl_init (00007ff1414e8fd9) ??:0:_dl_start_user 4 (0000000000400ae5) /data/mem_leak/gperf/gperf.cpp:5:leak (0000000000400b5c) /data/mem_leak/gperf/gperf.cpp:9:main (00007ff14041b492) ??:0:__libc_start_main (0000000000400a0d) ??:0:_start 24 (00007ff1404f0bd1) ??:0:__GI___tsearch (00007ff140431670) ??:0:__add_to_environ (00007ff14111b42b) ??:0:MallocExtension::Initialize (00007ff1414f78b9) dl-init.c:0:call_init.part.0 (00007ff1414f79b9) ??:0:_dl_init (00007ff1414e8fd9) ??:0:_dl_start_user 4 (00007ff14110bf82) ??:0:HeapLeakChecker::HeapLeakChecker (00007ff14110f98c) ??:0:HeapLeakChecker_InternalInitStart (00007ff1414f78b9) dl-init.c:0:call_init.part.0 (00007ff1414f79b9) ??:0:_dl_init (00007ff1414e8fd9) ??:0:_dl_start_user 232 (00007ff140431468) ??:0:__add_to_environ (00007ff14111b445) ??:0:MallocExtension::Initialize (00007ff1414f78b9) dl-init.c:0:call_init.part.0 (00007ff1414f79b9) ??:0:_dl_init (00007ff1414e8fd9) ??:0:_dl_start_user 31 (00007ff141110b83) ??:0:std::__cxx11::basic_string::_M_append (00007ff14110f4bb) ??:0:HeapLeakChecker_InternalInitStart (00007ff1414f78b9) dl-init.c:0:call_init.part.0 (00007ff1414f79b9) ??:0:_dl_init (00007ff1414e8fd9) ??:0:_dl_start_user Leak of 232 bytes in 1 objects allocated from: @ 7ff140431468 unknown @ 00007ff14111b445 MallocExtension::Initialize ??:0 @ 00007ff141104d3d tcmalloc::ThreadCache::threadlocal_data_ ??:0 @ 00007ff1414f78b9 call_init.part.0 dl-init.c:0 @ 00007ff1414f79b9 _dl_init :0 @ 00007ff1414e8fd9 _dl_start_user :0 Leak of 56 bytes in 1 objects allocated from: @ 7ff14110f982 unknown @ 00007ff141104b91 tcmalloc::ThreadCache::threadlocal_data_ ??:0 @ 00007ff1414f78b9 call_init.part.0 dl-init.c:0 @ 00007ff1414f79b9 _dl_init :0 @ 00007ff1414e8fd9 _dl_start_user :0 Leak of 32 bytes in 1 objects allocated from: @ 7ff14110f36b unknown @ 00007ff141104b91 tcmalloc::ThreadCache::threadlocal_data_ ??:0 @ 00007ff1414f78b9 call_init.part.0 dl-init.c:0 @ 00007ff1414f79b9 _dl_init :0 @ 00007ff1414e8fd9 _dl_start_user :0 Leak of 31 bytes in 1 objects allocated from: @ 7ff141110b83 unknown @ 00007ff14110f4bb HeapLeakChecker_InternalInitStart ??:0 @ 00007ff141104b91 tcmalloc::ThreadCache::threadlocal_data_ ??:0 @ 00007ff1414f78b9 call_init.part.0 dl-init.c:0 @ 00007ff1414f79b9 _dl_init :0 @ 00007ff1414e8fd9 _dl_start_user :0 Leak of 24 bytes in 1 objects allocated from: @ 7ff1404f0bd1 unknown @ 00007ff140431670 __add_to_environ :0 @ 00007ff14111b42b MallocExtension::Initialize ??:0 @ 00007ff141104d3d tcmalloc::ThreadCache::threadlocal_data_ ??:0 @ 00007ff1414f78b9 call_init.part.0 dl-init.c:0 @ 00007ff1414f79b9 _dl_init :0 @ 00007ff1414e8fd9 _dl_start_user :0 Leak of 24 bytes in 1 objects allocated from: @ 7ff1404f0bd1 unknown @ 00007ff140431670 __add_to_environ :0 @ 00007ff14111b445 MallocExtension::Initialize ??:0 @ 00007ff141104d3d tcmalloc::ThreadCache::threadlocal_data_ ??:0 @ 00007ff1414f78b9 call_init.part.0 dl-init.c:0 @ 00007ff1414f79b9 _dl_init :0 @ 00007ff1414e8fd9 _dl_start_user :0 Leak of 20 bytes in 1 objects allocated from: @ 7ff140431642 unknown @ 00007ff14111b42b MallocExtension::Initialize ??:0 @ 00007ff141104d3d tcmalloc::ThreadCache::threadlocal_data_ ??:0 @ 00007ff1414f78b9 call_init.part.0 dl-init.c:0 @ 00007ff1414f79b9 _dl_init :0 @ 00007ff1414e8fd9 _dl_start_user :0 Leak of 20 bytes in 1 objects allocated from: @ 7ff140431642 unknown @ 00007ff14111b445 MallocExtension::Initialize ??:0 @ 00007ff141104d3d tcmalloc::ThreadCache::threadlocal_data_ ??:0 @ 00007ff1414f78b9 call_init.part.0 dl-init.c:0 @ 00007ff1414f79b9 _dl_init :0 @ 00007ff1414e8fd9 _dl_start_user :0 Leak of 16 bytes in 1 objects allocated from: @ 7ff14110789a unknown @ 00007ff141104812 tcmalloc::ThreadCache::threadlocal_data_ ??:0 @ 00007ff1414f78b9 call_init.part.0 dl-init.c:0 @ 00007ff1414f79b9 _dl_init :0 @ 00007ff1414e8fd9 _dl_start_user :0 Leak of 4 bytes in 1 objects allocated from: @ 7ff14110bf82 unknown @ 00007ff14110f98c HeapLeakChecker_InternalInitStart ??:0 @ 00007ff141104b91 tcmalloc::ThreadCache::threadlocal_data_ ??:0 @ 00007ff1414f78b9 call_init.part.0 dl-init.c:0 @ 00007ff1414f79b9 _dl_init :0 @ 00007ff1414e8fd9 _dl_start_user :0 Leak of 4 bytes in 1 objects allocated from: @ 00400ae5 unknown @ 0000000000400b5c main /data/mem_leak/gperf/gperf.cpp:9 @ 00007ff14041b492 __libc_start_main ??:0 @ 0000000000400a0d _start ??:0 0.0 58.7% 58.7% 0.0 69.1% __add_to_environ :0 0.0 19.0% 77.8% 0.0 26.6% HeapLeakChecker_InternalInitStart ??:0 0.0 10.4% 88.1% 0.0 10.4% __GI___tsearch :0 0.0 6.7% 94.8% 0.0 6.7% std::__cxx11::basic_string::_M_append ??:0 0.0 3.5% 98.3% 0.0 99.1% call_init.part.0 dl-init.c:0 0.0 0.9% 99.1% 0.0 0.9% HeapLeakChecker::HeapLeakChecker ??:0 0.0 0.9% 100.0% 0.0 0.9% leak /data/mem_leak/gperf/gperf.cpp:5 0.0 0.0% 100.0% 0.0 69.1% MallocExtension::Initialize ??:0 0.0 0.0% 100.0% 0.0 0.9% __libc_start_main ??:0 0.0 0.0% 100.0% 0.0 99.1% _dl_init :0 0.0 0.0% 100.0% 0.0 99.1% _dl_start_user :0 0.0 0.0% 100.0% 0.0 0.9% _start ??:0 0.0 0.0% 100.0% 0.0 0.9% main /data/mem_leak/gperf/gperf.cpp:9
有的时候内存泄漏并不一定是demo上的情况,也可能是自己分配的内存,但是后面忘了去用,就一直放在那里,也没有去清除。可以用gperftools的HEAP PROFILE工具去查看内存分配的热点。
HEAP PROFILE
官方手册
-
源码
#include <map> #include <unistd.h> void malloc_mem(std::map<int, int *> &p, int i) { p[i] = new int(i); } int main() { int N = 1000; std::map<int, int *> m_; while (N--) { malloc_mem(m_, N); sleep(100); } return 0; } -
编译
g++ -o gperf_malloc -g gperf_malloc.cpp -
执行
HEAPPROFILE=/tmp/heapprof HEAP_PROFILE_TIME_INTERVAL=10 LD_PRELOAD="/usr/lib64/libtcmalloc.so" ./gperf_mallocHEAP_PROFILE_TIME_INTERVAL 间隔多长时间产生一个dump文件
HEAP_PROFILE_ALLOCATION_INTERVAL 内存消耗超过多少产生一个dump文件
具体参数可见官方文档gperftools.github.io/gperftools/…
-
分析
# 生成txt pprof --lines --text ./gperf_malloc /tmp/heapprof.0001.heap > out.txt # 生成pdf pprof --lines --pdf ./gperf_malloc /tmp/heapprof.0001.heap > out.pdf -
对比分析
生成的文件支持以一个为基准 进行比较分析,因为有的程序启动的时候会加载很多数据,需要分配很多内存,后续的持续内存分配 则可以用前几个的heap文件为基准去分析。比如以第一个为基准,查看第三个与第一个的差。
# 生成txt pprof --lines --text --base /tmp/heapprof.0001.heap ./gperf_malloc /tmp/heapprof.0003.heap > diff.txt # 生成pdf pprof --lines --pdf --base /tmp/heapprof.0001.heap ./gperf_malloc /tmp/heapprof.0003.heap > diff.pdfdiff.txtTotal: 0.0 MB 0.0 92.3% 92.3% 0.0 92.3% __gnu_cxx::new_allocator::allocate /usr/include/c++/8/ext/new_allocator.h:112 0.0 7.7% 100.0% 0.0 100.0% malloc_mem /data/mem_leak/gperf/gperf_malloc.cpp:6 0.0 0.0% 100.0% 0.0 100.0% __libc_start_main ??:0 0.0 0.0% 100.0% 0.0 100.0% _start ??:0 0.0 0.0% 100.0% 0.0 100.0% main /data/mem_leak/gperf/gperf_malloc.cpp:13 0.0 0.0% 100.0% 0.0 92.3% std::_Rb_tree::_M_create_node /usr/include/c++/8/bits/stl_tree.h:642 0.0 0.0% 100.0% 0.0 92.3% std::_Rb_tree::_M_emplace_hint_unique /usr/include/c++/8/bits/stl_tree.h:2421 0.0 0.0% 100.0% 0.0 92.3% std::_Rb_tree::_M_get_node /usr/include/c++/8/bits/stl_tree.h:588 0.0 0.0% 100.0% 0.0 92.3% std::allocator_traits::allocate /usr/include/c++/8/bits/alloc_traits.h:436 0.0 0.0% 100.0% 0.0 92.3% std::map::operator[] /usr/include/c++/8/bits/stl_map.h:499- 第一列是直接分配的内存单位 MB.
- 第四列包含自己及其所有被调用者的内存使用情况.
- 第二列和第五列只是第一列和第四列中数字的百分比表示.
- 第三列是第二列的累积和(即,第三列中的第 k 个条目是第二列中前 k 个条目的总和。)