本文主要是介绍使用 gperftools 检测内存泄露,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
每个 C/C++ 程序员可能都经历过定位内存泄露问题的痛苦。一方面,为了减少内存泄露问题,在 C++ 程序中,我们应该尽量使用智能指针,在 C 程序中,我们也可以通过一些内存池技术来管理内存的申请与释放。另一方面,当内存泄露问题真的出现时,通过 gperftools 的 heap checker
,我们也可以比较轻松地找到内存泄露的线索,缩短问题定位时间。
一、gperftools 简介
gperftools 是 google 开源的一组套件,提供了高性能的、支持多线程的 malloc 实现,以及一组优秀的性能分析工具。
二、安装 gperftools
2.1、下载源码
从 gperftools github 官网上下载最新版本的源码包:
wget https://github.com/gperftools/gperftools/releases/download/gperftools-2.9.1/gperftools-2.9.1.tar.gz
2.2、解压源码包
tar -zxv -f gperftools-2.9.1.tar.gz
2.3、configure
cd gperftools-2.9.1
./configure
命令结束执行后出现一个报错:
configure: WARNING: No frame pointers and no libunwind. Using experimental backtrace capturing via libgcc. Expect crashy cpu profiler.
这是因为没有安装 libunwind
。这里直接使用 yum 的方式安装:
yum install libunwind-devel
再次执行 ./configure
,命令执行成功。
2.4、编译并安装
执行如下两个命令,进行编译并安装:
make
sudo make install
最后执行 ldconfig
更新动态库文件
2.5、确认安装成功
执行如下命令,确认 gperftools 安装成功
[root@36eab106d3bf gperftools-2.9.1]# pprof --version
pprof (part of gperftools 2.0)Copyright 1998-2007 Google Inc.This is BSD licensed software; see the source for copying conditions
and license information.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
三、内存泄露检测
gperftools 的 heap checker
组件可以用于检测 C++ 程序中的内存泄露问题。要使用 heap checker
,总共分 3 步:
- 链接 tcmalloc 库到应用程序中
- 运行程序
- 分析输出
3.1、链接 tcmalloc 库
heapchecker
是 tcmalloc 的一部分,所以为了在可执行程序中使用 heap checker
,在应用程序的链接阶段使用 -ltcmalloc
链接 tcmalloc 库。
3.2、运行代码
使用 heap checker
的推荐方式是 完整程序运行模式
,此时 heap checker
可以在程序的 main 函数开始前跟踪内存分配,然后在程序退出时再次检查。如果它发现来了任何内存泄露,heap checker
会直接终止程序(通过 exit(1)),并打印一条消息,告诉你接下来如何使用 pprof 来继续跟踪该内存泄露问题。
完整程序运行
的 heap checker
支持 4 种模式:
- minimal:在程序初始化期间(即 main 函数运行前)不进行内存泄露检查
- normal:正常模式,通过跟踪某块内存是否可以被
live object
来访问,来判断是否出现内存泄露 - strict:类似于
normal
模式,但是对全局对象的内存泄露有一些额外的检查 - draconian:在该模式下,只有所有申请的内存都被释放,才认为没有出现内存泄露
一般使用 normal
模式就可以满足日常要求了。
heap checker
的另一种使用方法是检测指定代码块是否出现了内存泄露。为了实现这一点,需要在代码片段的开始部分创建一个 HeapLeakChecker
结构体,并在结束部分调用 NoLeaks()
。例如:
HeapLeakChecker heap_checker("test_foo");
{code that exercises some foo functionality;this code should not leak memory;
}
if (!heap_checker.NoLeaks()) assert(NULL == "heap memory leak");
需要注意,添加 HeapLeakChecker
只是在程序中添加了内存泄露检测代码,为了真实地检测程序是否出现了内存泄露,仍然需要运行程序,并打开 heap-checker。
env HEAPCHECK=local your_program
除了指定为 local 模式外,之前的 normal
等模式也是可以的,此时除了运行 local
检查外,还将进行 完整程序运行
检查。
当然,运行 heap checker
是有代价的。heap checker
需要记录每次内存申请时的调用栈信息,这就导致了使用 heap checker
时,程序需要消耗更多的内存,同时程序运行速度也更慢。另外,需要注意,由于 heap checker
内部使用了 heap profile
框架,所以不能同时运行 heap checker
和 heap profile
。
3.3、忽略已知的内存泄露
对于已知的内存泄露,如果想让 heap checker
忽略这些内存泄露信息,可以在应用程序代码中添加中如下代码:
{HeapLeakChecker::Disabler disabler;<leaky code>
}
另一种方式是使用 IgnoreObject,它接收一个指针参数,对该参数所指向的对象将不再进行内存泄露检查。
3.4、使用 pprof 查看内存泄露结果
heap checker
运行结束时会打印基本的泄露信息,包括调用栈和泄露对象的地址。除此之外,还可以使用 pprof
命令行工具来可视化地查看调用栈。
四、示例
接下来通过一个示例讲述如何使用 gperftools 的 heap checker
来发现程序的内存泄露问题。
4.1、示例程序如下:
// Copyright (C) fuchencong.com#include <iostream>int func() {int *p = new int(10);return 0;
}int main() {std::cout << "memory leak test" << std::endl;return func();
}
4.2、编译程序,并链接 tcmalloc
库:
g++ -std=c++0x -g -o memory_leak memory_leak.cpp -ltcmalloc
4.3、运行程序
[root@36eab106d3bf gperftools-test]# env HEAPCHECK=normal ./memory_leak
WARNING: Perftools heap leak checker is active -- Performance may suffer
memory leak test
Have memory regions w/o callers: might report false leaks
Leak check _main_ detected leaks of 4 bytes in 1 objects
The 1 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 4 bytes in 1 objects allocated from:@ 4008ff@ 400935@ 7fc9f81a6555@ 400829If the preceding stack traces are not enough to find the leaks, try running THIS shell command:pprof ./memory_leak "/tmp/memory_leak.16582._main_-end.heap" --inuse_objects --lines --heapcheck --edgefraction=1e-10 --nodefraction=1e-10 --gvIf 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 m
Exiting with error code (instead of crashing) because of whole-program memory leaks
这里虽然检测出了内存泄露,但是并没有打印出调用栈的符号信息,根据提示,可能是 PPROF_PATH 环境变量没有正确设置。按照如下方式设置环境变量:
# echo $PPROF_PATH
# which pprof
/usr/local/bin/pprof
# export PPROF_PATH=/usr/local/bin/pprof
再次运行,可以看到已经打印出了内存泄露的栈信息:
[root@36eab106d3bf gperftools-test]# env HEAPPROFILE=./heap_perf HEAPCHECK=normal ./memory_leak
WARNING: Perftools heap leak checker is active -- Performance may suffer
memory leak test
Have memory regions w/o callers: might report false leaks
Leak check _main_ detected leaks of 4 bytes in 1 objects
The 1 largest leaks:
Using local file ./memory_leak.
Leak of 4 bytes in 1 objects allocated from:@ 4008ff func@ 400935 main@ 7f39d94db555 __libc_start_main@ 400829 _startIf the preceding stack traces are not enough to find the leaks, try running THIS shell command:pprof ./memory_leak "/tmp/memory_leak.16586._main_-end.heap" --inuse_objects --lines --heapcheck --edgefraction=1e-10 --nodefraction=1e-10 --gvIf 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 m
Exiting with error code (instead of crashing) because of whole-program memory leaks
但是这个信息展现方式并没有直接指出问题产生的行数。我们可以使用其提示的指令,调用可视化工具
pprof ./memory_leak ./heap_perf.0001.heap --inuse_objects --lines --heapcheck --edgefraction=1e-10 --nodefraction=1e-10 --gv
4.4、错误解决办法
4.4.1、gv错误
gv: Unable to open the display.
说明无法打开显示器,也就是说,–gv选项,需要在带图形界面的系统上使用,我们可以用–svg选项,将生成svg,然后在浏览器打开,命令:
pprof ./memory_leak ./heap_perf.0001.heap --inuse_objects --lines --heapcheck --edgefraction=1e-10 --nodefraction=1e-10 --svg > svg.svg
4.4.2、addr2line错误
/usr/bin/addr2line: DWARF error: could not find variable specification at offset 5fc4
/usr/bin/addr2line: DWARF error: could not find variable specification at offset 60c0
/usr/bin/addr2line: DWARF error: could not find variable specification at offset 6137
可以在编译的时候将-g换成-pg,例如
g++ -std=c++0x -pg -o memory_leak memory_leak.cpp -ltcmalloc
4.4.3、dot错误
sh: dot: command not found
安装graphviz
yum install graphviz
五、线上内存泄露检测
5.1、demo
文件夹中的四个文件
BUILD
WORKSPACE
memory_leak.cpp
start.sh
.bazelrc
BUILD文件
cc_binary(name = "memory_leak",linkopts = ["-ltcmalloc",],srcs = ["memory_leak.cpp"],
)
-ltcmalloc必须链接上
memory_leak.cpp文件
#include <iostream>int func() {int *p = new int(10);return 0;
}int main() {std::cout << "memory leak test" << std::endl;return func();
}
start.sh文件
export PPROF_PATH=/usr/local/bin/pprof
nohup env HEAPPROFILE=./heap_perf HEAPCHECK=normal ./memory_leak 2>&1 | tee "./log.log">> "./log.log" 2>&1 &
.bazelrc文件
build --copt=-O2
build --copt=-g
build --cxxopt=-std=c++17
WORKSPACE文件为空文件
编译命令
bazel build :memory_leak
如果在生成svg过程中,出现4.4.2错误,将.bazelrc文件中的-g换成-pg
六、参考
https://fuchencong.com/2021/04/22/develop-tools-1/
https://cloud.tencent.com/developer/article/1383795
这篇关于使用 gperftools 检测内存泄露的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!