jemalloc內(nèi)存泄漏分析

jemalloc 是一款內(nèi)存分配器, 除了可以提高分配內(nèi)存的效率之外。jemalloc還可以通過profiling機(jī)制來發(fā)現(xiàn)并定位內(nèi)存泄漏。

1. 安裝

官方提供的install教程:
https://github.com/jemalloc/jemalloc/blob/dev/INSTALL.md

git clone https://github.com/jemalloc/jemalloc .

./configure --enable-prof
make 
make install

make后在jemalloc/lib下面也可以看到這幾個(gè)庫:

?  lib git:(dev) ls
libjemalloc.a  libjemalloc_pic.a  libjemalloc.so  libjemalloc.so.2

make install生成的文件
里面有兩個(gè)重要文件:lib/libjemalloc.so.2; bin/jeprof

?  jemalloc git:(dev) make install
/usr/bin/install -c -d /usr/local/bin
'bin/jemalloc-config' -> '/usr/local/bin/jemalloc-config'
'bin/jemalloc.sh' -> '/usr/local/bin/jemalloc.sh'
'bin/jeprof' -> '/usr/local/bin/jeprof'
/usr/bin/install -c -d /usr/local/include/jemalloc
'include/jemalloc/jemalloc.h' -> '/usr/local/include/jemalloc/jemalloc.h'
/usr/bin/install -c -d /usr/local/lib
/usr/bin/install -c -v -m 755 lib/libjemalloc.so.2 /usr/local/lib
'lib/libjemalloc.so.2' -> '/usr/local/lib/libjemalloc.so.2'
ln -sf libjemalloc.so.2 /usr/local/lib/libjemalloc.so
/usr/bin/install -c -d /usr/local/lib
'lib/libjemalloc.a' -> '/usr/local/lib/libjemalloc.a'
'lib/libjemalloc_pic.a' -> '/usr/local/lib/libjemalloc_pic.a'
/usr/bin/install -c -d /usr/local/lib/pkgconfig
'jemalloc.pc' -> '/usr/local/lib/pkgconfig/jemalloc.pc'
Missing xsltproc.  doc/jemalloc.html not (re)built.
/usr/bin/install -c -d /usr/local/share/doc/jemalloc
'doc/jemalloc.html' -> '/usr/local/share/doc/jemalloc/jemalloc.html'
Missing xsltproc.  doc/jemalloc.3 not (re)built.
/usr/bin/install -c -d /usr/local/share/man/man3
'doc/jemalloc.3' -> '/usr/local/share/man/man3/jemalloc.3'

2. jemalloc的使用

查看jemalloc的wiki,上面介紹了appliacation幾種接入jemalloc的方法。第一種,可以使用LD_PRELOAD的方式,去指定jemalloc.so,根據(jù)鏈接的順序這個(gè)庫會替換掉原來的malloc(這種也是一個(gè)hook malloc的方案)。第二種,可以在編譯的時(shí)候指定 g++ xxxx -ljemalloc。具體可以查看這個(gè)wiki: https://github.com/jemalloc/jemalloc/wiki/Getting-Started

// 隨便寫個(gè)測試程序
g++ test1.cpp -ljemalloc    
./a.out 
./a.out: error while loading shared libraries: libjemalloc.so.2: cannot open shared object file: No such file or directory

# 遇到這種問題,可以切到root
echo /usr/local/lib >> /etc/ld.so.conf
ldconfig

# 當(dāng)然編譯的時(shí)候 指定rpath也可以
g++ test.cpp -ljemalloc -Wl,-rpath="/usr/local/lib" 
  • 如何開啟jemalloc的特性

Once you have jemalloc integrated into your application, you can use special features in a variety of ways:

  • Set the /etc/malloc.conf symlink or MALLOC_CONF environment variable to tune jemalloc, e.g.
export MALLOC_CONF="prof:true,lg_prof_sample:1,prof_accum:false,prof_prefix:jeprof.out"
  • Directly invoke jemalloc features in the application:
    Compile the following code as such:
cc ex_stats_print.c -o ex_stats_print -I`jemalloc-config --includedir` \
-L`jemalloc-config --libdir` -Wl,-rpath,`jemalloc-config --libdir` \
-ljemalloc `jemalloc-config --libs`

$ g++ test1.cpp
$ MALLOC_CONF=prof_leak:true,lg_prof_sample:0,prof_final:true LD_PRELOAD=/usr/local/lib/libjemalloc.so.2 ./a.out 

<jemalloc>: Leak approximation summary: ~54755672 bytes, ~1001 objects, >= 2 contexts
<jemalloc>: Run jeprof on dump output for leak detail

jeprof 查看內(nèi)存細(xì)節(jié)

jeprof
/usr/local/bin/jeprof a.out jeprof.2545447.0.f.heap 
Using local file a.out.
Using local file jeprof.2545447.0.f.heap.
Welcome to jeprof!  For help, type 'help'.
(jeprof) top
Total: 52.2 MB
    52.2 100.0% 100.0%     52.2 100.0% prof_backtrace_impl
     0.0   0.0% 100.0%     52.1  99.9% __libc_start_main
     0.0   0.0% 100.0%      0.1   0.1% _dl_init
     0.0   0.0% 100.0%      0.1   0.1% _dl_start_user
     0.0   0.0% 100.0%     52.1  99.9% _start
     0.0   0.0% 100.0%      0.1   0.1% call_init.part.0
     0.0   0.0% 100.0%     52.1  99.9% do_something
     0.0   0.0% 100.0%     52.2 100.0% je_prof_backtrace
     0.0   0.0% 100.0%     52.2 100.0% je_prof_tctx_create
     0.0   0.0% 100.0%     52.1  99.9% main
jeprof  --show_bytes --pdf a.out jeprof.2545447.0.f.heap > show.pdf

  • 每1MB dump一次

其中l(wèi)g_prof_interval:20中的20表示1MB(2^20),prof:true是打開profiling。運(yùn)行程序時(shí),每分配(大約)1MB就會dump產(chǎn)生一個(gè)文件。

$ export MALLOC_CONF="prof:true,lg_prof_interval:20"
$ LD_PRELOAD=/usr/local/jemalloc-5.1.0/lib/libjemalloc.so.2  ./a.out
$ ll

jeprof工具不僅可以查看詳細(xì)信息或者生成調(diào)用路徑圖(如上所示),還可以用來比較兩個(gè)dump(顯示增量部分):

# /usr/local/jemalloc-5.1.0/bin/jeprof a.out --base=jeprof.34584.2.i2.heap jeprof.34584.3.i3.heap
Using local file a.out.
Using local file jeprof.34584.3.i3.heap.
Welcome to jeprof!  For help, type 'help'.
(jeprof) top

其中--base指定比較的基礎(chǔ)。如上例,dump jeprof.34584.3.i3.heap的時(shí)候,分配了5.8 MB內(nèi)存,do_something和do_something_else分別占81.8%和18.2%;但和dump jeprof.34584.2.i2.heap的時(shí)候相比,多分配了1.6MB內(nèi)存,do_something和do_something_else分別占66.2%和33.8%。


  • 內(nèi)存使用達(dá)到新高時(shí),dump一次內(nèi)存
$ export MALLOC_CONF="prof:true,prof_gdump:true"
$ LD_PRELOAD=/usr/local/jemalloc-5.1.0/lib/libjemalloc.so.2  ./a.out
  • 手動在代碼中調(diào)用dump,記錄內(nèi)存現(xiàn)場
#include <stdio.h>
#include <stdlib.h>
#include <jemalloc/jemalloc.h>

void do_something(size_t i)
{
  // Leak some memory.
  malloc(i * 1024);
}

void do_something_else(size_t i)
{
  // Leak some memory.
  malloc(i * 4096);
}

int main(int argc, char **argv)
{
  size_t i, sz;

  for (i = 0; i < 80; i++)
  {
    do_something(i);
  }

  mallctl("prof.dump", NULL, NULL, NULL, 0);

  for (i = 0; i < 40; i++)
  {
    do_something_else(i);
  }

  mallctl("prof.dump", NULL, NULL, NULL, 0);

  return (0);
}

// gcc -I/usr/local/jemalloc-5.1.0/include test.c -L/usr/local/jemalloc-5.1.0/lib -ljemalloc

  • 程序運(yùn)行穩(wěn)定之后,dump一次內(nèi)存
    程序啟動的時(shí)候,勢必要分配內(nèi)存,我們查找內(nèi)存泄漏的時(shí)候,往往更關(guān)注程序在穩(wěn)定狀態(tài)時(shí)的內(nèi)存分配:只要程序啟動完成之后內(nèi)存不再增長,就沒有嚴(yán)重的泄漏問題。所以,穩(wěn)定狀態(tài)的內(nèi)存profiling往往更有意義。設(shè)置MALLOC_CONF=prof_active:false,使得程序在啟動的時(shí)候profiling是disabled;程序啟動完成后,再通過mallctl(“prof.active”)來enable profiling;或者定時(shí)enable。
#include <stdio.h>
#include <stdlib.h>
#include <jemalloc/jemalloc.h>

void do_something(size_t i)
{
  // Leak some memory.
  malloc(i * 1024);
}

void do_something_else(size_t i)
{
  // Leak some memory.
  malloc(i * 4096);
}

int main(int argc, char **argv)
{
  size_t i, sz;

  //initialization ...

  for (i = 0; i < 80; i++)
  {
    do_something(i);
  }

  //enter into steady-state...

  bool active = true;
  mallctl("prof.active", NULL, NULL, &active, sizeof(bool));

  for (i = 0; i < 40; i++)
  {
    do_something_else(i);
  }

  mallctl("prof.dump", NULL, NULL, NULL, 0);

  return (0);
}
?  jemalloc_test export MALLOC_CONF="prof:true,prof_active:false,prof_prefix:jeprof.out"
?  jemalloc_test LD_PRELOAD=/usr/local/lib/libjemalloc.so.2 ./a.out

用jeprof查看,發(fā)現(xiàn)只有steady-state之后的內(nèi)存分配:

?  jemalloc_test jeprof a.out jeprof.out.2789991.0.m0.heap 
Using local file a.out.
Using local file jeprof.out.2789991.0.m0.heap.
Welcome to jeprof!  For help, type 'help'.
(jeprof) top
Total: 3.2 MB
     3.2 100.0% 100.0%      3.2 100.0% prof_backtrace_impl
     0.0   0.0% 100.0%      3.2 100.0% __libc_start_main
     0.0   0.0% 100.0%      3.2 100.0% _start
     0.0   0.0% 100.0%      3.2 100.0% do_something_else
     0.0   0.0% 100.0%      3.2 100.0% je_prof_backtrace
     0.0   0.0% 100.0%      3.2 100.0% je_prof_tctx_create
     0.0   0.0% 100.0%      3.2 100.0% main
     0.0   0.0% 100.0%      3.2 100.0% prof_alloc_prep

  • 定時(shí)profiling

通過jeprof a.out --base=prof.1 prof.2來比較這兩個(gè)dump,這可以突顯出穩(wěn)定狀態(tài)下程序的內(nèi)存分配行為。

bool active;

mallctl("prof.dump", NULL, NULL, NULL, 0);    //生成prof.1

active = true;
mallctl("prof.active", NULL, NULL, &active, sizeof(bool));

//sleep 30 seconds

active = false;
mallctl("prof.active", NULL, NULL, &active, sizeof(bool));

//sleep 30 seconds

mallctl("prof.dump", NULL, NULL, NULL, 0);   //生成prof.2

  • 實(shí)踐
  1. 為了方便直接在編譯的時(shí)候,指定jemalloc.so,這樣可以在程序運(yùn)行前LD_PRELOAD指定jemalloc。
  2. 在程序運(yùn)行的時(shí)候,執(zhí)行MALLOC_CONF環(huán)境變量。 這樣程序就直接可以運(yùn)行了,不需要在程序執(zhí)行前在設(shè)置各種環(huán)境變量了。
// 在服務(wù)器程序啟動穩(wěn)定后,設(shè)置環(huán)境變量,并且開啟內(nèi)存profile
setenv("MALLOC_CONF", "prof:true,prof_active:false,prof_prefix:jeprof.out", 1);
bool active = true;
mallctl("prof.active", NULL, NULL, &active, sizeof(bool));
  1. 周期性的dump內(nèi)存
mallctl("prof.dump", NULL, NULL, NULL, 0);
  1. 使用jeprof命令對比內(nèi)存
方式1:(不太推薦,只能看到部分調(diào)用鏈)
jeprof a.out jeprof.out.28954.1.m1.heap 
> top

方式2:
jeprof --show_bytes --pdf  a.out jeprof.out.28954.1.m1.heap > je.pdf
依賴:
yum -y install ghostscript
yum install graphviz


方式3: (可以在服務(wù)器啟動后 dump一份內(nèi)存作為基準(zhǔn),然后兩份dump文件進(jìn)行對比)
jeprof --dot a.out -base=jeprof.out.28954.0.m0.heap jeprof.out.28954.1.m1.heap 

執(zhí)行后會生成圖片的信息,粘貼后在這里查看:
http://www.webgraphviz.com/

參考:

https://github.com/jemalloc/jemalloc
https://github.com/jemalloc/jemalloc/wiki/Use-Case%3A-Heap-Profiling

https://zhuanlan.zhihu.com/p/462516856
https://www.yuanguohuo.com/2019/01/02/jemalloc-heap-profiling/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容