jvm內存診斷全集

jvm內存診斷一直是一個強需求,大家能馬上想到mat等工具,在診斷heap,metaspace,直接內存上是有奇效。但是介紹能解決jvm的一部分問題。 下面就來一套,全面診斷的方式。

利用nmt找出泄漏區域

java有NativeMemoryTracking幫助我們查看jvm帶來的內存分配問題,這個只能看jvm帶來的,如果是jni的調用申請的內存,那這個工具是沒有用的。那大家可能疑惑了,那這個工具也沒想象的那麼有用,java各種分區,堆的,非堆的,還有直接內存的值jmx都有,想排查是否是jvm帶來的似乎也可以做到。那NativeMemoryTracking的作用是什麼呢?

對比現有的工具

查看內存數據的工具其實很多,我們通過NativeMemoryTracking和他們進行一下比較。 ||jstat|NativeMemoryTracking|jmx| |-|-|-|-| |是否支持命令行觀察|支持|支持|不支持| |是否支持直接內存|不支持|支持|支持| |是否支持unsafe的分配|不支持|支持|不支持| 對比的最後一項,很多人可能就要提出疑問,直接內存不就是利用unsafe分配的嗎,那這裏怎麼還寫了jmx不支持呢。 直接內存的使用其實是DirectByteBuffer自己維護的內存計數。也就是這個直接內存的數據其實是java code自己維護出來的,只要跳過這些,直接拿他的底層實現方式unsafe.allocateMemory(size);那這個數據是不會被直接內存統計到的,但是nmt可以。 對比之下,我們可以瞭解nmt的功能和優勢,他提供了一種,非圖形化界面,而且可以和top配合,快速區分內存佔用的到底是誰的問題,如果nmt的數據沒有增長,但是top的res漲了說明大概率是jni的調用導致的,如果是jvm的內存增長,我們可以通過detail來查看。

上手使用nmt

我們就拿一個直接內存的demo測試。

public class Malloc {
    public static void main(String[] args) {
        new Thread(()->{
            int i=0;

            List s= new LinkedList();
            while (i<10){
                ByteBuffer buffer = ByteBuffer.allocateDirect(1024*1024*100);

                s.add(buffer);
                System.out.println("malloc");
                i++;
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }



        }).start();
        new Thread(()->{
            int i=0;
            while (i<10){
                byte[] bytes =new byte[1024*1024*10];

            }
        }).start();
    }
}

啓動nmt功能。

-XX:NativeMemoryTracking=detail

開啓baseline功能。

jcmd pid VM.native_memory baseline

開啓了baseline,我們可以更直觀的看到數據增長,打印出的數據會有+號。 我們先查看summary信息,先看看誰在增長

jcmd pid VM.native_memory summary.diff
                            (malloc=123KB #536 +1)
                            (mmap: reserved=249600KB, committed=2536KB)

-                        GC (reserved=165933KB, committed=155689KB)
                            (malloc=12689KB #138)
                            (mmap: reserved=153244KB, committed=143000KB)

-                  Compiler (reserved=133KB, committed=133KB)
                            (malloc=2KB #32)
                            (arena=131KB #7)

-                  Internal (reserved=1035599KB +307200KB, committed=1035599KB +307200KB)
                            (malloc=1035567KB +307200KB #2102 +3)
                            (mmap: reserved=32KB, committed=32KB)

目前測試使用的是java8,用unsafe申請的內存會在Internal中。 然後使用detail.diff來查看到底是誰導致的。

我們依據summary的分區的數據,找到我們關注的是Internal。然後基於這個再看diff

[0x000000010cf8638b] Unsafe_AllocateMemory+0x78
[0x0000000110d99667]
                             (malloc=1024000KB type=Internal +307200KB #10 +3)

這裏我們就只看type爲Internal的,可以看到這裏是unsafe分配的。

heap分析

通過上面unsafe的demo,我們可以檢測出直接內存或者直接調用unsafe導致的內存增長問題。

如果增長的是heap,metaspace,說是mat等內存分析工具可達的,那麼直接做heapdump做詳細的分析。這裏不做詳細介紹。

native泄漏

如果發現nmt沒有發現增長,但是res卻一直變大,問題主要來源於jni等native的調用。我們得依賴其餘工具來做分析,這裏推薦jemalloc。

準備環境

下載jemalloc下載鏈接 因爲是native的,所以我們要在自己用的機器上進行編譯。我使用的是ubuntu系統。 編譯時有很多編譯選項,可以參考項目的wiki裏。wiki 我們只需要加內存檢測的功能就好,所以只加一個參數。

./configure --enable-prof
make && make install

編譯成功後,我們可以看到庫位置的輸出

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

這裏的目錄可以記錄一下,知道我們需要的庫要從哪裏查找。

添加環境變量

export MALLOC_CONF="prof_leak:true,lg_prof_sample:0,prof_final:true"
export LD_PRELOAD="/usr/local/lib/libjemalloc.so"

lg_prof_sample後的數字是2的n次的意思,這裏需要根據自己的閾值進行設置。 這裏設置的是最後輸出結果,如果是需要按照大小輸出,需要更改配置。

export MALLOC_CONF="prof:true,lg_prof_interval:4"

這裏多大生成一個文件需要自己設置。

java Malloc

我們可以看到這樣的輸出字樣

<jemalloc>: Leak approximation summary: ~83261344 bytes, ~3275 objects, >= 1120 contexts
<jemalloc>: Run jeprof on "jeprof.17013.0.f.heap" for leak detail

查看分析結果

jeprof  /usr/bin/java jeprof.17013.0.f.heap 

可以用top命令查看最大

(jeprof) top
Total: 179.4 MB
   131.0  73.0%  73.0%    131.0  73.0% je_prof_backtrace
    48.0  26.8%  99.8%     79.0  44.0% SUNWprivate_1.1
     0.4   0.2% 100.0%      0.4   0.2% Java_java_util_zip_ZipFile_getZipMessage
     0.0   0.0% 100.0%      0.0   0.0% _dl_new_object
     0.0   0.0% 100.0%      0.0   0.0% allocate_dtv
     0.0   0.0% 100.0%      0.0   0.0% _dl_check_map_versions
     0.0   0.0% 100.0%      0.4   0.2% ZIP_Unlock
     0.0   0.0% 100.0%      0.0   0.0% Java_java_util_zip_Inflater_init
     0.0   0.0% 100.0%     48.0  26.8% _dlerror_run
     0.0   0.0% 100.0%      0.0   0.0% 0x0000000000400620

生成調用路徑

安裝依賴

sudo apt-get install  ghostscript graphviz

生成命令

jeprof --show_bytes --pdf /usr/bin/java jeprof.17013.0.f.heap  > my.pdf
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章