JVM監控命令詳解

JVM監控命令基本就是 jps、jstack、jmap、jhat、jstat 幾個命令的使用就能夠了

JDK自己提供了不少方便的JVM性能調優監控工具,除了集成式的VisualVM和jConsole外,還有jps、jstack、jmap、jhat、jstat等小巧的工具,本博客但願能起拋磚引玉之用,讓你們能開始對JVM性能調優的經常使用工具備所瞭解。

現實企業級Java開發中,有時候咱們會碰到下面這些問題:html

  • OutOfMemoryError,內存不足
  • 內存泄露
  • 線程死鎖
  • 鎖爭用(Lock Contention)
  • Java進程消耗CPU太高
  • ......

    這些問題在平常開發中可能被不少人忽視(好比有的人遇到上面的問題只是重啓服務器或者調大內存,而不會深究問題根源),但可以理解並解決這些問題是Java程序員進階的必備要求。本文將對一些經常使用的JVM性能調優監控工具進行介紹,但願能起拋磚引玉之用。本文參考了網上不少資料,難以一一列舉,在此對這些資料的做者表示感謝!關於JVM性能調優相關的資料,請參考文末。java

A、 jps(Java Virtual Machine Process Status Tool) 程序員

jps主要用來輸出JVM中運行的進程狀態信息。語法格式以下:算法

jps [options] [hostid]

若是不指定hostid就默認爲當前主機或服務器。mongodb

命令行參數選項說明以下:apache

1 -q 不輸出類名、Jar名和傳入main方法的參數
2 -m 輸出傳入main方法的參數
3 -l 輸出main類或Jar的全限名
4 -v 輸出傳入JVM的參數ubuntu

好比下面:
1 root@ubuntu:/# jps -m -l
2 2458 org.artifactory.standalone.main.Main /usr/local/artifactory-2.2.5/etc/jetty.xml
3 29920 com.sun.tools.hat.Main -port 9998 /tmp/dump.dat
4 3149 org.apache.catalina.startup.Bootstrap start
5 30972 sun.tools.jps.Jps -m -l
6 8247 org.apache.catalina.startup.Bootstrap start
7 25687 com.sun.tools.hat.Main -port 9999 dump.dat
8 21711 mrf-center.jarsegmentfault

B、 jstack數組

jstack主要用來查看某個Java進程內的線程堆棧信息。語法格式以下:瀏覽器

1 jstack [option] pid
2 jstack [option] executable core
3 jstack [option] [server-id@]remote-hostname-or-ip

命令行參數選項說明以下:

1 -l long listings,會打印出額外的鎖信息,在發生死鎖時能夠用jstack -l pid來觀察鎖持有狀況
2 -m mixed mode,不只會輸出Java堆棧信息,還會輸出C/C++堆棧信息(好比Native方法)

jstack能夠定位到線程堆棧,根據堆棧信息咱們能夠定位到具體代碼,因此它在JVM性能調優中使用得很是多。下面咱們來一個實例找出某個Java進程中最耗費CPU的Java線程並定位堆棧信息,用到的命令有ps、top、printf、jstack、grep。

第一步先找出Java進程ID,我部署在服務器上的Java應用名稱爲mrf-center:

1 root@ubuntu:/# ps -ef | grep mrf-center | grep -v grep
2 root 21711 1 1 14:47 pts/3 00:02:10 java -jar mrf-center.jar

獲得進程ID爲21711,第二步找出該進程內最耗費CPU的線程,可使用ps -Lfp pid或者ps -mp pid -o THREAD, tid, time或者top -Hp pid,我這裏用第三個,輸出以下:

JVM性能調優監控工具jps、jstack、jmap、jhat、jstat使用詳解

TIME列就是各個Java線程耗費的CPU時間,CPU時間最長的是線程ID爲21742的線程,用

printf "%x\n" 21742

獲得21742的十六進制值爲54ee,下面會用到。

OK,下一步終於輪到jstack上場了,它用來輸出進程21711的堆棧信息,而後根據線程ID的十六進制值grep,以下:

1 root@ubuntu:/# jstack 21711 | grep 54ee
2 "PollIntervalRetrySchedulerThread" prio=10 tid=0x00007f950043e000 nid=0x54ee in Object.wait() [0x00007f94c6eda000] |

能夠看到CPU消耗在PollIntervalRetrySchedulerThread這個類的Object.wait(),我找了下個人代碼,定位到下面的代碼:

// Idle wait
getLog().info("Thread [" + getName() + "] is idle waiting...");
schedulerThreadState = PollTaskSchedulerThreadState.IdleWaiting;
long now = System.currentTimeMillis();
long waitTime = now + getIdleWaitTime();
long timeUntilContinue = waitTime - now;
synchronized(sigLock) {
    try {
        if(!halted.get()) {
            sigLock.wait(timeUntilContinue);
        }
    } 
    catch (InterruptedException ignore) {
    }
}

它是輪詢任務的空閒等待代碼,上面的sigLock.wait(timeUntilContinue)就對應了前面的Object.wait()。

C、 jmap(Memory Map)和jhat(Java Heap Analysis Tool)

jmap用來查看堆內存使用情況,通常結合jhat使用。

jmap語法格式以下:

1 jmap [option] pid
2 jmap [option] executable core
3 jmap [option] [server-id@]remote-hostname-or-ip

若是運行在64位JVM上,可能須要指定-J-d64命令選項參數。

jmap -permstat pid

打印進程的類加載器和類加載器加載的持久代對象信息,輸出:類加載器名稱、對象是否存活(不可靠)、對象地址、父類加載器、已加載的類大小等信息,以下圖:

JVM性能調優監控工具jps、jstack、jmap、jhat、jstat使用詳解

使用jmap -heap pid查看進程堆內存使用狀況,包括使用的GC算法、堆配置參數和各代中堆內存使用狀況。好比下面的例子:

root@ubuntu:/# jmap -heap 21711
Attaching to process ID 21711, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 20.10-b01
 
using thread-local object allocation.
Parallel GC with 4 thread(s)
 
Heap Configuration:
   MinHeapFreeRatio = 40
   MaxHeapFreeRatio = 70
   MaxHeapSize      = 2067791872 (1972.0MB)
   NewSize          = 1310720 (1.25MB)
   MaxNewSize       = 17592186044415 MB
   OldSize          = 5439488 (5.1875MB)
   NewRatio         = 2
   SurvivorRatio    = 8
   PermSize         = 21757952 (20.75MB)
   MaxPermSize      = 85983232 (82.0MB)
 
Heap Usage:
PS Young Generation
Eden Space:
   capacity = 6422528 (6.125MB)
   used     = 5445552 (5.1932830810546875MB)
   free     = 976976 (0.9317169189453125MB)
   84.78829520089286% used
From Space:
   capacity = 131072 (0.125MB)
   used     = 98304 (0.09375MB)
   free     = 32768 (0.03125MB)
   75.0% used
To Space:
   capacity = 131072 (0.125MB)
   used     = 0 (0.0MB)
   free     = 131072 (0.125MB)
   0.0% used
PS Old Generation
   capacity = 35258368 (33.625MB)
   used     = 4119544 (3.9287033081054688MB)
   free     = 31138824 (29.69629669189453MB)
   11.683876009235595% used
PS Perm Generation
   capacity = 52428800 (50.0MB)
   used     = 26075168 (24.867218017578125MB)
   free     = 26353632 (25.132781982421875MB)
   49.73443603515625% used
   ....

使用jmap -histo[:live] pid查看堆內存中的對象數目、大小統計直方圖,若是帶上live則只統計活對象,以下:

root@ubuntu:/# jmap -histo:live 21711 | more
 
 num     #instances         #bytes  class name
----------------------------------------------
   1:         38445        5597736  <constMethodKlass>
   2:         38445        5237288  <methodKlass>
   3:          3500        3749504  <constantPoolKlass>
   4:         60858        3242600  <symbolKlass>
   5:          3500        2715264  <instanceKlassKlass>
   6:          2796        2131424  <constantPoolCacheKlass>
   7:          5543        1317400  [I
   8:         13714        1010768  [C
   9:          4752        1003344  [B
  10:          1225         639656  <methodDataKlass>
  11:         14194         454208  java.lang.String
  12:          3809         396136  java.lang.Class
  13:          4979         311952  [S
  14:          5598         287064  [[I
  15:          3028         266464  java.lang.reflect.Method
  16:           280         163520  <objArrayKlassKlass>
  17:          4355         139360  java.util.HashMap$Entry
  18:          1869         138568  [Ljava.util.HashMap$Entry;
  19:          2443          97720  java.util.LinkedHashMap$Entry
  20:          2072          82880  java.lang.ref.SoftReference
  21:          1807          71528  [Ljava.lang.Object;
  22:          2206          70592  java.lang.ref.WeakReference
  23:           934          52304  java.util.LinkedHashMap
  24:           871          48776  java.beans.MethodDescriptor
  25:          1442          46144  java.util.concurrent.ConcurrentHashMap$HashEntry
  26:           804          38592  java.util.HashMap
  27:           948          37920  java.util.concurrent.ConcurrentHashMap$Segment
  28:          1621          35696  [Ljava.lang.Class;
  29:          1313          34880  [Ljava.lang.String;
  30:          1396          33504  java.util.LinkedList$Entry
  31:           462          33264  java.lang.reflect.Field
  32:          1024          32768  java.util.Hashtable$Entry
  33:           948          31440  [Ljava.util.concurrent.ConcurrentHashMap$HashEntry;

class name是對象類型,說明以下:

1 B byte
2 C char
3 D double
4 F float
5 I int
6 J long
7 Z boolean
8 [ 數組,如[I表示int[]
9 [L+類名 其餘對象

還有一個很經常使用的狀況是:用jmap把進程內存使用狀況dump到文件中,再用jhat分析查看。jmap進行dump命令格式以下:

jmap -dump:format=b,file=dumpFileName

我同樣地對上面進程ID爲21711進行Dump:

root@ubuntu:/# jmap -dump:format=b,file=/tmp/dump.dat 21711
Dumping heap to /tmp/dump.dat ...
Heap dump file created

dump出來的文件能夠用MAT、VisualVM等工具查看,這裏用jhat查看:

root@ubuntu:/# jhat -port 9998 /tmp/dump.dat
Reading from /tmp/dump.dat...
Dump file created Tue Jan 28 17:46:14 CST 2014
Snapshot read, resolving...
Resolving 132207 objects...
Chasing references, expect 26 dots..........................
Eliminating duplicate references..........................
Snapshot resolved.
Started HTTP server on port 9998
Server is ready.

而後就能夠在瀏覽器中輸入主機地址:9998查看了:

JVM性能調優監控工具jps、jstack、jmap、jhat、jstat使用詳解

上面紅線框出來的部分你們能夠本身去摸索下,最後一項支持OQL(對象查詢語言)。

D、jstat(JVM統計監測工具)

語法格式以下:

jstat [ generalOption | outputOptions vmid [interval[s|ms] [count]] ]

vmid是虛擬機ID,在Linux/Unix系統上通常就是進程ID。interval是採樣時間間隔。count是採樣數目。好比下面輸出的是GC信息,採樣時間間隔爲250ms,採樣數爲4:

root@ubuntu:/# jstat -gc 21711 250 4
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       PC     PU    YGC     YGCT    FGC    FGCT     GCT   
192.0  192.0   64.0   0.0    6144.0   1854.9   32000.0     4111.6   55296.0 25472.7    702    0.431   3      0.218    0.649
192.0  192.0   64.0   0.0    6144.0   1972.2   32000.0     4111.6   55296.0 25472.7    702    0.431   3      0.218    0.649
192.0  192.0   64.0   0.0    6144.0   1972.2   32000.0     4111.6   55296.0 25472.7    702    0.431   3      0.218    0.649
192.0  192.0   64.0   0.0    6144.0   2109.7   32000.0     4111.6   55296.0 25472.7    702    0.431   3      0.218    0.649

要明白上面各列的意義,先看JVM堆內存佈局:

JVM性能調優監控工具jps、jstack、jmap、jhat、jstat使用詳解

能夠看出:

1 堆內存 = 年輕代 + 年老代 + 永久代
2 年輕代 = Eden區 + 兩個Survivor區(From和To)

如今來解釋各列含義:

1 S0C、S1C、S0U、S1U:Survivor 0/1區容量(Capacity)和使用量(Used)
2 EC、EU:Eden區容量和使用量
3 OC、OU:年老代容量和使用量
4 PC、PU:永久代容量和使用量
5 YGC、YGT:年輕代GC次數和GC耗時
6 FGC、FGCT:Full GC次數和Full GC耗時
7 GCT:GC總耗時

其餘JVM性能調優參考資料:

《Java虛擬機規範》

《Java Performance》

《Trouble Shooting Guide for JavaSE 6 with HotSpot VM》: http://www.oracle.com/technet...

《Effective Java》

VisualVM: http://docs.oracle.com/javase...

jConsole: http://docs.oracle.com/javase...

Monitoring and Managing JavaSE 6 Applications: http://www.oracle.com/technet...

原文路徑:http://blog.csdn.net/wisgood/...

對應參數解釋:

各類 Java Thread State 第一分析法則

使用 TDA 工具,看到大量 Java Thread State 的第一反應是:

1,線程狀態爲「waiting for monitor entry」:

意味着它 在等待進入一個臨界區 ,因此它在」Entry Set「隊列中等待。

此時線程狀態通常都是 Blocked:

  • java.lang.Thread.State: BLOCKED (on object monitor)

2,線程狀態爲「waiting on condition」:

說明它在等待另外一個條件的發生,來把本身喚醒,或者乾脆它是調用了 sleep(N)。

此時線程狀態大體爲如下幾種:

  • java.lang.Thread.State: WAITING (parking):一直等那個條件發生;
  • java.lang.Thread.State: TIMED_WAITING (parking或sleeping):定時的,那個條件不到來,也將定時喚醒本身。

3,若是大量線程在「waiting for monitor entry」:

多是一個全局鎖阻塞住了大量線程。

若是短期內打印的 thread dump 文件反映,隨着時間流逝,waiting for monitor entry 的線程愈來愈多,沒有減小的趨勢,可能意味着某些線程在臨界區裏呆的時間太長了,以致於愈來愈多新線程遲遲沒法進入臨界區

4,若是大量線程在waiting on condition」:

多是它們又跑去獲取第三方資源,尤爲是第三方網絡資源,遲遲獲取不到Response,致使大量線程進入等待狀態。

因此若是你發現有大量的線程都處在 Wait on condition,從線程堆棧看,正等待網絡讀寫,這多是一個網絡瓶頸的徵兆,由於網絡阻塞致使線程沒法執行。

線程狀態爲「in Object.wait()」:

說明它得到了監視器以後,又調用了 java.lang.Object.wait() 方法

每一個 Monitor在某個時刻,只能被一個線程擁有,該線程就是 「Active Thread」,而其它線程都是 「Waiting Thread」,分別在兩個隊列 「 Entry Set」和 「Wait Set」裏面等候。在 「Entry Set」中等待的線程狀態是 「Waiting for monitor entry」,而在 「Wait Set」中等待的線程狀態是 「in Object.wait()」。

當線程得到了 Monitor,若是發現線程繼續運行的條件沒有知足,它則調用對象(通常就是被 synchronized 的對象)的 wait() 方法,放棄了 Monitor,進入 「Wait Set」隊列。

此時線程狀態大體爲如下幾種:

  • java.lang.Thread.State: TIMED_WAITING (on object monitor);
  • java.lang.Thread.State: WAITING (on object monitor);

通常都是RMI相關線程(RMI RenewClean、 GC Daemon、RMI Reaper),GC線程(Finalizer),引用對象垃圾回收線程(Reference Handler)等系統線程處於這種狀態。

Java Monitor

圖1 A Java Monitor

示範一:

下面這個線程在等待這個鎖 0x00000000fe7e3b50,等待進入臨界區:

"RMI TCP Connection(64896)-172.16.52.118" daemon prio=10 tid=0x00000000405a6000 nid=0x68fe waiting for monitor entry [0x00007f2be65a3000]

java.lang.Thread.State: BLOCKED (on object monitor)

at com.xyz.goods.service.impl.GoodsServiceImpl.findChanellGoodsCountWithCache(GoodsServiceImpl.java:1734)

- waiting to lock <0x00000000fe7e3b50> (a java.lang.String)

那麼誰持有這個鎖呢?

是另外一個先調用了 findChanellGoodsCountWithCache 函數的線程:

"RMI TCP Connection(64878)-172.16.52.117" daemon prio=10 tid=0x0000000040822000 nid=0x6841 runnable [0x00007f2be76b3000]

java.lang.Thread.State: RUNNABLE

at java.net.SocketInputStream.socketRead0(Native Method)

at java.net.SocketInputStream.read(SocketInputStream.java:129)

at java.io.BufferedInputStream.fill(BufferedInputStream.java:218)

at java.io.BufferedInputStream.read1(BufferedInputStream.java:258)

at java.io.BufferedInputStream.read(BufferedInputStream.java:317)

- locked <0x00000000af4ed638> (a java.io.BufferedInputStream)

at org.bson.io.Bits.readFully(Bits.java:35)

at org.bson.io.Bits.readFully(Bits.java:28)

at com.mongodb.Response.<init>(Response.java:35)

at com.mongodb.DBPort.go(DBPort.java:110)

- locked <0x00000000af442d48> (a com.mongodb.DBPort)

at com.mongodb.DBPort.go(DBPort.java:75)

- locked <0x00000000af442d48> (a com.mongodb.DBPort)

at com.mongodb.DBPort.call(DBPort.java:65)

at com.mongodb.DBTCPConnector.call(DBTCPConnector.java:202)

at com.mongodb.DBApiLayer$MyCollection.__find(DBApiLayer.java:296)

at com.mongodb.DB.command(DB.java:152)

at com.mongodb.DBCollection.getCount(DBCollection.java:760)

at com.mongodb.DBCollection.getCount(DBCollection.java:731)

at com.mongodb.DBCollection.count(DBCollection.java:697)

at com.xyz.goods.manager.MongodbManager.count(MongodbManager.java:202)

at com.xyz.goods.service.impl.GoodsServiceImpl.findChanellGoodsCount(GoodsServiceImpl.java:1787)

at com.xyz.goods.service.impl.GoodsServiceImpl.findChanellGoodsCountWithCache(GoodsServiceImpl.java:1739)

- locked <0x00000000fe7e3b50> (a java.lang.String)

示範二:

等待另外一個條件發生來將本身喚醒:

"RMI TCP Connection(idle)" daemon prio=10 tid=0x00007fd50834e800 nid=0x56b2 waiting on condition [0x00007fd4f1a59000]

java.lang.Thread.State: TIMED_WAITING (parking)

at sun.misc.Unsafe.park(Native Method)

- parking to wait for <0x00000000acd84de8> (a java.util.concurrent.SynchronousQueue$TransferStack)

at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:198)

at java.util.concurrent.SynchronousQueue$TransferStack.awaitFulfill(SynchronousQueue.java:424)

at java.util.concurrent.SynchronousQueue$TransferStack.transfer(SynchronousQueue.java:323)

at java.util.concurrent.SynchronousQueue.poll(SynchronousQueue.java:874)

at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:945)

at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:907)

at java.lang.Thread.run(Thread.java:662)

1)「TIMED_WAITING (parking)」中的 timed_waiting 指等待狀態,但這裏指定了時間,到達指定的時間後自動退出等待狀態;parking指線程處於掛起中。

2)「waiting on condition」須要與堆棧中的「parking to wait for <0x00000000acd84de8> (a java.util.concurrent.SynchronousQueue$TransferStack)」 結合來看。首先,本線程確定是在等待某個條件的發生,來把本身喚醒。其次,SynchronousQueue 並非一個隊列,只是線程之間移交信息的機制,當咱們把一個元素放入到 SynchronousQueue 中時必須有另外一個線程正在等待接受移交的任務,所以這就是本線程在等待的條件。

示範三:

"RMI RenewClean-[172.16.50.182:4888]" daemon prio=10 tid=0x0000000040d2c800 nid=0x97e in Object.wait() [0x00007f9ccafd0000]

java.lang.Thread.State: TIMED_WAITING (on object monitor)

at java.lang.Object.wait(Native Method)

- waiting on <0x0000000799b032d8> (a java.lang.ref.ReferenceQueue$Lock)

at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:118)

- locked <0x0000000799b032d8> (a java.lang.ref.ReferenceQueue$Lock)

at sun.rmi.transport.DGCClient$EndpointEntry$RenewCleanThread.run(DGCClient.java:516)

at java.lang.Thread.run(Thread.java:662)

參考資源:

1)CUBRID,2012,How to Analyze Java Thread Dumps

2)鄭昀,2013,三個實例演示Java THread Dump日誌分析

相關文章
相關標籤/搜索