jvm内存诊断全集

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