关于 堆外内存的组成可以看上一篇文章 JVM 堆外内存泄漏分析(一)
NMT(Native Memory Tracking)是 HotSpot JVM 引入的跟踪 JVM 内部使用的本地内存的一个特性,可以通过 jcmd 工具访问 NMT 数据。NMT 目前不支持跟踪第三方本地代码的内存分配和 JDK 类库。
NMT 不跟踪非 JVM 代码的内存分配,本地代码里的内存泄露需要使用操作系统支持的工具来定位。
启用 NMT 会带来 5-10% 的性能损失。NMT 的内存使用率情况需要添加两个机器字 word 到 malloc 内存的 malloc 头里。NMT 内存使用率也被 NMT 跟踪。
启动命令: -XX:NativeMemoryTracking=[off | summary | detail]
。
off:NMT 默认是关闭的;
summary:只收集子系统的内存使用的总计数据;
detail:收集每个调用点的内存使用数据。
命令: 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
-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
组合的输出示例:
内存泄漏一般都不是突然猛增到极限,而是一个慢慢增长的过程,这样我们可以选取两个时间的内存来进行对比,看新增的内存里到底存的是什么内容。
gdb 导出指定地址范围的内存块的内容 :
sudo gdb --batch --pid 2754 -ex "dump memory a.dump 0x7f1023ff6000 0x7f1023ff6000+268435456"
然后用 hexdump -C /tmp/memory.bin
或 strings /tmp/memory.bin |less
查看内存块里的内容。
如果内存块里存的是文本信息,这样是可以看出存的是什么内容的,如果是二进制的内存,就没法看了。
先生成 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-file jmap <options> exec core-file
jhsdb: hsdb 是 HotSpot debugger 的简称,是 JDK9 开始引入的一个调试工具。
$ jhsdb clhsdb command line debugger hsdb ui debugger debugd --help to get more information jstack --help to get more information jmap --help to get more information jinfo --help to get more information jsnap --help to get more information
在 openJDK 11 提取实操失败了,生成堆 dump 时会出现一些内存地址读取失败。
用 jstack 从 core dump 提取信息:
sudo jstack -J-d64 /usr/bin/java core.2316 jhsdb jstack --exe /usr/bin/java --core core.2316
-d64
表示64位的系统,这两个也是网上找的,没有实际成功。
欢迎关注我的微信公众号: coderbee笔记 ,可以更及时回复你的讨论。