暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

JVM 堆外内存泄漏分析(二)

coderbee笔记 2019-09-16
1368


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]

optiondesc
summary按分类打印汇总数据
detail按分类打印汇总数据
打印虚拟内存映射
按调用点打印内存使用汇总
baseling创建内存使用快照用于后续对比
summary.diff基于最新的基线打印一份汇总报告
detail.diff基于最新的基线打印一份明细报告
shutdown关闭 NMT


在 NMT 启用的情况下,可以通过下面的命令行选项在 JVM 退出时输出最后的内存使用数据:
-XX:+UnlockDiagnosticVMOptions -XX:+PrintNMTStatistics


1.3 使用 NMT 检测内存泄露

  1. 开启 NMT,用命令:-XX:NativeMemoryTracking=summary|detail

  2. 创建基线,用命令:jcmd <pid> VM.native_memory baseline

  3. 观察内存变化: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-file
jmap <options> exec core-file
复制


2.2 jhsdb

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位的系统,这两个也是网上找的,没有实际成功。


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笔记,可以更及时回复你的讨论。


文章转载自coderbee笔记,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论