1. NMT
NMT(Native Memory Tracking)是 HotSpot JVM 引入的跟踪 JVM 内部使用的本地内存的一个特性,可以通过 jcmd 工具访问 NMT 数据。NMT 目前不支持跟踪第三方本地代码的内存分配和 JDK 类库。
NMT 不跟踪非 JVM 代码的内存分配,本地代码里的内存泄露需要使用操作系统支持的工具来定位。
1.1 开启 NMT
启用 NMT 会带来 5-10% 的性能损失。NMT 的内存使用率情况需要添加两个机器字 word 到 malloc 内存的 malloc 头里。NMT 内存使用率也被 NMT 跟踪。
启动命令:-XX:NativeMemoryTracking=[off | summary | detail]
。
off:NMT 默认是关闭的;
summary:只收集子系统的内存使用的总计数据;
detail:收集每个调用点的内存使用数据。
1.2 jcmd 访问 NMT 数据
命令:jcmd <pid> VM.native_memory [summary | detail | baseline | summary.diff | detail.diff | shutdown] [scale= KB | MB | GB]
| option | desc |
|---|---|
| summary | 按分类打印汇总数据 |
| detail | 按分类打印汇总数据 打印虚拟内存映射 按调用点打印内存使用汇总 |
| baseling | 创建内存使用快照用于后续对比 |
| summary.diff | 基于最新的基线打印一份汇总报告 |
| detail.diff | 基于最新的基线打印一份明细报告 |
| shutdown | 关闭 NMT |
在 NMT 启用的情况下,可以通过下面的命令行选项在 JVM 退出时输出最后的内存使用数据:-XX:+UnlockDiagnosticVMOptions -XX:+PrintNMTStatistics
1.3 使用 NMT 检测内存泄露
开启 NMT,用命令:
-XX:NativeMemoryTracking=summary|detail创建基线,用命令:
jcmd <pid> VM.native_memory baseline观察内存变化:
jcmd <pid> VM.native_memory detail.diff
NMT 数据输出解释:
reserved memory:预订内存,不表示实际使用,最主要的是申请了一批连续的地址空间;(OS 角度)
commited memory:实际使用的。(OS 角度)
对于 64 位的系统,地址空间几乎是无限的,但越来越多的内存 committed,可能会导致 swapping 或本地 OOM 。
以下示例来自 https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/tooldescr007.html 。
-XX:NativeMemoryTracking=summary
与 jcmd <pid> VM.native_memory summary
输出:
Total: reserved=664192KB, committed=253120KB <--- total memory tracked by Native Memory Tracking- Java Heap (reserved=516096KB, committed=204800KB) <--- Java Heap(mmap: reserved=516096KB, committed=204800KB)- Class (reserved=6568KB, committed=4140KB) <--- class metadata(classes #665) <--- number of loaded classes(malloc=424KB, #1000) <--- malloc'd memory, #number of malloc(mmap: reserved=6144KB, committed=3716KB)- Thread (reserved=6868KB, committed=6868KB)(thread #15) <--- number of threads(stack: reserved=6780KB, committed=6780KB) <--- memory used by thread stacks(malloc=27KB, #66)(arena=61KB, #30) <--- resource and handle areas- Code (reserved=102414KB, committed=6314KB)(malloc=2574KB, #74316)(mmap: reserved=99840KB, committed=3740KB)- GC (reserved=26154KB, committed=24938KB)(malloc=486KB, #110)(mmap: reserved=25668KB, committed=24452KB)- Compiler (reserved=106KB, committed=106KB)(malloc=7KB, #90)(arena=99KB, #3)- Internal (reserved=586KB, committed=554KB)(malloc=554KB, #1677)(mmap: reserved=32KB, committed=0KB)- Symbol (reserved=906KB, committed=906KB)(malloc=514KB, #2736)(arena=392KB, #1)- Memory Tracking (reserved=3184KB, committed=3184KB)(malloc=3184KB, #300)- Pooled Free Chunks (reserved=1276KB, committed=1276KB)(malloc=1276KB)- Unknown (reserved=33KB, committed=33KB)(arena=33KB, #1)
-XX:NativeMemoryTracking=detail
与 jcmd <pid> VM.native_memory detail
组合的输出示例:
2. 系统层面的分析思路
内存泄漏一般都不是突然猛增到极限,而是一个慢慢增长的过程,这样我们可以选取两个时间的内存来进行对比,看新增的内存里到底存的是什么内容。
2.0 gdb 方式
gdb 导出指定地址范围的内存块的内容 :
sudo gdb --batch --pid 2754 -ex "dump memory a.dump 0x7f1023ff6000 0x7f1023ff6000+268435456"
然后用 hexdump -C /tmp/memory.bin
或 strings /tmp/memory.bin |less
查看内存块里的内容。
如果内存块里存的是文本信息,这样是可以看出存的是什么内容的,如果是二进制的内存,就没法看了。
2.1 jstack/jmap + core dump
先生成 core dump,然后从 core dump 里提取线程栈、JVM 堆 dump,JDK 8 下提取成功:
# 使用 gcore 命令生成 core dump,gcore 1791# 使用 jstack 从 core dump 文件提取线程信息~/zuul-jdk/zulu8.40.0.25-ca-jdk8.0.222-linux_x64/bin/jstack ~/zuul-jdk/zulu8.40.0.25-ca-jdk8.0.222-linux_x64/bin/java core.1791# 使用 jmap 从 core dump 文件提取 JVM 堆 dump~/zuul-jdk/zulu8.40.0.25-ca-jdk8.0.222-linux_x64/bin/jmap -dump:format=b,file=zuul.jmap.hprof ~/zuul-jdk/zulu8.40.0.25-ca-jdk8.0.222-linux_x64/bin/java core.1791# jstack、jmap 从 core dump 里提取信息的方式,exec 一般是指向可执行命令 java 的路径jstack exec core-filejmap <options> exec core-file
2.2 jhsdb
jhsdb:hsdb 是 HotSpot debugger 的简称,是 JDK9 开始引入的一个调试工具。
$ jhsdbclhsdb command line debuggerhsdb ui debuggerdebugd --help to get more informationjstack --help to get more informationjmap --help to get more informationjinfo --help to get more informationjsnap --help to get more information
在 openJDK 11 提取实操失败了,生成堆 dump 时会出现一些内存地址读取失败。
用 jstack 从 core dump 提取信息:
sudo jstack -J-d64 /usr/bin/java core.2316jhsdb jstack --exe /usr/bin/java --core core.2316
-d64
表示64位的系统,这两个也是网上找的,没有实际成功。
3. 参考资料
Java堆外内存增长问题排查Case https://coldwalker.com/2018/08//troubleshooter_native_memory_increase/
记一次java native memory增长问题的排查 http://blog.2baxb.me/archives/918
https://www.atlassian.com/blog/archives/so-you-want-your-jvms-heap
欢迎关注我的微信公众号: coderbee笔记,可以更及时回复你的讨论。





