一、JVM内存跟踪

354 阅读4分钟

Java7U40之后JDK提供了Native Memory Tracking工具,跟踪JVM内部的内存使用,并可以通过jcmd命令来访问。不过要注意的是NMT是通过在JVM代码中添加跟踪点的方式实现内存跟踪的,因此NMT不能跟踪第三方Native库的内存使用

一、 如何开启NMT

NMT功能默认关闭,可以通过以下方式开启:

-XX:NativeMemoryTracking=[off | summary | detail]
配置项说明
off默认配置
summary只收集汇总信息
detail收集每次调用的信息

注意,根据Java官方文档,开启NMT会有5%-10%的性能损耗;

如果想JVM退出时打印退出时的内存使用情况,可以通过如下配置项:

二、 访问NMT数据

JDK提供了jcmd命令来访问NMT数据:

jcmd <pid> VM.native_memory [summary | detail | baseline | summary.diff | detail.diff | shutdown] [scale= KB | MB | GB]
配置项说明
summary只打印打印按分类汇总的内存用法
detail打印按分类汇总的内存用法、virtual memory map和每次内存分配调用
baseline创建内存快照,以比较不同时间的内存差异
summary.diff打印自上次baseline到现在的内存差异,显示汇总信息
detail.diff打印自上次baseline到现在的内存差异, 显示详细信息
shutdown关闭NMT功能
scale指定内存单位,默认为KB

三、示例

启动时,加上如下的jvm参数:

-XX:MaxDirectMemorySize=10m -XX:NativeMemoryTracking=detail  -XX:+UnlockDiagnosticVMOptions -XX:+PrintNMTStatistics

分析

服务通过-Xmx=6G指定最大堆分配为6G,但实际RSS已达到11G,开始怀疑堆外内存是否有内存泄露。为了有更好详细的数据,就在本地重现这个问题,并且打开了NMT持续监控。

NMT的Report如下,重点关注每个分类下的commit大小,这个是实际使用的内存大小。

命令: jcmd 6739 VM.native_memory summary scale=MB

D:\workspace\study\target>jcmd 11532 VM.native_memory summary
11532:

Native Memory Tracking:

Total: reserved=1361449KB, committed=62425KB
-                 Java Heap (reserved=20480KB, committed=20480KB)
                            (mmap: reserved=20480KB, committed=20480KB)

-                     Class (reserved=1062013KB, committed=10109KB)
                            (classes #421)
                            (malloc=5245KB #154)
                            (mmap: reserved=1056768KB, committed=4864KB)

-                    Thread (reserved=15423KB, committed=15423KB)
                            (thread #16)
                            (stack: reserved=15360KB, committed=15360KB)
                            (malloc=45KB #82)
                            (arena=18KB #30)

-                      Code (reserved=249636KB, committed=2572KB)
                            (malloc=36KB #314)
                            (mmap: reserved=249600KB, committed=2536KB)

-                        GC (reserved=6667KB, committed=6611KB)
                            (malloc=5771KB #117)
                            (mmap: reserved=896KB, committed=840KB)

-                  Compiler (reserved=132KB, committed=132KB)
                            (malloc=2KB #22)
                            (arena=131KB #3)

-                  Internal (reserved=5380KB, committed=5380KB)
                            (malloc=5316KB #1365)
                            (mmap: reserved=64KB, committed=64KB)

-                    Symbol (reserved=1495KB, committed=1495KB)
                            (malloc=943KB #111)
                            (arena=552KB #1)

-    Native Memory Tracking (reserved=38KB, committed=38KB)
                            (malloc=3KB #37)
                            (tracking overhead=35KB)

-               Arena Chunk (reserved=185KB, committed=185KB)
                            (malloc=185KB)

说明:

Total: reserved=1361449KB(保留内存), committed=62425KB(提交内存)
-                 Java Heap (reserved=20480KB(20MB,同参数设置一致), committed=20480KB)
                            (mmap: reserved=20480KB, committed=20480KB)
堆使用情况:
Java Heap (reserved=20480KB, committed=20480KB)
                            (mmap: reserved=20480KB, committed=20480KB)
保留内存为20MB与设置的相同。
-                     Class (reserved=1062013KB, committed=10109KB)
                            (classes #421)
                            (malloc=5245KB #154)
                            (mmap: reserved=1056768KB, committed=4864KB)
用于保存类的元数据的原生内存。classes #421是实际用于保存程序中的421个类而占用的内存相比,JVM保留的内存要更多。
-                    Thread (reserved=15423KB, committed=15423KB)
                            (thread #16)
                            (stack: reserved=15360KB, committed=15360KB)
                            (malloc=45KB #82)
                            (arena=18KB #30)
thread #16,表示16个线程,分配的总内存有15423KB,平均一个线程是1MB。
-                      Code (reserved=249636KB, committed=2572KB)
                            (malloc=36KB #314)
                            (mmap: reserved=249600KB, committed=2536KB)
JIT的代码缓存:根据上面的类数量来的,所以内存占用不是很多。
-                        GC (reserved=6667KB, committed=6611KB)
                            (malloc=5771KB #117)
                            (mmap: reserved=896KB, committed=840KB)
GC算法的处理锁使用的一些堆外空间。
-                  Compiler (reserved=132KB, committed=132KB)
                            (malloc=2KB #22)
                            (arena=131KB #3)
这个区域是供编译器自身操作使用的,这与生成的代码放在代码缓存中是不同的。
-                    Symbol (reserved=1495KB, committed=1495KB)
                            (malloc=943KB #111)
                            (arena=552KB #1)

保留字符串(Interned String)的引用与符号表引用放在这里。
-    Native Memory Tracking (reserved=38KB, committed=38KB)
                            (malloc=3KB #37)
                            (tracking overhead=35KB)
NMT本身的操作也需要一些空间。

四、NMT跟踪

NMT也支持跟踪内存分配随时间的变化情况。如果JVM在启动时启用了NMT,可以使用如下命令确定内存的基线使用情况:

jcmd process_id VM.native_memory baseline





参照: