十3、jdk命令之Java內存之本地內存分析神器:NMT 和 pmap

目錄

1、jdk工具之jps(JVM Process Status Tools)命令使用html

2、jdk命令之javah命令(C Header and Stub File Generator)java

3、jdk工具之jstack(Java Stack Trace)算法

4、jdk工具之jstat命令(Java Virtual Machine Statistics Monitoring Tool)sql

4、jdk工具之jstat命令2(Java Virtual Machine Statistics Monitoring Tool)詳解ruby

5、jdk工具之jmap(java memory map)、 mat之四--結合mat對內存泄露的分析服務器

6、jdk工具之jinfo命令(Java Configuration Info)app

7、jdk工具之jconsole命令(Java Monitoring and Management Console)jvm

8、jdk工具之JvisualVM、JvisualVM之二--Java程序性能分析工具Java VisualVM工具

9、jdk工具之jhat命令(Java Heap Analyse Tool)post

10、jdk工具之Jdb命令(The Java Debugger)

11、jdk命令之Jstatd命令(Java Statistics Monitoring Daemon)

11、jdk命令之Jstatd命令(Java Statistics Monitoring Daemon)

12、jdk工具之jcmd介紹(堆轉儲、堆分析、獲取系統信息、查看堆外內存)

十3、jdk命令之Java內存之本地內存分析神器:NMT 和 pmap

1、概述

NMT是Native Memory Tracking的縮寫,是Java7U40引入的HotSpot新特性。 pmap,衆所周知,就是Linux上用來看進程地址空間的。

2、NMT

Java7U40以後JDK提供了Native Memory Tracking工具,跟蹤JVM內部的內存使用,並能夠經過jcmd命令來訪問。不過要注意的是NMT是經過在JVM代碼中添加跟蹤點的方式實現內存跟蹤的,所以NMT不能跟蹤第三方Native庫的內存使用。

2.一、如何開啓NMT

NMT功能默認關閉,能夠經過如下方式開啓:

-XX:NativeMemoryTracking=[off | summary | detail]
配置項 說明
off 默認配置
summary 只收集彙總信息
detail 收集每次調用的信息

注意,根據Java官方文檔,開啓NMT會有5%-10%的性能損耗;

若是想JVM退出時打印退出時的內存使用狀況,能夠經過以下配置項:

-XX:+UnlockDiagnosticVMOptions -XX:+PrintNMTStatistics

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.三、示例

啓動時,加上以下的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的輸出能夠得出這個結論。
相關文章
相關標籤/搜索