十三、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的輸出可以得出這個結論。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM