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
参照: