堆状态分析的利器——gperftools的Heap Profiler

Posted breaksoftware

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了堆状态分析的利器——gperftools的Heap Profiler相关的知识,希望对你有一定的参考价值。

        在《内存泄漏分析的利器——gperftools的Heap Checker》一文中,我们介绍了如何使用gperftools分析内存泄漏。本文将介绍其另一个强大的工具——Heap Profiler去分析堆的变化过程。(转载请指明出于breaksoftware的csdn博客)

        我们使用类似于《堆状态分析的利器——valgraind的DHAT》中的测试代码作为例子。为了让Heap Profiler产生多份快照文件,我将申请的内存放大了很多

#include <stdlib.h>

void* create(unsigned int size) 
    return malloc(size);


void create_destory(unsigned int size) 
    void *p = create(size);
    free(p);


int main(void) 
    const int loop = 4;
    char* a[loop];
    unsigned int mega = 1024 * 1024;

    for (int i = 0; i < loop; i++) 
        const unsigned int create_size = 1024 * mega;
        create(create_size);

        const unsigned int malloc_size = 1024 * mega;
        a[i] = (char*)malloc(malloc_size);

        const unsigned int create_destory_size = mega;
        create_destory(create_destory_size);
    

    for (int i = 0; i < loop; i++) 
        free(a[i]);
    

    return 0;

        第19行每次申请1G的空间,且在整个程序周期中,都不会释放。

        第22行每次申请1G的空间,每个空间都将在第29行释放掉。

        第25行调用的create_destory方法,每次申请1M的空间,每次申请完就释放掉。

        为了方便起见,我们还是要链接tcmalloc库,并开启调试信息

g++ heap_profiler.cpp -ltcmalloc -g -o heap_profiler

        编译完后,使用如下指令开始分析。其中HEAPPROFILE表示的“生成快照文件的目录格式”。

HEAPPROFILE=/tmp/profile /home/fangliang/gperftools_test/heap_profiler/heap_profiler

        会得到输出结果

Starting tracking the heap
Dumping heap profile to /tmp/profile.0001.heap (1024 MB allocated cumulatively, 1024 MB currently in use)
Dumping heap profile to /tmp/profile.0002.heap (2048 MB allocated cumulatively, 2048 MB currently in use)
Dumping heap profile to /tmp/profile.0003.heap (3073 MB allocated cumulatively, 3072 MB currently in use)
Dumping heap profile to /tmp/profile.0004.heap (4097 MB allocated cumulatively, 4096 MB currently in use)
Dumping heap profile to /tmp/profile.0005.heap (5122 MB allocated cumulatively, 5120 MB currently in use)
Dumping heap profile to /tmp/profile.0006.heap (6146 MB allocated cumulatively, 6144 MB currently in use)
Dumping heap profile to /tmp/profile.0007.heap (7171 MB allocated cumulatively, 7168 MB currently in use)
Dumping heap profile to /tmp/profile.0008.heap (8195 MB allocated cumulatively, 8192 MB currently in use)
Dumping heap profile to /tmp/profile.0009.heap (Exiting, 4096 MB in use)

        第2到9行显示,每个快照都会增长1G的内存申请。第10行显示,释放了4G的内存,最终还有4G的内存没有被释放。这个分析结果和代码的逻辑是一致的:

        第19行和第22行每次都申请1G空间,一共执行了4次,故8G的在用内存使用量。

        第25行每次申请并释放了1M,故不会造成内存增长。

        第29行每次释放1G的空间,共执行4次,释放了4G空间。最终有4G的内存泄漏。

        我们先看下第一个快照的状态

pprof --text heap_profiler /tmp/profile.0001.heap

        此时我们使用的文本输出方式(--text)

Using local file heap_profiler.
Using local file /tmp/profile.0001.heap.
Total: 1024.0 MB
  1024.0 100.0% 100.0%   1024.0 100.0% create
     0.0   0.0% 100.0%   1024.0 100.0% __libc_start_main
     0.0   0.0% 100.0%   1024.0 100.0% _start
     0.0   0.0% 100.0%   1024.0 100.0% main

        第4到7行是调用堆栈,这段显示create方法申请了1G的空间,且该空间还是可用状态。

        再查看快照2

pprof --text heap_profiler /tmp/profile.0002.heap 

        其结果显示main函数和create函数各申请了1G的空间(第4~5行第1列),各占总未释放内存(2G)的50%(第4~5行第2列)。main函数中调用了create方法(第4~5行第3,4,5列显示出main中直接调用了create,因为main函数中直接和间接申请了2G的空间,其中1G是直接申请的)

Using local file heap_profiler.
Using local file /tmp/profile.0002.heap.
Total: 2048.0 MB
  1024.0  50.0%  50.0%   1024.0  50.0% create
  1024.0  50.0% 100.0%   2048.0 100.0% main
     0.0   0.0% 100.0%   2048.0 100.0% __libc_start_main
     0.0   0.0% 100.0%   2048.0 100.0% _start

        为了更方便解读这组信息,我们使用图形显示命令

pprof --gv heap_profiler /tmp/profile.0002.heap 

        显示结果如下

        上图中,main下面“1024.0(50.0%)”意思是main函数直接申请了1024M尚未释放的空间,占总未释放空间的50%。再下面一行“of 2048.0 (100.0%)”意思是main函数直接或者间接申请了2048M尚未释放的空间(这意味着它申请并释放了的空间不在该统计内)。create中的信息解读是类似的。

        如果只是单纯的看一个快照点,是比较难以发现问题。我们需要对比两个快照,比如我们对比1号和2号快照,看看1G内存的增长是什么导致的

pprof --gv heap_profiler --base=/tmp/profile.0001.heap  /tmp/profile.0002.heap 

        可以发现,快照1和快照2的变化是:main函数自身申请了1G的空间。

        我们再对比下8和9号快照

pprof --gv heap_profiler --base=/tmp/profile.0008.heap  /tmp/profile.0009.heap 

        上面显示:main函数内部释放了4G的空间(出现了负值)。这个就是第29行代码的执行结果,分析和代码逻辑一致。

        最后我们再看下最后一片快照

pprof --gv heap_profiler /tmp/profile.0009.heap

        create函数导致的4G内存泄漏就一目了然了。

        最后提一句,如果项目不能链接tcmalloc,则可以使用如下的指令去获取快照

LD_PRELOAD="/usr/local/lib/libtcmalloc.so" HEAPPROFILE=/tmp/profile /home/fangliang/gperftools_test/heap_profiler/heap_profiler

 

以上是关于堆状态分析的利器——gperftools的Heap Profiler的主要内容,如果未能解决你的问题,请参考以下文章

gperf heap profiler

golang中container/heap包源码分析

JVM:jmap heap 堆参数分析MinHeapFreeRatioMaxHeapFreeRatioMaxHeapSizeNewSizeMaxNewSize

JVM:jmap heap 堆参数分析MinHeapFreeRatioMaxHeapFreeRatioMaxHeapSizeNewSizeMaxNewSize

算法分析-堆排序 Heap Sort

工程师利器系列5---一次线上Jvm内存占用过高分析全纪录(Jmap篇)