使用heap profile工具解决内存占用过高和内存泄露问题


前几天运行程序,发现程序运行了一段时间之后,内存一直在涨,导致最终因为OOM的原因被系统杀死。后面经过同事推荐,分别使用了dump内存的方法和借助谷歌的gperftools提供的heap-profiler工具来分析问题。最终还是后者的工具让我一下子找到了出现问题的函数。

heap profiler工具需要依赖tcmalloc的库。因此我们先要准备好环境。

准备环境

tcmalloc是谷歌的gperftools中的一个内存管理库,包含在gperftools中,i因此,我们安装该工具即可、

 yum install gperftools  
执行完之后,tcmalloc的库就安装好了,后面需要用到的pprof也安装好了。

因为在将函数调用堆栈生成图表时,需要用到graph,所以还需要安装graphviz和gv

 yum install graphviz gv

编译和启动程序

首先,我们编写一段明显带有内存泄露的代码,然后编译成可执行文件

memory_leak_demo.cpp

#include <iostream>
#include <stdio.h>
#include <unistd.h>
#include <stdint.h>
using namespace std;

static const int MAX_LEN = 32;

struct TestA {
    int num;
    uint64_t offset;
    char name[MAX_LEN];
};

TestA *create_testA(int i) {
    TestA *temp = new TestA();
    temp->num = i;
    temp->offset = i << 1;
    snprintf(temp->name, MAX_LEN, "test_%d", i); 
    
    return temp;
}

int main() {
    int i = 0;

    while(true) {
        ++i;
        TestA *testa = create_testA(i);
    }   

    return 0;
}
编译的时候,可以直接链接 tcmalloc的库,这样就能使用内存分析工具
g++ -g -o memory_leak_demo memory_leak_demo.cpp -ltcmalloc

接下来,就是在程序启动的时候,使用heap profile的工具了。

 HEAPPROFILE=test   ./memory_leak_demo
启动之后,如果内存有变化,我们会看到以下的打印:
Starting tracking the heap
Dumping heap profile to test.0001.heap (100 MB currently in use)
Dumping heap profile to test.0002.heap (200 MB currently in use)

如果是编译的时候,没有链接tcmalloc库的,也不用慌。我们可以在启动程序的时候,通过PRE_LOAD的方式加载tcmalloc的库:

HEAPPROFILE=test  LD_PRELOAD="/usr/lib64/libtcmalloc.so" ./memory_leak_demo

使用pprof分析程序运行过程中产生的heap文件

pprof工具提供了多种格式的输出,在这里,我们使用带图形显示的pdf格式

pprof --pdf memory_leak_demo test.0004.heap >1.pdf
pprof的使用方法如下:
/usr/bin/pprof [options] <program> <profiles>
[options]选项,包括通用的输出类型,有以下选择:
Output type:
   --text              Generate text report
   --stacks            Generate stack traces similar to the heap profiler (requires --text)
   --callgrind         Generate callgrind format to stdout
   --gv                Generate Postscript and display
   --evince            Generate PDF and display
   --web               Generate SVG and display
   --list=<regexp>     Generate source listing of matching routines
   --disasm=<regexp>   Generate disassembly of matching routines
   --symbols           Print demangled symbol names found at given addresses
   --dot               Generate DOT file to stdout
   --ps                Generate Postscript to stdout
   --pdf               Generate PDF to stdout
   --svg               Generate SVG to stdout
   --gif               Generate GIF to stdout
   --raw               Generate symbolized pprof data (useful with remote fetch)
   --collapsed         Generate collapsed stacks for building flame graphs
                       (see http://www.brendangregg.com/flamegraphs.html)
这里,我们选择了pdf,然后注意到,这里是将输出直接打到终端的,所以我们要进行重定向到文件中,才能看。在示例中,我们生成了1.pdf文件,现在我们看看文件里面的内容是什么样的。

图中,线的粗细代表所占内存的多少,因为示例程序中,只有一个地方会new内存,所以只有一条线。

从图中可以看出,main函数,直接占用内存0M,占总内存的0%,但是间接占用了内存400M,占总内存的100%,而create_testA函数,占用内存400M,占总内存的100%。通过这种各函数间内存的占用情况,可以很清晰地知道内存的使用情况,有利于我们分析内存泄露问题。

    而在实际的工作中,我也通过这个工具,解决了在内存池的结构体中,使用STL库带来的内存泄露问题。亲测非常有效

评论

发表评论