十三、jdk命令之Java内存之本地内存分析神器:NMT 和 pmap
目录
一、jdk工具之jps(JVM Process Status Tools)命令使用
二、jdk命令之javah命令(C Header and Stub File Generator)
三、jdk工具之jstack(Java Stack Trace)
四、jdk工具之jstat命令(Java Virtual Machine Statistics Monitoring Tool)
四、jdk工具之jstat命令2(Java Virtual Machine Statistics Monitoring Tool)详解
五、jdk工具之jmap(java memory map)、 mat之四--结合mat对内存泄露的分析
六、jdk工具之jinfo命令(Java Configuration Info)
七、jdk工具之jconsole命令(Java Monitoring and Management Console)
八、jdk工具之JvisualVM、JvisualVM之二--Java程序性能分析工具Java VisualVM
九、jdk工具之jhat命令(Java Heap Analyse Tool)
十、jdk工具之Jdb命令(The Java Debugger)
十一、jdk命令之Jstatd命令(Java Statistics Monitoring Daemon)
十一、jdk命令之Jstatd命令(Java Statistics Monitoring Daemon)
十二、jdk工具之jcmd介绍(堆转储、堆分析、获取系统信息、查看堆外内存)
十三、jdk命令之Java内存之本地内存分析神器:NMT 和 pmap
一、概述
NMT是Native Memory Tracking的缩写,是Java7U40引入的HotSpot新特性。 pmap,众所周知,就是Linux上用来看进程地址空间的。
二、NMT
Java7U40之后JDK提供了Native Memory Tracking工具,跟踪JVM内部的内存使用,并可以通过jcmd命令来访问。不过要注意的是NMT是通过在JVM代码中添加跟踪点的方式实现内存跟踪的,因此NMT不能跟踪第三方Native库的内存使用。
2.1、如何开启NMT
NMT功能默认关闭,可以通过以下方式开启:
-XX:NativeMemoryTracking=[off | summary | detail]
| 配置项 | 说明 |
|---|---|
| off | 默认配置 |
| summary | 只收集汇总信息 |
| detail | 收集每次调用的信息 |
注意,根据Java官方文档,开启NMT会有5%-10%的性能损耗;
如果想JVM退出时打印退出时的内存使用情况,可以通过如下配置项:
-XX:+UnlockDiagnosticVMOptions -XX:+PrintNMTStatistics
2.2、访问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 |
2.3、示例
启动时,加上如下的jvm参数:
-XX:MaxDirectMemorySize=10m -XX:NativeMemoryTracking=detail -XX:+UnlockDiagnosticVMOptions -XX:+PrintNMTStatistics
分析
服务通过-Xmx=6G指定最大堆分配为6G,但实际RSS已达到11G,开始怀疑堆外内存是否有内存泄露。为了有更好详细的数据,就在本地重现这个问题,并且打开了NMT持续监控。
NMT的Report如下,重点关注每个分类下的commit大小,这个是实际使用的内存大小。
命令:[root@ip-172-29-206-104 applogs]# jcmd 6739 VM.native_memory summary scale=MB
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的内存使用量也在持续增加。见下图:

验证后发现问题和JDK8的一个bug https://bugs.java.com/view_bug.do?bug_id=8180048 非常类似,测试后也证实确实如此,最后通过升级JDK解决了这个问题。具体是那个组件命中了JDK的这个bug,会在下一篇文章中详细描述。
jcmd 14179 VM.native_memory detail
首先,你要在Java启动项中,加入启动项: -XX:NativeMemoryTracking=detail 然后,重新启动Java程序。执行如下命令: jcmd 14179 VM.native_memory detail 你会在标准输出得到类似下面的内容(本文去掉了许多与本文无关或者重复的信息):
14179: Native Memory Tracking: Total: reserved=653853KB, committed=439409KB
- Java Heap (reserved=262144KB, committed=262144KB)
(mmap: reserved=262144KB, committed=262144KB) - Class (reserved=82517KB, committed=81725KB)
(classes #17828)
(malloc=1317KB #26910)
(mmap: reserved=81200KB, committed=80408KB) - Thread (reserved=20559KB, committed=20559KB)
(thread #58)
(stack: reserved=20388KB, committed=20388KB)
(malloc=102KB #292)
(arena=69KB #114) - Code (reserved=255309KB, committed=41657KB)
(malloc=5709KB #11730)
(mmap: reserved=249600KB, committed=35948KB) - GC (reserved=1658KB, committed=1658KB)
(malloc=798KB #676)
(mmap: reserved=860KB, committed=860KB) - Compiler (reserved=130KB, committed=130KB)
(malloc=31KB #357)
(arena=99KB #3) - Internal (reserved=5039KB, committed=5039KB)
(malloc=5007KB #20850)
(mmap: reserved=32KB, committed=32KB) - Symbol (reserved=18402KB, committed=18402KB)
(malloc=14972KB #221052)
(arena=3430KB #1) - Native Memory Tracking (reserved=2269KB, committed=2269KB)
(malloc=53KB #1597)
(tracking overhead=2216KB) - Arena Chunk (reserved=187KB, committed=187KB)
(malloc=187KB) - Unknown (reserved=5640KB, committed=5640KB)
(mmap: reserved=5640KB, committed=5640KB)
. . .
Virtual memory map: [0xceb00000 - 0xcec00000] reserved 1024KB for Class from
[0xced00000 - 0xcee00000] reserved 1024KB for Class from
. . .
[0xcf85e000 - 0xcf8af000] reserved and committed 324KB for Thread Stack from
[0xd4eaf000 - 0xd4f00000] reserved and committed 324KB for Thread Stack from
[0xf687866e] Thread::record_stack_base_and_size()+0x1be
[0xf68818bf] JavaThread::run()+0x2f
[0xf67541f9] java_start(Thread*)+0x119
[0xf7606395] start_thread+0xd5
[0xd5a00000 - 0xe5a00000] reserved 262144KB for Java Heap from
. . .
[0xe5e00000 - 0xf4e00000] reserved 245760KB for Code from
[0xf737f000 - 0xf7400000] reserved 516KB for GC from
[0xf745d000 - 0xf747d000] reserved 128KB for Unknown from
[0xf7700000 - 0xf7751000] reserved and committed 324KB for Thread Stack from
[0xf7762000 - 0xf776a000] reserved and committed 32KB for Internal from
上面的输出也就两大部分:Total和Virtual Memory Map. Total部分就是Java进程所使用的本地内存大小的一个分布: Heap用了多少,所有的Class用了多少。其中,最重要的一个就是Heap大小,此处它的Reserved值为262144KB, 其实也就是256MB, 因为该Java启动参数最大堆设为了256M:-Xmx256M。 Virtual Memory Map部分就是细节了,也就是Java进程的地址空间的每一段是用来干什么的,大小是多少。这些进程空间段按照用途分可以分为以下几种:
Reserved for Class (总共有76段)
例如:[0xceb00000 - 0xcec00000] reserved 1024KB for Class from
[0xced00000 - 0xcee00000] reserved 1024KB for Class from
大部分的为Class分配的进程空间都是1024KB的。
Reserved for Heap ( 总共只有1段)
例如:[0xd5a00000 - 0xe5a00000] reserved 262144KB for Java Heap from
简单演算一下:0xe5a00000-0xd5a00000=0x10000000=pow(2, 28)。很明显2的28方个比特就是256MB.
Reserved for Internal (总共只有1段)
例如:[0xf7762000 - 0xf776a000] reserved and committed 32KB for Internal from
Reserved for Thread Stack(总共有57段)
例如:[0xcf85e000 - 0xcf8af000] reserved and committed 324KB for Thread Stack from
从输出看,大部分的 Stack的地址空间都是324KB的,还有不少部分是516KB的。
Reserved for Code( 总共有2段 )
例如:[0xe5e00000 - 0xf4e00000] reserved 245760KB for Code from
这个地方用了好大的进程空间。后面,我们会在pmap的输出中找到它。它用了很大的Virtual Address Space, 但是RSS却相对比较小。
Reserved for Unknown( 总共有4 段)
例如: [0xf745d000 - 0xf747d000] reserved 128KB for Unknown from
Reserved for GC (总共有2段)
例如: [0xf737f000 - 0xf7400000] reserved 516KB for GC from
pmap的输出
使用命令行: pmap -p PID, 我们就可以得到对应进程的VSS&RSS信息。
pmap输出的中,我们把其中我们比较关心的部分列在下面:
START SIZE RSS PSS DIRTY SWAP PERM MAPPING
0000000008048000 4K 4K 4K 0K 0K r-xp /usr/java/jre1.8.0_65/bin/java
0000000008049000 4K 4K 4K 4K 0K rw-p /usr/java/jre1.8.0_65/bin/java
000000000804a000 74348K 71052K 71052K 71052K 0K rw-p [heap]
…
00000000ced00000 1024K 976K 976K 976K 0K rw-p [anon]
…
00000000d4eaf000 12K 0K 0K 0K 0K ---p [anon]
00000000d4eb2000 312K 28K 28K 28K 0K rwxp [stack:21151]
00000000d4f00000 1024K 1024K 1024K 1024K 0K rw-p [anon]
00000000d5000000 32K 32K 32K 0K 0K r-xp /usr/java/jre1.8.0_65/jre/lib/i386/libmanagement.so
00000000d5008000 4K 4K 4K 4K 0K rw-p /usr/java/jre1.8.0_65/jre/lib/i386/libmanagement.so
00000000d500d000 324K 24K 24K 24K 0K rwxp [stack:18608]
00000000d505e000 4376K 4376K 4376K 4376K 0K rw-p [anon]
00000000d54a4000 24K 0K 0K 0K 0K ---p [anon]
00000000d54aa000 92824K 92824K 92824K 92824K 0K rw-p [anon]
00000000daf50000 174784K 174784K 174784K 174784K 0K rw-p [anon]
00000000e5a40000 544K 544K 544K 544K 0K rw-p [anon]
00000000e5ac8000 3296K 0K 0K 0K 0K ---p [anon]
00000000e5e00000 34656K 34300K 34300K 34300K 0K rwxp [anon]
00000000e7fd8000 211104K 0K 0K 0K 0K ---p [anon]
00000000f4e00000 100K 60K 60K 0K 0K r-xp /usr/java/jre1.8.0_65/jre/lib/i386/libzip.so
00000000f4e19000 4K 4K 4K 4K 0K rw-p /usr/java/jre1.8.0_65/jre/lib/i386/libzip.so
00000000f4e5e000 648K 68K 68K 68K 0K rwxp [stack:18331]
00000000f4f00000 1024K 1024K 1024K 1024K 0K rw-p [anon]
…
Total: 735324K 482832K 479435K 462244K 0K
我们对几个重要部分的pmap输出一一作出分析,
- 000000000804a000: 该部分是Java进程的Heap区,此处的Heap指的不是Java那种特殊的Heap, 还是一个任何C/C++内存区域中Heap区。VSS和RSS差不多,都在70M上下。
- 00000000ced00000: 该区域就是用来存放class的。在NMT输出中可以找到对应项。Mapping那一栏是[anon], 因为pmap并不知道这部分区域是干什么用的,而直有JVM自己知道,所以, NMT的输出可以看出该内存区域的用处。
- 00000000d4eaf000 00000000d4eb2000: 这两部分合起来就是一个324K大小的Java Thread Stack。NTM输出中可以找到对应项。
- 00000000d54aa000, 00000000daf50000: 这两部分就非常重要的。它对应就是我们Java意义上的堆的那一部分。简单地讲,- 00000000daf50000那一块就是老年代(Java内存分布分析要以垃圾收集算法为前提)。00000000d54aa000这一部分包含了新生代。结合jstat –gc的输出可以得出这个结论。
十三、jdk命令之Java内存之本地内存分析神器:NMT 和 pmap的更多相关文章
- 【转】哪个更快:Java堆还是本地内存
译文出处: shenzhang 原文出处:原文链接 使用Java的一个好处就是你可以不用亲自来管理内存的分配和释放.当你用new关键字来实例化一个对象时,它所需的内存会自动的在Java堆中分配.堆 ...
- 十一、jdk命令之Jstatd命令(Java Statistics Monitoring Daemon)远程的监控工具连接到本地的JVM执行命令
目录 一.jdk工具之jps(JVM Process Status Tools)命令使用 二.jdk命令之javah命令(C Header and Stub File Generator) 三.jdk ...
- 十、jdk工具之Jdb命令(The Java Debugger)
目录 一.jdk工具之jps(JVM Process Status Tools)命令使用 二.jdk命令之javah命令(C Header and Stub File Generator) 三.jdk ...
- Java内存模型(JMM)是什么?JMM 通过控制主内存与每个线程的本地内存之间的交互,来提供内存可见性保证
Java内存模型就是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能保证效果一致的机制及规范. Java内存模型是根据英文Java Memo ...
- 二、jdk命令之javah命令(C Header and Stub File Generator)
目录 一.jdk工具之jps(JVM Process Status Tools)命令使用 二.jdk命令之javah命令(C Header and Stub File Generator) 三.jdk ...
- Java常见问题分析(内存溢出、内存泄露、线程阻塞等)
Java垃圾回收机制(GC) 1.1 GC机制作用 1.2 堆内存3代分布(年轻代.老年代.持久代) 1.3 GC分类 1.4 GC过程 Java应用内存问题分析 2.1 Java内存划分 2.2 J ...
- 十一、jdk命令之Jstatd命令(Java Statistics Monitoring Daemon)
目录 一.jdk工具之jps(JVM Process Status Tools)命令使用 二.jdk命令之javah命令(C Header and Stub File Generator) 三.jdk ...
- JDK本地内存追踪NMT
通常情况下, JVM占用的内存不仅仅是-Xmx, -Xms等指定的大小, 因为JVM也是一个应用, 它需要额外的空间去完成它的工作, 除了堆外, JVM会分配内存的地方包括以下这些: Metaspac ...
- Java内存区域与内存溢出异常(jdk 6,7,8)
运行时数据区域 Java虚拟机在执行Java程序的过程中会把它关联的内存划分为若干个不同的数据区域.这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户 ...
随机推荐
- .net core开发 (一)
1..net core: 是微软开发的另外一个可以跨平台的.net 2..net framework,.net core, mono的关系三者都是.net在不同操作系统的实现 3. .net core ...
- Docker常用命令汇总,和常用操作举例
Docker命令 docker 常用命令如下 管理命令: container 管理容器 image 管理镜像 network 管理网络 node 管理Swarm节点 plugin 管理插件 secre ...
- python fromkeys() 创建字典
# d = dict.fromkeys("张无忌","赵敏") #创建字典 # print(d)#{'张': '赵敏', '无': '赵敏', '忌': '赵敏 ...
- 优化 Redis 的使用策略
Redis Key 的命名策略 Redis 是 K-V 形式的缓存数据库,每一个需要缓存的 Object 都需要唯一的 Key 来标识.但是,我们日常在做开发的时候,经常会出现一个公司或者部门之间共用 ...
- 还是 js 替代 vw vh 了
有个需求是要层叠两张图,就像你现在看到的:整个浏览器和html页面,内层图片要水平居中,等比例与源UI图适配不同设备 本来很简单的使用 vw vh了( 核心代码 top: 13 ...
- hdu3951巴什博弈变型
参考博客:http://blog.csdn.net/sun897949163/article/details/50609070 特判一下m=1的情况,然后m!=1时,无论对手取多少,我只要取的让这条链 ...
- Java队列的两种实现方式
1. 基于数组 package Algorithm.learn; import java.util.Arrays; /** * Created by liujinhong on 2017/3/7. * ...
- vue 点击按钮 input框架获取焦点的方法
在按钮事件里加上这一段this.$nextTick(() =>{ this.$refs.input.focus()})
- C++ lower_bound 和upper_bound
二分查找的函数有 3 个: 1.lower_bound(起始地址,结束地址,要查找的数值) 返回的是数值 第一个 出现的位置. 2.upper_bound(起始地址,结束地址,要查找的数值) 返回的是 ...
- ASP.NET常用的指令
指令的类型 指令 说明 Application 配置全局应用程序类 Assembly 注册用在Web窗体中的程序集.建议不使用此指令.而是NuGet. Control 配置用户控件 Implement ...