堆狀態分析的利器——gperftools的Heap Profiler

堆狀態分析的利器——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