【翻译】heap-checking using tcmalloc

前一阵子在项目中使用tcmalloc的heap-checker查找内存泄露的问题,今天翻译一下官方文档的一篇heap-checker相关的文章,由于时间有限,没有完全翻译完,只翻译了比较重要的部分,后续将会补全。

在Google,我们使用heap checker检查C++程序的内存泄露。使用heap checker分三步:将lib库链接到应用中;运行代码;分析输出结果。

链接tcmalloc

heap-checker是tcmalloc的一部分,为了在应用程序中安装heap checker,在程序链接阶段需要加入-ltcmalloc。但上面这种方法并不是唯一的方式,我们可以使用LD_PRELOAD宏在程序运行时加入tcmalloc:

% env LD_PRELOAD="/usr/lib/libtcmalloc.so"

上面这种方式并没有打开heap checking;仅仅将代码插入到程序中。因此,在开发阶段我们建议使用链接tcmalloc库的方法,这也是我们在Google使用的方法(然而,我们也可以使用环境变量开启profiler,因此通过链接tcmalloc库并不是必须的。)。需要注意的是为了使用heap checker必须使用tcmalloc内存分配库。

运行代码

注意:由于一些安全原因,对于setuid程序,heap profiler在使用的过程中并不会写文件。

对整个程序进行内存泄露检查

我们推荐使用heap-checker的方式是“全程序”模式。在这种模式下,heap-checker在main()函数开始之前跟踪内存分配,在程序介绍的时候再次检查。如果发现有内存泄露(任何已经分配的内存在程序结束的时候并没有对象引用它们),程序将会中断(通过exit(1))并且打印怎样跟踪内存泄露的信息(使用pprof)

heap-checker会记录每一次内存分配的调用栈,因此开启heap-checker会导致程序的内存增长并影响程序的性能。

下面介绍怎么开启全程序模式的内存泄露检查:

  1. 定义环境变量 HEAPCHECK 声明内存泄露检查的模式。例如下面的方式:

    % env HEAPCHECK=normal /usr/local/bin/my_binary_compiled_with_tcmalloc

没有其他的操作了。

需要注意的是heap-checker使用heap-profiling的架构,因此不可能同时运行heap-checker和heap-profiler。

内存检查的特性

如下是在使用全程序内存检查的合法变量:

  1. minimal
  2. normal
  3. strict
  4. draconian

“minimal”的模式开始内存泄露检查尽可能晚,意味着你可以在初始化例程存在一些内存泄露(在main()函数前),但并不会让heap-checker记录。如果你在全局初始化时候存在内存泄露,“minimal”模式就很适合。否则,你应该使用更加严格的模式。

“normal”模式跟踪存活对象(live objects)并报告其内存泄露信息(在程序结束时,无法通过存活对象找到的内存空间都是内存泄露)

“strict”模式与“normal”模式很相似,但“strict”模式会监控全局变量析构函数的内存泄露。例如,如果你有一个全局变量,在运行时申请了部分内存,在析构函数中忘记释放内存,在“strict”模式下将会被监控到,在“normal”模式下将不会。

“draconian”模式适合想清晰了解应用程序内存管理的情况,其希望heap-checker去帮助他们优化内存管理。在“draconian”模式,heap-checker并不会只检查存活对象,只要有内存泄露,其都会报告。

“normal”模式是最常用的模式。

as-is”是一个更加灵活的模式;它允许你自定义heap-checker的一些特性。“local”激活“显式heap-check指令”,但并不会开启任何全程序内存泄露检查。

用于调试内存泄露的小方法

工作原理

当一个HeapLeakChecker对象被构造的时候,它会在tmp文件夹输出一个名为.-beg.heap的记录内存使用信息的文件。当NoLeak()被调用的时候(对于全程序检查,其发生在程序结束),它将输出一个名为.-end.heap的文件(是自动诊断的,是argv[0]的内容)。heap-checker将会对比这两个文件。如果第二个文件使用了过多的内存使用量,NoLeaks()函数将会返回false。对于全程序,这种情况将会引起程序中断。所有情况下都将会打印如何处理dump文件的信息。