jvm 內存dump、gc查看、線程死鎖,jmap、jstack、jstat

1. jstat 

    這個命令對於查看Jvm的堆棧信息很有用。能夠查看eden,survivor,old,perm等heap的capacity,utility信息

    對於查看系統是不是有能存泄漏以及參數設置是否合理有不錯的意義

2. jstack

    這個是用來查看jvm當前的thread dump的。可以看到當前Jvm裏面的線程狀況。

    這個對於查找blocked線程比較有意義

3. jmap .

    這個是用來查看jvm當前的heap dump的。可以看出當前jvm中各種對象的數量,所佔空間等等。

    尤其值得一提的是這個命令可以到處一份binary heap dump的bin文件,這個文件能夠直接用

    Eclipse Memory Anayliser來分析,並找出潛在的內存泄漏的地方。

4. 還有一個比較有用的非jvm命令--netstat

    通過這個命令可以看到linux系統當前在各個端口的鏈接狀態,比如查看數據庫連接數等等

jstat


       1. jstat -gc pid

            可以顯示gc的信息,查看gc的次數,及時間。

            其中最後五項,分別是young gc的次數,young gc的時間,full gc的次數,full gc的時間,gc的總時間。

      2.jstat -gccapacity pid
            可以顯示,VM內存中三代(young,old,perm)對象的使用和佔用大小,

            如:PGCMN顯示的是最小perm的內存使用量,PGCMX顯示的是perm的內存最大使用量,

            PGC是當前新生成的perm內存佔用量,PC是但前perm內存佔用量。

            其他的可以根據這個類推, OC是old內純的佔用量。

     3.jstat -gcutil pid

            統計gc信息統計。

     4.jstat -gcnew pid

           年輕代對象的信息。

     5.jstat -gcnewcapacity pid

           年輕代對象的信息及其佔用量。

     6.jstat -gcold pid

          old代對象的信息。

     7.stat -gcoldcapacity pid

          old代對象的信息及其佔用量。

     8.jstat -gcpermcapacity pid

          perm對象的信息及其佔用量。

     9.jstat -class pid

          顯示加載class的數量,及所佔空間等信息。
     10.jstat -compiler pid

          顯示VM實時編譯的數量等信息。

     11.stat -printcompilation pid

          當前VM執行的信息。

        一些術語的中文解釋:

         S0C:年輕代中第一個survivor(倖存區)的容量 (字節)
         S1C:年輕代中第二個survivor(倖存區)的容量 (字節)
         S0U:年輕代中第一個survivor(倖存區)目前已使用空間 (字節)
         S1U:年輕代中第二個survivor(倖存區)目前已使用空間 (字節)
           EC:年輕代中Eden(伊甸園)的容量 (字節)
           EU:年輕代中Eden(伊甸園)目前已使用空間 (字節)
           OC:Old代的容量 (字節)
           OU:Old代目前已使用空間 (字節)
           PC:Perm(持久代)的容量 (字節)
           PU:Perm(持久代)目前已使用空間 (字節)
         YGC:從應用程序啓動到採樣時年輕代中gc次數
       YGCT:從應用程序啓動到採樣時年輕代中gc所用時間(s)
         FGC:從應用程序啓動到採樣時old代(全gc)gc次數
       FGCT:從應用程序啓動到採樣時old代(全gc)gc所用時間(s)
         GCT:從應用程序啓動到採樣時gc用的總時間(s)

    NGCMN:年輕代(young)中初始化(最小)的大小 (字節)

    NGCMX:年輕代(young)的最大容量 (字節)

        NGC:年輕代(young)中當前的容量 (字節)

   OGCMN:old代中初始化(最小)的大小 (字節)

   OGCMX:old代的最大容量 (字節)

       OGC:old代當前新生成的容量 (字節)

   PGCMN:perm代中初始化(最小)的大小 (字節)

   PGCMX:perm代的最大容量 (字節)  

       PGC:perm代當前新生成的容量 (字節)

          S0:年輕代中第一個survivor(倖存區)已使用的佔當前容量百分比

         S1:年輕代中第二個survivor(倖存區)已使用的佔當前容量百分比

           E:年輕代中Eden(伊甸園)已使用的佔當前容量百分比

           O:old代已使用的佔當前容量百分比

           P:perm代已使用的佔當前容量百分比

  S0CMX:年輕代中第一個survivor(倖存區)的最大容量 (字節)

 S1CMX :年輕代中第二個survivor(倖存區)的最大容量 (字節)

    ECMX:年輕代中Eden(伊甸園)的最大容量 (字節)

       DSS:當前需要survivor(倖存區)的容量 (字節)(Eden區已滿)

          TT: 持有次數限制

       MTT : 最大持有次數限制

jstack

   jstack用於打印出給定的Java進程ID或core file或遠程調試服務的Java堆棧信息,如果是在64位機器上,需要指定選項"-J-d64",Windows的jstack使用方式只支持以下的這種方式:jstack [-l] pid

如果java程序崩潰生成core文件,jstack工具可以用來獲得core文件的java stack和native stack的信息,從而可以輕鬆地知道java程序是如何崩潰和在程序何處發生問題。另外,jstack工具還可以附屬到正在運行的java程序中,看到當時運行的java程序的java stack和native stack的信息, 如果現在運行的java程序呈現hung的狀態,jstack是非常有用的。

需要注意的問題:

l 不同的 JAVA虛機的線程 DUMP的創建方法和文件格式是不一樣的,不同的 JVM版本, dump信息也有差別。

l 在實際運行中,往往一次 dump的信息,還不足以確認問題。建議產生三次 dump信息,如果每次 dump都指向同一個問題,我們才確定問題的典型性。 

2、命令格式

$jstack [ option ] pid

$jstack [ option ] executable core

$jstack [ option ] [server-id@]remote-hostname-or-IP

參數說明:

pid: java應用程序的進程號,一般可以通過jps來獲得;

executable:產生core dump的java可執行程序;

core:打印出的core文件;

remote-hostname-or-ip:遠程debug服務器的名稱或IP;

server-id: 唯一id,假如一臺主機上多個遠程debug服務;

 

示例:

$jstack –l 23561

 

線程分析:

一般情況下,通過jstack輸出的線程信息主要包括:jvm自身線程、用戶線程等。其中jvm線程會在jvm啓動時就會存在。對於用戶線程則是在用戶訪問時纔會生成。

l jvm線程:

在線程中,有一些 JVM內部的後臺線程,來執行譬如垃圾回收,或者低內存的檢測等等任務,這些線程往往在JVM初始化的時候就存在,如下所示:

"Attach Listener" daemon prio=10 tid=0x0000000052fb8000 nid=0xb8f waiting on condition [0x0000000000000000]

   java.lang.Thread.State: RUNNABLE

 

   Locked ownable synchronizers:

        - None

destroyJavaVM" prio=10 tid=0x00002aaac1225800 nid=0x7208 waiting on condition [0x0000000000000000]

   java.lang.Thread.State: RUNNABLE

 

   Locked ownable synchronizers:

        - None

l 用戶級別的線程

還有一類線程是用戶級別的,它會根據用戶請求的不同而發生變化。該類線程的運行情況往往是我們所關注的重點。而且這一部分也是最容易產生死鎖的地方。

"qtp496432309-42" prio=10 tid=0x00002aaaba2a1800 nid=0x7580 waiting on condition [0x00000000425e9000]

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

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

        - parking to wait for  <0x0000000788cfb020> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)

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

        at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2025)

        at org.eclipse.jetty.util.BlockingArrayQueue.poll(BlockingArrayQueue.java:320)

        at org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:479)

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

 

   Locked ownable synchronizers:

        - None

從上述的代碼示例中我們可以看到該用戶線程的以下幾類信息:

Ø 線程的狀態:waiting on condition(等待條件發生)

Ø 線程的調用情況;

Ø 線程對資源的鎖定情況;

 

線程的狀態分析:

正如我們剛看到的那樣,線程的狀態是一個重要的指標,它會顯示在線程每行結尾的地方。那麼線程常見的有哪些狀態呢?線程在什麼樣的情況下會進入這種狀態呢?我們能從中發現什麼線索?

Runnable

該狀態表示線程具備所有運行條件,在運行隊列中準備操作系統的調度,或者正在運行。 

Waiton condition 

該狀態出現在線程等待某個條件的發生。具體是什麼原因,可以結合stacktrace來分析。最常見的情況是線程在等待網絡的讀寫,比如當網絡數據沒有準備好讀時,線程處於這種等待狀態,而一旦有數據準備好讀之後,線程會重新激活,讀取並處理數據。在 Java引入 NIO之前,對於每個網絡連接,都有一個對應的線程來處理網絡的讀寫操作,即使沒有可讀寫的數據,線程仍然阻塞在讀寫操作上,這樣有可能造成資源浪費,而且給操作系統的線程調度也帶來壓力。在 NIO裏採用了新的機制,編寫的服務器程序的性能和可擴展性都得到提高。 

如果發現有大量的線程都在處在 Wait on condition,從線程 stack看, 正等待網絡讀寫,這可能是一個網絡瓶頸的徵兆。因爲網絡阻塞導致線程無法執行。一種情況是網絡非常忙,幾乎消耗了所有的帶寬,仍然有大量數據等待網絡讀寫;另一種情況也可能是網絡空閒,但由於路由等問題,導致包無法正常的到達。所以要結合系統的一些性能觀察工具來綜合分析,比如 netstat統計單位時間的發送包的數目,如果很明顯超過了所在網絡帶寬的限制 ; 觀察 cpu的利用率,如果系統態的 CPU時間,相對於用戶態的 CPU時間比例較高;如果程序運行在 Solaris 10平臺上,可以用 dtrace工具看系統調用的情況,如果觀察到 read/write的系統調用的次數或者運行時間遙遙領先;這些都指向由於網絡帶寬所限導致的網絡瓶頸。 

另外一種出現 Wait on condition的常見情況是該線程在 sleep,等待 sleep的時間到了時候,將被喚醒。 

Waitingfor monitor entry 和 in Object.wait() 

在多線程的 JAVA程序中,實現線程之間的同步,就要說說Monitor。Monitor是Java中用以實現線程之間的互斥與協作的主要手段,它可以看成是對象或者 Class的鎖。每一個對象都有,也僅有一個 monitor。下面這個圖,描述了線程和 Monitor之間關係,以及線程的狀態轉換圖: 

 

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

先看 “Entry Set”裏面的線程。我們稱被 synchronized保護起來的代碼段爲臨界區。當一個線程申請進入臨界區時,它就進入了 “Entry Set”隊列。對應的 code就像: 

synchronized(obj){ 

......... 

這時有兩種可能性: 

該 monitor不被其它線程擁有,Entry Set裏面也沒有其它等待線程。本線程即成爲相應類或者對象的 Monitor的 Owner,執行臨界區的代碼 。此時線程將處於Runnable狀態;

該 monitor被其它線程擁有,本線程在 Entry Set隊列中等待。此時dump的信息顯示“waiting for monitor entry”。

"Thread-0" prio=10 tid=0x08222eb0 nid=0x9 waiting for monitor entry [0xf927b000..0xf927bdb8] 

at testthread.WaitThread.run(WaitThread.java:39) 
- waiting to lock <0xef63bf08> (a java.lang.Object) 
- locked <0xef63beb8> (a java.util.ArrayList) 
at java.lang.Thread.run(Thread.java:595) 

臨界區的設置,是爲了保證其內部的代碼執行的原子性和完整性。但是因爲臨界區在任何時間只允許線程串行通過,這和我們多線程的程序的初衷是相反的。如果在多線程的程序中,大量使用 synchronized,或者不適當的使用了它,會造成大量線程在臨界區的入口等待,造成系統的性能大幅下降。如果在線程 DUMP中發現了這個情況,應該審查源碼,改進程序。 

現在我們再來看現在線程爲什麼會進入 “Wait Set”。當線程獲得了 Monitor,進入了臨界區之後,如果發現線程繼續運行的條件沒有滿足,它則調用對象(一般就是被 synchronized 的對象)的 wait() 方法,放棄了 Monitor,進入 “Wait Set”隊列。只有當別的線程在該對象上調用了 notify() 或者 notifyAll() , “ Wait Set”隊列中線程纔得到機會去競爭,但是隻有一個線程獲得對象的Monitor,恢復到運行態。在 “Wait Set”中的線程, DUMP中表現爲: in Object.wait(),類似於: 

"Thread-1" prio=10 tid=0x08223250 nid=0xa in Object.wait() [0xef47a000..0xef47aa38] 

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

- waiting on <0xef63beb8> (a java.util.ArrayList) 

at java.lang.Object.wait(Object.java:474) 

at testthread.MyWaitThread.run(MyWaitThread.java:40) 

- locked <0xef63beb8> (a java.util.ArrayList) 

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

仔細觀察上面的 DUMP信息,你會發現它有以下兩行: 

² locked <0xef63beb8> (ajava.util.ArrayList) 

² waiting on <0xef63beb8> (ajava.util.ArrayList) 

這裏需要解釋一下,爲什麼先 lock了這個對象,然後又 waiting on同一個對象呢?讓我們看看這個線程對應的代碼: 

synchronized(obj){

......... 

obj.wait();

......... 

線程的執行中,先用 synchronized 獲得了這個對象的 Monitor(對應於 locked <0xef63beb8> )。當執行到 obj.wait(), 線程即放棄了 Monitor的所有權,進入 “wait set”隊列(對應於 waiting on<0xef63beb8> )。 

往在你的程序中,會出現多個類似的線程,他們都有相似的 dump也可能是正常的。比如,在程序中有多個服務線程,設計成從一個隊列裏面讀取請求數據。這個隊列就是 lock以及 waiting on的對象。當隊列爲空的時候,這些線程都會在這個隊列上等待,直到隊列有了數據,這些線程被notify,當然只有一個線程獲得了 lock,繼續執行,而其它線程繼續等待。 



jmap


jmap 的用途是爲了展示java進程的內存映射信息,或者堆內存詳情

常用的參數如下:

histo

jmap -histo pid 展示class的內存情況

展示的信息爲編號,實例數,字節,類名

 

 

heap

jmap -heap pid 展示pid的整體堆信息

jmap -heap 2464  
    JVM version is 16.3-b01  
      
    using thread-local object allocation.  
    Parallel GC with 13 thread(s)  
      
    Heap Configuration:  
       MinHeapFreeRatio = 40  
       MaxHeapFreeRatio = 70  
       MaxHeapSize      = 8436842496 (8046.0MB)  
       NewSize          = 5439488 (5.1875MB)  
       MaxNewSize       = 17592186044415 MB  
       OldSize          = 5439488 (5.1875MB)  
       NewRatio         = 2  
       SurvivorRatio    = 8  
       PermSize         = 21757952 (20.75MB)  
       MaxPermSize      = 88080384 (84.0MB)  
      
    Heap Usage:  
    PS Young Generation  
    Eden Space:  
       capacity = 87883776 (83.8125MB)  
       used     = 31053080 (29.614524841308594MB)  
       free     = 56830696 (54.197975158691406MB)  
       35.33425782706469% used  
    From Space:  
       capacity = 13828096 (13.1875MB)  
       used     = 196608 (0.1875MB)  
       free     = 13631488 (13.0MB)  
       1.4218009478672986% used  
    To Space:  
       capacity = 16384000 (15.625MB)  
       used     = 0 (0.0MB)  
       free     = 16384000 (15.625MB)  
       0.0% used  
    PS Old Generation  
       capacity = 156172288 (148.9375MB)  
       used     = 27098208 (25.842864990234375MB)  
       free     = 129074080 (123.09463500976562MB)  
       17.35148299805917% used  
    PS Perm Generation  
       capacity = 88080384 (84.0MB)  
       used     = 50847592 (48.492042541503906MB)  
       free     = 37232792 (35.507957458496094MB)  
       57.728622073218936% used  

說明如下:

Parallel GC with 13 thread(s)   #13個gc線程  
  
Heap Configuration:#堆內存初始化配置  
   MinHeapFreeRatio = 40  #-XX:MinHeapFreeRatio設置JVM堆最小空閒比率  
   MaxHeapFreeRatio = 70  #-XX:MaxHeapFreeRatio設置JVM堆最大空閒比率  
   MaxHeapSize      = 8436842496 (8046.0MB)#-XX:MaxHeapSize=設置JVM堆的最大大小  
   NewSize          = 5439488 (5.1875MB) #-XX:NewSize=設置JVM堆的‘新生代’的默認大小  
   MaxNewSize       = 17592186044415 MB  #-XX:MaxNewSize=設置JVM堆的‘新生代’的最大大小  
   OldSize          = 5439488 (5.1875MB) #-XX:OldSize=設置JVM堆的‘老生代’的大小  
   NewRatio         = 2 #-XX:NewRatio=:‘新生代’和‘老生代’的大小比率  
   SurvivorRatio    = 8 #-XX:SurvivorRatio=設置年輕代中Eden區與Survivor區的大小比值  
   PermSize         = 21757952 (20.75MB) #-XX:PermSize=<value>:設置JVM堆的‘永生代’的初始大小  
   MaxPermSize      = 88080384 (84.0MB) #-XX:MaxPermSize=<value>:設置JVM堆的‘永生代’的最大大小  
  
Heap Usage:  
PS Young Generation  
Eden Space:#Eden區內存分佈  
   capacity = 87883776 (83.8125MB)  
   used     = 31053080 (29.614524841308594MB)  
   free     = 56830696 (54.197975158691406MB)  
   35.33425782706469% used  
From Space:#其中一個Survivor區的內存分佈  
   capacity = 13828096 (13.1875MB)  
   used     = 196608 (0.1875MB)  
   free     = 13631488 (13.0MB)  
   1.4218009478672986% used  
To Space:#另一個Survivor區的內存分佈  
   capacity = 16384000 (15.625MB)  
   used     = 0 (0.0MB)  
   free     = 16384000 (15.625MB)  
   0.0% used  
PS Old Generation#當前的Old區內存分佈  
   capacity = 156172288 (148.9375MB)  
   used     = 27098208 (25.842864990234375MB)  
   free     = 129074080 (123.09463500976562MB)  
   17.35148299805917% used  
PS Perm Generation#當前的 “永生代” 內存分佈  
   capacity = 88080384 (84.0MB)  
   used     = 50847592 (48.492042541503906MB)  
   free     = 37232792 (35.507957458496094MB)  
   57.728622073218936% used 

dump

導出的文件可以供分析用,比如jhat或者mat,以便查找內存溢出原因


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章