宝剑锋从磨砺出,梅花香自苦寒来。这篇文章主要讲述一次完整的JVM NativeMemoryTracking 堆外内存泄露分析相关的知识,希望能为你提供帮助。
Native Memory Tracking (NMT) 是Hotspot VM用来分析VM内部内存使用情况的一个功能。我们可以利用jcmd(jdk自带)这个工具来访问NMT的数据。
NMT介绍
工欲善其事必先利其器,我们先把相关需要的配置和工具介绍清楚,再通过例子来看看具体如何使用NMT。打开NMT
NMT必须先通过VM启动参数中打开,不过要注意的是,打开NMT会带来5%-10%的性能损耗。
-XX:NativeMemoryTracking=[off | summary | detail]
# off: 默认关闭
# summary: 只统计各个分类的内存使用情况.
# detail: Collect memory usage by individual call sites.
jcmd查看NMT报告
通过jcmd查看NMT报告以及查看对比情况。
jcmd < pid> VM.native_memory [summary | detail | baseline | summary.diff | detail.diff | shutdown] [scale= KB | MB | GB]
# summary: 分类内存使用情况.
# detail: 详细内存使用情况,除了summary信息之外还包含了虚拟内存使用情况。
# baseline: 创建内存使用快照,方便和后面做对比
# summary.diff: 和上一次baseline的summary对比
# detail.diff: 和上一次baseline的detail对比
# shutdown: 关闭NMT
VM退出时打印NMT
可以通过下面VM参数在JVM退出时打印NMT报告。
-XX:+UnlockDiagnosticVMOptions -XX:+PrintNMTStatistics
NMT实战症状
某个服务(C)在客户环境使用后发现其内存占用不断变大且远超Xmx指定的大小,导致整个系统因缺少内存造成其他服务无法启动。当时查看到其RSS大约为11G,-Xmx=6G而且heap利用率不到50%。
user@hostxxx> prstat -p 2780
PID USERNAMESIZERSSSTATEPRINICETIMECPUPROCESS/NLWP
2780 user11G11Gsleep59044:16:39 0.0%java/196
user@hostxxx> /opt/jdk1.8.0_40/bin/jstat -gcutil 2780
S0S1EOMCCSYGCYGCTFGCFGCTGCT
0.00 100.0090.6046.8098.0297.10113234049.74511225.3454275.090
分析
服务通过-Xmx=6G指定最大堆分配为6G,但实际RSS已达到11G,开始怀疑堆外内存是否有内存泄露。为了有更好详细的数据,就在本地重现这个问题,并且打开了NMT持续监控。
NMT的Report如下,重点关注每个分类下的commit大小,这个是实际使用的内存大小。
6739: #进程ID
Native Memory Tracking:
Total: reserved=8491110KB, committed=7220750KB
-Java Heap (reserved=6293504KB, committed=6291456KB)
(mmap: reserved=6293504KB, committed=6291456KB)
-Class (reserved=1107429KB, committed=66189KB)
(classes #11979)
(malloc=1509KB #18708)
(mmap: reserved=1105920KB, committed=64680KB)
-Thread (reserved=159383KB, committed=159383KB)
(thread #156)
(stack: reserved=158720KB, committed=158720KB)
(malloc=482KB #788)
(arena=182KB #310)
-Code (reserved=255862KB, committed=41078KB)
(malloc=6262KB #9319)
(mmap: reserved=249600KB, committed=34816KB)
-GC (reserved=449225KB, committed=449225KB)
(malloc=166601KB #1714646)
(mmap: reserved=282624KB, committed=282624KB)
-Compiler (reserved=395KB, committed=395KB)
(malloc=265KB #856)
(arena=131KB #3)
-Internal (reserved=146041KB, committed=146041KB)
(malloc=132185KB #276370)
(mmap: reserved=13856KB, committed=13856KB)
-Symbol (reserved=31487KB, committed=31487KB)
(malloc=29209KB #91080)
(arena=2278KB #1)
-Native Memory Tracking (reserved=33212KB, committed=33212KB)
(malloc=168KB #2575)
(tracking overhead=33044KB)
-Arena Chunk (reserved=2284KB, committed=2284KB)
(malloc=2284KB)
-Unknown (reserved=12288KB, committed=0KB)
(mmap: reserved=12288KB, committed=0KB)
Virtual memory map:
......
并且在服务器上通过cron job来定期抓取NMT的report保存下来做分析,而且同时也把其对应的RSS和PMAP都抓取了一份。
COLLECTOR_PID=`ps -ef|grep "ProcessName" | grep -v grep | awk {print $2}`
OUTDIR=/opt/chkmem
HOSTNAME=`hostname`
prstat -s rss 1 1 > ${OUTDIR}/${HOSTNAME}_coll_${COLLECTOR_PID}_prstat_`date +%Y%m%d_%H%M%S`.txt
/opt/jdk1.8.0_40/bin/jcmd ${COLLECTOR_PID} VM.native_memory detail > ${OUTDIR}/${HOSTNAME}_coll_${COLLECTOR_PID}_nmd_`date +%Y%m%d_%H%M%S`.txt
pmap -x ${COLLECTOR_PID} > ${OUTDIR}/${HOSTNAME}_coll_${COLLECTOR_PID}_pmap_`date +%Y%m%d_%H%M%S`.txt
分析发现NMT中的Symbol域持续增大,从最开始的几十兆已经增加到了2G左右,而且整个jvm的内存使用量也在持续增加。见下图:
【一次完整的JVM NativeMemoryTracking 堆外内存泄露分析】验证后发现问题和JDK8的bug非常类似,测试后也证实确实如此,最后通过升级JDK解决了这个问题。 导致这个问题的组件是Jackson Streaming API 中的 JsonFactory.Feature.INTERN_FIELD_NAMES 引起的,由于项目中需要大量解析动态json文件,并且key都被intern到JVM native 内存无法释放导致内存泄露。
推荐阅读
- 从冰箱装大象到女娲造人,带你彻底吃透Python面向对象编程
- rpm 命令 – RPM软件包管理器
- #yyds干货盘点# MyBatis-Plus——代码生成器(3.5.1+版本)
- WordPress主题中的Bootstrap无法在浏览器中渲染
- bootstrap字形在WordPress主题中不起作用
- 缩放时的bootstrap网格对齐问题
- 使用the_post_thumbnail()图像引导4张卡片可以通过wordpress拉伸以适合自己()
- Avada在菜单项之间添加空格
- 输入凭据后空白wp-login.php页面