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 和 pmapNMT是Native Memory Tracking的縮寫,是Java7U40引入的HotSpot新特性。 pmap,衆所周知,就是Linux上用來看進程地址空間的。
Java7U40以後JDK提供了Native Memory Tracking工具,跟蹤JVM內部的內存使用,並能夠經過jcmd命令來訪問。不過要注意的是NMT是經過在JVM代碼中添加跟蹤點的方式實現內存跟蹤的,所以NMT不能跟蹤第三方Native庫的內存使用。
NMT功能默認關閉,能夠經過如下方式開啓:
-XX:NativeMemoryTracking=[off | summary | detail]
配置項 | 說明 |
---|---|
off | 默認配置 |
summary | 只收集彙總信息 |
detail | 收集每次調用的信息 |
注意,根據Java官方文檔,開啓NMT會有5%-10%的性能損耗;
若是想JVM退出時打印退出時的內存使用狀況,能夠經過以下配置項:
-XX:+UnlockDiagnosticVMOptions -XX:+PrintNMTStatistics
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 |
啓動時,加上以下的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,會在下一篇文章中詳細描述。
首先,你要在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 -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輸出一一做出分析,