1. 程式人生 > 實用技巧 >Java常用命令:jps、jstack、jmap、jstat(帶有例項教程)

Java常用命令:jps、jstack、jmap、jstat(帶有例項教程)

一、常用命令:

在JDK的bin目彔下,包含了java命令及其他實用工具。

jps:檢視本機的Java中程序資訊。

jstack:列印執行緒的棧資訊,製作執行緒Dump。

jmap:列印記憶體對映,製作堆Dump。

jstat:效能監控工具。

jhat:記憶體分析工具。

jconsole:簡易的視覺化控制檯。

jvisualvm:功能強大的控制檯。

二、認識Java Dump:

什麼是Java Dump?

Java虛擬機器的執行時快照。將Java虛擬機器執行時的狀態和資訊儲存到檔案。

執行緒Dump,包含所有執行緒的執行狀態。純文字格式。

堆Dump,包含執行緒Dump,幵包含所有堆物件的狀態。二進位制格式。

Java Dump有什麼用?

補足傳統Bug分析手段的不足: 可在任何Java環境使用;資訊量充足。 針對非功能正確性的Bug,主要為:多執行緒幵發、記憶體洩漏。

製作Java Dump

使用Java虛擬機制作Dump

指示虛擬機器在發生記憶體不足錯誤時,自動生成堆Dump

-XX:+HeapDumpOnOutOfMemoryError

使用圖形化工具製作Dump

使用JDK(1.6)自帶的工具:Java VisualVM。

使用命令列製作Dump

jstack:列印執行緒的棧資訊,製作執行緒Dump。

jmap:列印記憶體對映,製作堆Dump。

步驟:

  1. 檢查虛擬機器版本(java -version)
  2. 找出目標Java應用的程序ID(jps)
  3. 使用jstack命令製作執行緒Dump• Linux環境下使用kill命令製作執行緒Dump
  4. 使用jmap命令製作堆Dump

檢視Java程序:jps

用法介紹

jps命令:顯示所有程序號和短的類名稱

Jps –q 命令:只顯示程序號

Jps –l 用於傳輸主函式的完整路徑

Jps –v 顯示傳遞給Java虛擬機器的引數(感覺這個命令才是完美,把虛擬機器的一些引數全都打印出來)

檢視執行緒堆疊命令:jstack命令

Jstack命令主要用來檢視Java執行緒的呼叫堆疊的,可以用來分析執行緒問題(如死鎖)。談到執行緒,在Java裡面,執行緒一共有6中狀態

  • New 新建 ————- 不會出現在dump中
  • Runnable 正在執行中——–在虛擬機器內執行
  • Blocked 阻塞————受阻塞,並等待監視器鎖
  • Waiting 等待————無限期等待另一個執行緒執行特定操作
  • Timed_waiting 超時等待————有時限等待另一個執行緒的操作
  • Terminated 終止/結束————已退出的

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

進入區(Entrt Set):

表示執行緒通過synchronized要求獲取物件的鎖。如果物件未被鎖住,則迚入擁有者;否則則在進入區等待。一旦物件鎖被其他執行緒釋放,立即參與競爭。

擁有者(The Owner):

表示某一執行緒成功競爭到物件鎖。

等待區(Wait Set):

表示執行緒通過物件的wait方法,釋放物件的鎖,並在等待區等待被喚醒。

從圖中可以看出,一個 Monitor在某個時刻,只能被一個執行緒擁有,該執行緒就是 “Active Thread”,而其它執行緒都是 “Waiting Thread”,分別在兩個佇列 “ Entry Set”和 “Wait Set”裡面等候。在 “Entry Set”中等待的執行緒狀態是 “Waiting for monitor entry”,而在 “Wait Set”中等待的執行緒狀態是 “in Object.wait()”。 先看 “Entry Set”裡面的執行緒。我們稱被 synchronized保護起來的程式碼段為臨界區。當一個執行緒申請進入臨界區時,它就進入了 “Entry Set”佇列。對應的 code就像:

synchronized(obj) {
.........

}

呼叫修飾
表示執行緒在方法呼叫時,額外的重要的操作。執行緒Dump分析的重要資訊。修飾上方的方法呼叫。
locked <地址> 目標:使用synchronized申請物件鎖成功,監視器的擁有者。
waiting to lock <地址> 目標:使用synchronized申請物件鎖未成功,在迚入區等待。
waiting on <地址> 目標:使用synchronized申請物件鎖成功後,釋放鎖幵在等待區等待。
parking to wait for <地址> 目標

locked

at oracle.jdbc.driver.PhysicalConnection.prepareStatement
- locked <0x00002aab63bf7f58> (a oracle.jdbc.driver.T4CConnection)
at oracle.jdbc.driver.PhysicalConnection.prepareStatement
- locked <0x00002aab63bf7f58> (a oracle.jdbc.driver.T4CConnection)
at com.jiuqi.dna.core.internal.db.datasource.PooledConnection.prepareStatement

通過synchronized關鍵字,成功獲取到了物件的鎖,成為監視器的擁有者,在臨界區內操作。物件鎖是可以執行緒重入的。

waiting to lock

at com.jiuqi.dna.core.impl.CacheHolder.isVisibleIn(CacheHolder.java:165)
- waiting to lock <0x0000000097ba9aa8> (a CacheHolder)
at com.jiuqi.dna.core.impl.CacheGroup$Index.findHolder
at com.jiuqi.dna.core.impl.ContextImpl.find
at com.jiuqi.dna.bap.basedata.common.util.BaseDataCenter.findInfo

通過synchronized關鍵字,沒有獲取到了物件的鎖,執行緒在監視器的進入區等待。在呼叫棧頂出現,執行緒狀態為Blocked。

waiting on

at java.lang.Object.wait(Native Method)
- waiting on <0x00000000da2defb0> (a WorkingThread)
at com.jiuqi.dna.core.impl.WorkingManager.getWorkToDo
- locked <0x00000000da2defb0> (a WorkingThread)
at com.jiuqi.dna.core.impl.WorkingThread.run

通過synchronized關鍵字,成功獲取到了物件的鎖後,呼叫了wait方法,進入物件的等待區等待。在呼叫棧頂出現,執行緒狀態為WAITING或TIMED_WATING。

parking to wait for
park是基本的執行緒阻塞原語,不通過監視器在物件上阻塞。隨concurrent包會出現的新的機制,不synchronized體系不同。

執行緒動作
執行緒狀態產生的原因
runnable:狀態一般為RUNNABLE。
in Object.wait():等待區等待,狀態為WAITING或TIMED_WAITING。
waiting for monitor entry:進入區等待,狀態為BLOCKED。
waiting on condition:等待區等待、被park。
sleeping:休眠的執行緒,呼叫了Thread.sleep()。

Wait on condition 該狀態出現線上程等待某個條件的發生。具體是什麼原因,可以結合 stacktrace來分析。 最常見的情況就是執行緒處於sleep狀態,等待被喚醒。 常見的情況還有等待網路IO:在java引入nio之前,對於每個網路連線,都有一個對應的執行緒來處理網路的讀寫操作,即使沒有可讀寫的資料,執行緒仍然阻塞在讀寫操作上,這樣有可能造成資源浪費,而且給作業系統的執行緒排程也帶來壓力。在 NewIO裡採用了新的機制,編寫的伺服器程式的效能和可擴充套件性都得到提高。 正等待網路讀寫,這可能是一個網路瓶頸的徵兆。因為網路阻塞導致執行緒無法執行。一種情況是網路非常忙,幾 乎消耗了所有的頻寬,仍然有大量資料等待網路讀 寫;另一種情況也可能是網路空閒,但由於路由等問題,導致包無法正常的到達。所以要結合系統的一些效能觀察工具來綜合分析,比如 netstat統計單位時間的傳送包的數目,如果很明顯超過了所在網路頻寬的限制 ; 觀察 cpu的利用率,如果系統態的 CPU時間,相對於使用者態的 CPU時間比例較高;如果程式執行在 Solaris 10平臺上,可以用 dtrace工具看系統呼叫的情況,如果觀察到 read/write的系統呼叫的次數或者執行時間遙遙領先;這些都指向由於網路頻寬所限導致的網路瓶頸。(來自http://www.blogjava.net/jzone/articles/303979.html

jstack 命令詳解

簡單介紹:

F當’jstack [-l] pid’沒有相應的時候強制列印棧資訊 -l長列表. 列印關於鎖的附加資訊,例如屬於java.util.concurrent的ownable synchronizers列表. -m列印java和native c/c++框架的所有棧資訊. -h | -help列印幫助資訊 pid 需要被列印配置資訊的java程序id,可以用jps查詢.

第一個實戰程式碼:

/**
 * Created by Cser_W on 2018/7/10.
 */
public class JstackDemo {
    public static void main(String[] args){
        while (true) {
            //do nothing
        }
    }
}

先利用 jps 檢視程序號

利用jstack 程序號檢視執行緒堆疊資訊,如果發現自己寫的程式碼一直處於Runnable狀態,這有很大可能是自己寫了個死迴圈。
第二個實戰程式碼

/**
 * Created by Cser_W on 2018/7/10.
 */
public class JstackDemo1 {
    public static void main(String[] args){
        Thread thread = new Thread(new Thread1());
        thread.start();
    }
}
class Thread1 extends Thread {
    @Override
    public void run(){
        while (true) {
            System.out.println(1);
        }
    }
}


我們能看到:
執行緒的狀態: WAITING 執行緒的呼叫棧 執行緒的當前鎖住的資源: < < <0x00000000da380ee0>>> 執行緒當前等待的資源:< < <0x00000000da380ee0>>>

為什麼同時鎖住的等待同一個資源:
執行緒的執行中,先獲得了這個物件的 Monitor(對應於 locked < <0x00000000da380ee0>>)。當執行到 obj.wait(), 執行緒即放棄了 Monitor的所有權,進入 “wait set”佇列(對應於 waiting on < <0x00000000da380ee0>> )。

死鎖模擬實戰

package com.wxy.test;

/**
 * Created by Cser_W on 2018/7/10.
 */
public class JstackDemo2 {
    public static void main(String[] args){
        Thread thread1 = new Thread(new DeadLockClass(true));
        Thread thread2 = new Thread((new DeadLockClass(false)));
        thread1.start();
        thread2.start();
    }
}
class DeadLockClass implements Runnable {
    public boolean flag;
    DeadLockClass(boolean flag) {
        this.flag = flag;
    }
    @Override
    public void run() {
        if (flag) {
            while (true) {
                synchronized (Suo.o1) {
                    System.out.println("o1" + Thread.currentThread().getName());
                    synchronized (Suo.o2) {
                        System.out.println("o2" + Thread.currentThread().getName());
                    }
                }
            }
        } else {
            while (true) {
                synchronized (Suo.o2) {
                    System.out.println("o2" + Thread.currentThread().getName());
                    synchronized (Suo.o1) {
                        System.out.println("o1" + Thread.currentThread().getName());
                    }
                }
            }
        }
    }
}
class Suo {
    static Object o1 = new Object();
    static Object o2 = new Object();
}

執行輸出:

上圖已經鎖死,只要兩個執行緒都啟動起來,必定會發生死鎖。這個時候趕緊拿jstack練手了
用jstack命令顯示:

列印記憶體對映,製作堆Dump命令:Jmap

堆map的概述

堆Dump是反應Java堆使用情況的記憶體映象,其中主要包括系統資訊、虛擬機器屬性、完整的執行緒Dump、所有類和物件的狀態等。 一般,在記憶體不足、GC異常等情況下,我們就會懷疑有記憶體洩露。這個時候我們就可以製作堆Dump來檢視具體情況

用法摘要

Usage:
    jmap [option] <pid>
        (to connect to running process)
    jmap [option] <executable <core>
        (to connect to a core file)
    jmap [option] [server_id@]<remote server IP or hostname>
        (to connect to remote debug server)

where <option> is one of:
    <none>               to print same info as Solaris pmap
    -heap                to print java heap summary
    -histo[:live]        to print histogram of java object heap; if the "live"
                         suboption is specified, only count live objects
    -permstat            to print permanent generation statistics
    -finalizerinfo       to print information on objects awaiting finalization
    -dump:<dump-options> to dump java heap in hprof binary format
                         dump-options:
                           live         dump only live objects; if not specified,
                                        all objects in the heap are dumped.
                           format=b     binary format
                           file=<file>  dump heap to <file>
                         Example: jmap -dump:live,format=b,file=heap.bin <pid>
    -F                   force. Use with -dump:<dump-options> <pid> or -histo
                         to force a heap dump or histogram when <pid> does not
                         respond. The "live" suboption is not supported
                         in this mode.
    -h | -help           to print this help message
    -J<flag>             to pass <flag> directly to the runtime system

指定程序號(pid)的程序 jmap [ option ] 指定核心檔案 jmap [ option ] 指定遠端除錯伺服器jmap [ option ] [server-id@]
引數:
option 選項引數是互斥的(不可同時使用)。想要使用選項引數,直接跟在命令名稱後即可。
pid 需要列印配置資訊的程序ID。該程序必須是一個Java程序。想要獲取執行的Java程序列表,你可以使用jps。
executable 產生核心dump的Java可執行檔案。
core 需要列印配置資訊的核心檔案。
remote-hostname-or-IP 遠端除錯伺服器的(請檢視jsadebugd)主機名或IP地址。
server-id 可選的唯一id,如果相同的遠端主機上運行了多臺除錯伺服器,用此選項引數標識伺服器。
選項:
如果使用不帶選項引數的jmap列印共享物件對映,將會列印目標虛擬機器中載入的每個共享物件的起始地址、對映大小以及共享物件檔案的路徑全稱。這與Solaris的pmap工具比較相似。
-dump:[live,]format=b,file= 以hprof二進位制格式轉儲Java堆到指定filename的檔案中。live子選項是可選的。如果指定了live子選項,堆中只有活動的物件會被轉儲。想要瀏覽heap dump,你可以使用jhat(Java堆分析工具)讀取生成的檔案。
-finalizerinfo 列印等待終結的物件資訊。
-heap 列印一個堆的摘要資訊,包括使用的GC演算法、堆配置資訊和generation wise heap usage。
-histo[:live] 列印堆的柱狀圖。其中包括每個Java類、物件數量、記憶體大小(單位:位元組)、完全限定的類名。列印的虛擬機器內部的類名稱將會帶有一個’*’字首。如果指定了live子選項,則只計算活動的物件。
-permstat 列印Java堆記憶體的永久儲存區域的類載入器的智慧統計資訊。對於每個類載入器而言,它的名稱、活躍度、地址、父類載入器、它所載入的類的數量和大小都會被列印。此外,包含的字串數量和大小也會被列印。
-F 強制模式。如果指定的pid沒有響應,請使用jmap -dump或jmap -histo選項。此模式下,不支援live子選項。
-h 列印幫助資訊。
-help 列印幫助資訊。
-J 指定傳遞給執行jmap的JVM的引數。

檢視java 堆(heap)使用情況,執行命令:

Jmap –heap pid

檢視堆記憶體(histogram)中的物件數量及大小。執行命令:

Jmap –histo pid

總結:
1. 如果程式記憶體不足或者頻繁GC,很有可能存在記憶體洩露情況,這時候就要藉助Java堆Dump檢視物件的情況。
2.要製作堆Dump可以直接使用jvm自帶的jmap命令
3.可以先使用jmap -heap命令檢視堆的使用情況,看一下各個堆空間的佔用情況。
4.使用jmap -histo:[live]檢視堆記憶體中的物件的情況。如果有大量物件在持續被引用,並沒有被釋放掉,那就產生了記憶體洩露,就要結合程式碼,把不用的物件釋放掉。
5.也可以使用 jmap -dump:format=b,file=命令將堆資訊儲存到一個檔案中,再借助jhat命令檢視詳細內容
6.在記憶體出現洩露、溢位或者其它前提條件下,建議多dump幾次記憶體,把記憶體檔案進行編號歸檔,便於後續記憶體整理分析。

效能監控工具命令:jstat

用法講解

jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]

引數解釋:
Option — 選項,我們一般使用 -gcutil 檢視gc情況
vmid — VM的程序號,即當前執行的java程序號
interval– 間隔時間,單位為秒或者毫秒
count — 列印次數,如果預設則列印無數次
引數 interval 和 count 代表查詢間隔和次數,如果省略這兩個引數,說明只查詢一次。

示例:
Jstat –gc 4100 250 5

上圖中引數的意思:

S0C 年輕代中第一個survivor(倖存區)的容量 (位元組)
S0U 年輕代中第一個survivor(倖存區)目前已使用空間 (位元組)
EC 年輕代中Eden(伊甸園)的容量 (位元組)
EU 年輕代中Eden(伊甸園)目前已使用空間 (位元組)
OU Old代目前已使用空間 (位元組)
PC Perm(持久代)的容量 (位元組)
PU Perm(持久代)目前已使用空間 (位元組)
YGC 從應用程式啟動到取樣時年輕代中gc次數
FGC 從應用程式啟動到取樣時old代(全gc)gc次數
FGCT 從應用程式啟動到取樣時old代(全gc)gc所用時間(s)
GCT 從應用程式啟動到取樣時gc用的總時間(s)

Jstat –class 4100 250 5 顯示載入class的數量,及所佔空間等資訊。

Loaded 裝載的類的數量
Unloaded 解除安裝類的數量
Bytes 解除安裝類的位元組數
Time 裝載和解除安裝類所花費的時間

jstat -compiler 顯示VM實時編譯的數量等資訊

Compiled 編譯任務執行數量
Invalid 編譯任務執行失效數量
Time 編譯任務消耗時間
FailedType 最後一個編譯失敗任務的型別
FailedMethod 最後一個編譯失敗任務所在的類及方法

Jstat –gccapacity 4100

NGCMN 年輕代(young)中初始化(最小)的大小(位元組)
NGC 年輕代(young)中當前的容量 (位元組)
S0C 年輕代中第一個survivor(倖存區)的容量 (位元組)
S1C 年輕代中第二個survivor(倖存區)的容量 (位元組)
EC 年輕代中Eden(伊甸園)的容量 (位元組)
OGCMN old代中初始化(最小)的大小 (位元組)
OGCMX old代的最大容量(位元組)
OGC old代當前新生成的容量 (位元組)
OC Old代的容量 (位元組)
PGCMN perm代中初始化(最小)的大小 (位元組)
PGCMX perm代的最大容量 (位元組)
PGC perm代當前新生成的容量 (位元組)
PC Perm(持久代)的容量 (位元組)
YGC 從應用程式啟動到取樣時年輕代中gc次數
FGC 從應用程式啟動到取樣時old代(全gc)gc次數

jstack是java虛擬機器自帶的一種堆疊跟蹤工具。

功能

jstack用於生成java虛擬機器當前時刻的執行緒快照。執行緒快照是當前java虛擬機器內每一條執行緒正在執行的方法堆疊的集合,生成執行緒快照的主要目的是定位執行緒出現長時間停頓的原因,如執行緒間死鎖、死迴圈、請求外部資源導致的長時間等待等。 執行緒出現停頓的時候通過jstack來檢視各個執行緒的呼叫堆疊,就可以知道沒有響應的執行緒到底在後臺做什麼事情,或者等待什麼資源。 如果java程式崩潰生成core檔案,jstack工具可以用來獲得core檔案的java stack和native stack的資訊,從而可以輕鬆地知道java程式是如何崩潰和在程式何處發生問題。另外,jstack工具還可以附屬到正在執行的java程式中,看到當時執行的java程式的java stack和native stack的資訊, 如果現在執行的java程式呈現hung的狀態,jstack是非常有用的。

So,jstack命令主要用來檢視Java執行緒的呼叫堆疊的,可以用來分析執行緒問題(如死鎖)。

執行緒狀態

想要通過jstack命令來分析執行緒的情況的話,首先要知道執行緒都有哪些狀態,下面這些狀態是我們使用jstack命令檢視執行緒堆疊資訊時可能會看到的執行緒的幾種狀態:

NEW,未啟動的。不會出現在Dump中。

RUNNABLE,在虛擬機器內執行的。

BLOCKED,受阻塞並等待監視器鎖。

WATING,無限期等待另一個執行緒執行特定操作。

TIMED_WATING,有時限的等待另一個執行緒的特定操作。

TERMINATED,已退出的。

Monitor

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

進入區(Entrt Set):表示執行緒通過synchronized要求獲取物件的鎖。如果物件未被鎖住,則迚入擁有者;否則則在進入區等待。一旦物件鎖被其他執行緒釋放,立即參與競爭。

擁有者(The Owner):表示某一執行緒成功競爭到物件鎖。

等待區(Wait Set):表示執行緒通過物件的wait方法,釋放物件的鎖,並在等待區等待被喚醒。

從圖中可以看出,一個 Monitor在某個時刻,只能被一個執行緒擁有,該執行緒就是“Active Thread”,而其它執行緒都是“Waiting Thread”,分別在兩個佇列“ Entry Set”“Wait Set”裡面等候。在“Entry Set”中等待的執行緒狀態是“Waiting for monitor entry”,而在“Wait Set”中等待的執行緒狀態是“in Object.wait()”。 先看 “Entry Set”裡面的執行緒。我們稱被 synchronized保護起來的程式碼段為臨界區。當一個執行緒申請進入臨界區時,它就進入了 “Entry Set”佇列。對應的 code就像:

synchronized(obj) {
.........

}

呼叫修飾

表示執行緒在方法呼叫時,額外的重要的操作。執行緒Dump分析的重要資訊。修飾上方的方法呼叫。

locked <地址> 目標:使用synchronized申請物件鎖成功,監視器的擁有者。

waiting to lock <地址> 目標:使用synchronized申請物件鎖未成功,在迚入區等待。

waiting on <地址> 目標:使用synchronized申請物件鎖成功後,釋放鎖幵在等待區等待。

parking to wait for <地址> 目標

locked

at oracle.jdbc.driver.PhysicalConnection.prepareStatement
- locked <0x00002aab63bf7f58> (a oracle.jdbc.driver.T4CConnection)
at oracle.jdbc.driver.PhysicalConnection.prepareStatement
- locked <0x00002aab63bf7f58> (a oracle.jdbc.driver.T4CConnection)
at com.jiuqi.dna.core.internal.db.datasource.PooledConnection.prepareStatement

通過synchronized關鍵字,成功獲取到了物件的鎖,成為監視器的擁有者,在臨界區內操作。物件鎖是可以執行緒重入的。

waiting to lock

at com.jiuqi.dna.core.impl.CacheHolder.isVisibleIn(CacheHolder.java:165)
- waiting to lock <0x0000000097ba9aa8> (a CacheHolder)
at com.jiuqi.dna.core.impl.CacheGroup$Index.findHolder
at com.jiuqi.dna.core.impl.ContextImpl.find
at com.jiuqi.dna.bap.basedata.common.util.BaseDataCenter.findInfo

通過synchronized關鍵字,沒有獲取到了物件的鎖,執行緒在監視器的進入區等待。在呼叫棧頂出現,執行緒狀態為Blocked。

waiting on

at java.lang.Object.wait(Native Method)
- waiting on <0x00000000da2defb0> (a WorkingThread)
at com.jiuqi.dna.core.impl.WorkingManager.getWorkToDo
- locked <0x00000000da2defb0> (a WorkingThread)
at com.jiuqi.dna.core.impl.WorkingThread.run

通過synchronized關鍵字,成功獲取到了物件的鎖後,呼叫了wait方法,進入物件的等待區等待。在呼叫棧頂出現,執行緒狀態為WAITING或TIMED_WATING。

parking to wait for

park是基本的執行緒阻塞原語,不通過監視器在物件上阻塞。隨concurrent包會出現的新的機制,不synchronized體系不同。

執行緒動作

執行緒狀態產生的原因

runnable:狀態一般為RUNNABLE。

in Object.wait():等待區等待,狀態為WAITING或TIMED_WAITING。

waiting for monitor entry:進入區等待,狀態為BLOCKED。

waiting on condition:等待區等待、被park。

sleeping:休眠的執行緒,呼叫了Thread.sleep()。

Wait on condition該狀態出現線上程等待某個條件的發生。具體是什麼原因,可以結合 stacktrace來分析。 最常見的情況就是執行緒處於sleep狀態,等待被喚醒。 常見的情況還有等待網路IO:在java引入nio之前,對於每個網路連線,都有一個對應的執行緒來處理網路的讀寫操作,即使沒有可讀寫的資料,執行緒仍然阻塞在讀寫操作上,這樣有可能造成資源浪費,而且給作業系統的執行緒排程也帶來壓力。在 NewIO裡採用了新的機制,編寫的伺服器程式的效能和可擴充套件性都得到提高。 正等待網路讀寫,這可能是一個網路瓶頸的徵兆。因為網路阻塞導致執行緒無法執行。一種情況是網路非常忙,幾 乎消耗了所有的頻寬,仍然有大量資料等待網路讀 寫;另一種情況也可能是網路空閒,但由於路由等問題,導致包無法正常的到達。所以要結合系統的一些效能觀察工具來綜合分析,比如 netstat統計單位時間的傳送包的數目,如果很明顯超過了所在網路頻寬的限制 ; 觀察 cpu的利用率,如果系統態的 CPU時間,相對於使用者態的 CPU時間比例較高;如果程式執行在 Solaris 10平臺上,可以用 dtrace工具看系統呼叫的情況,如果觀察到 read/write的系統呼叫的次數或者執行時間遙遙領先;這些都指向由於網路頻寬所限導致的網路瓶頸。(來自http://www.blogjava.net/jzone/articles/303979.html

執行緒Dump的分析

原則

結合程式碼閱讀的推理。需要執行緒Dump和原始碼的相互推導和印證。

造成Bug的根源往往丌會在呼叫棧上直接體現,一定格外注意執行緒當前呼叫之前的所有呼叫。

入手點

進入區等待

"d&a-3588" daemon waiting for monitor entry [0x000000006e5d5000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.jiuqi.dna.bap.authority.service.UserService$LoginHandler.handle()
- waiting to lock <0x0000000602f38e90> (a java.lang.Object)
at com.jiuqi.dna.bap.authority.service.UserService$LoginHandler.handle()

執行緒狀態BLOCKED,執行緒動作wait on monitor entry,呼叫修飾waiting to lock總是一起出現。表示在程式碼級別已經存在衝突的呼叫。必然有問題的程式碼,需要儘可能減少其發生。

同步塊阻塞

一個執行緒鎖住某物件,大量其他執行緒在該物件上等待。

"blocker" runnable
java.lang.Thread.State: RUNNABLE
at com.jiuqi.hcl.javadump.Blocker$1.run(Blocker.java:23)
- locked <0x00000000eb8eff68> (a java.lang.Object)
"blockee-11" waiting for monitor entry
java.lang.Thread.State: BLOCKED (on object monitor)
at com.jiuqi.hcl.javadump.Blocker$2.run(Blocker.java:41)
- waiting to lock <0x00000000eb8eff68> (a java.lang.Object)
"blockee-86" waiting for monitor entry
java.lang.Thread.State: BLOCKED (on object monitor)
at com.jiuqi.hcl.javadump.Blocker$2.run(Blocker.java:41)
- waiting to lock <0x00000000eb8eff68> (a java.lang.Object)

持續執行的IOIO操作是可以以RUNNABLE狀態達成阻塞。例如:資料庫死鎖、網路讀寫。 格外注意對IO執行緒的真實狀態的分析。 一般來說,被捕捉到RUNNABLE的IO呼叫,都是有問題的。

以下堆疊顯示: 執行緒狀態為RUNNABLE。 呼叫棧在SocketInputStream或SocketImpl上,socketRead0等方法。 呼叫棧包含了jdbc相關的包。很可能發生了資料庫死鎖

"d&a-614" daemon prio=6 tid=0x0000000022f1f000 nid=0x37c8 runnable
[0x0000000027cbd000]
java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.read(Unknown Source)
at oracle.net.ns.Packet.receive(Packet.java:240)
at oracle.net.ns.DataPacket.receive(DataPacket.java:92)
at oracle.net.ns.NetInputStream.getNextPacket(NetInputStream.java:172)
at oracle.net.ns.NetInputStream.read(NetInputStream.java:117)
at oracle.jdbc.driver.T4CMAREngine.unmarshalUB1(T4CMAREngine.java:1034)
at oracle.jdbc.driver.T4C8Oall.receive(T4C8Oall.java:588)

分執行緒排程的休眠

正常的執行緒池等待

"d&a-131" in Object.wait()
java.lang.Thread.State: TIMED_WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
at com.jiuqi.dna.core.impl.WorkingManager.getWorkToDo(WorkingManager.java:322)
- locked <0x0000000313f656f8> (a com.jiuqi.dna.core.impl.WorkingThread)
at com.jiuqi.dna.core.impl.WorkingThread.run(WorkingThread.java:40)

可疑的執行緒等待

"d&a-121" in Object.wait()
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:485)
at com.jiuqi.dna.core.impl.AcquirableAccessor.exclusive()
- locked <0x00000003011678d8> (a com.jiuqi.dna.core.impl.CacheGroup)
at com.jiuqi.dna.core.impl.Transaction.lock()

入手點總結

wait on monitor entry:被阻塞的,肯定有問題

runnable: 注意IO執行緒

in Object.wait(): 注意非執行緒池等待

使用

想要學習一個命令,先來看看幫助,使用jstack -help檢視幫助:

hollis@hos:~$ jstack -help
Usage:
    jstack [-l] <pid>
        (to connect to running process)
    jstack -F [-m] [-l] <pid>
        (to connect to a hung process)
    jstack [-m] [-l] <executable> <core>
        (to connect to a core file)
    jstack [-m] [-l] [server_id@]<remote server IP or hostname>
        (to connect to a remote debug server)

Options:
    -F  to force a thread dump. Use when jstack <pid> does not respond (process is hung)
    -m  to print both java and native frames (mixed mode)
    -l  long listing. Prints additional information about locks
    -h or -help to print this help message

-F當’jstack [-l] pid’沒有相應的時候強制列印棧資訊-l長列表. 列印關於鎖的附加資訊,例如屬於java.util.concurrent的ownable synchronizers列表.-m列印java和native c/c++框架的所有棧資訊.-h| -help列印幫助資訊pid需要被列印配置資訊的java程序id,可以用jps查詢.

首先,我們分析這麼一段程式的執行緒情況:

/**
 * @author hollis
 */
public class JStackDemo1 {
    public static void main(String[] args) {
        while (true) {
            //Do Nothing
        }
    }
}

先是有jps檢視程序號:

hollis@hos:~$ jps
29788 JStackDemo1
29834 Jps
22385 org.eclipse.equinox.launcher_1.3.0.v20130327-1440.jar

然後使用jstack 檢視堆疊資訊:

hollis@hos:~$ jstack 29788
2015-04-17 23:47:31
...此處省略若干內容...
"main" prio=10 tid=0x00007f197800a000 nid=0x7462 runnable [0x00007f197f7e1000]
   java.lang.Thread.State: RUNNABLE
    at javaCommand.JStackDemo1.main(JStackDemo1.java:7)

我們可以從這段堆疊資訊中看出什麼來呢?我們可以看到,當前一共有一條使用者級別執行緒,執行緒處於runnable狀態,執行到JStackDemo1.java的第七行。 看下面程式碼:

/**
 * @author hollis
 */
public class JStackDemo1 {
    public static void main(String[] args) {
        Thread thread = new Thread(new Thread1());
        thread.start();
    }
}
class Thread1 implements Runnable{
    @Override
    public void run() {
        while(true){
            System.out.println(1);
        }
    }
}

執行緒堆疊資訊如下:

"Reference Handler" daemon prio=10 tid=0x00007fbbcc06e000 nid=0x286c in Object.wait() [0x00007fbbc8dfc000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x0000000783e066e0> (a java.lang.ref.Reference$Lock)
    at java.lang.Object.wait(Object.java:503)
    at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:133)
    - locked <0x0000000783e066e0> (a java.lang.ref.Reference$Lock)

我們能看到:

執行緒的狀態: WAITING 執行緒的呼叫棧 執行緒的當前鎖住的資源: <0x0000000783e066e0> 執行緒當前等待的資源:<0x0000000783e066e0>

為什麼同時鎖住的等待同一個資源:

執行緒的執行中,先獲得了這個物件的 Monitor(對應於 locked <0x0000000783e066e0>)。當執行到 obj.wait(), 執行緒即放棄了 Monitor的所有權,進入 “wait set”佇列(對應於 waiting on <0x0000000783e066e0> )。

死鎖分析

學會了怎麼使用jstack命令之後,我們就可以看看,如何使用jstack分析死鎖了,這也是我們一定要掌握的內容。啥叫死鎖?所謂死鎖: 是指兩個或兩個以上的程序在執行過程中,由於競爭資源或者由於彼此通訊而造成的一種阻塞的現象,若無外力作用,它們都將無法推進下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的程序稱為死鎖程序。 說白了,我現在想吃雞蛋灌餅,桌子上放著雞蛋和餅,但是我和我的朋友同時分別拿起了雞蛋和病,我手裡拿著雞蛋,但是我需要他手裡的餅。他手裡拿著餅,但是他想要我手裡的雞蛋。就這樣,如果不能同時拿到雞蛋和餅,那我們就不能繼續做後面的工作(做雞蛋灌餅)。所以,這就造成了死鎖。看一段死鎖的程式:

package javaCommand;
/**
 * @author hollis
 */
public class JStackDemo {
    public static void main(String[] args) {
        Thread t1 = new Thread(new DeadLockclass(true));//建立一個執行緒
        Thread t2 = new Thread(new DeadLockclass(false));//建立另一個執行緒
        t1.start();//啟動一個執行緒
        t2.start();//啟動另一個執行緒
    }
}
class DeadLockclass implements Runnable {
    public boolean falg;// 控制執行緒
    DeadLockclass(boolean falg) {
        this.falg = falg;
    }
    public void run() {
        /**
         * 如果falg的值為true則呼叫t1執行緒
         */
        if (falg) {
            while (true) {
                synchronized (Suo.o1) {
                    System.out.println("o1 " + Thread.currentThread().getName());
                    synchronized (Suo.o2) {
                        System.out.println("o2 " + Thread.currentThread().getName());
                    }
                }
            }
        }
        /**
         * 如果falg的值為false則呼叫t2執行緒
         */
        else {
            while (true) {
                synchronized (Suo.o2) {
                    System.out.println("o2 " + Thread.currentThread().getName());
                    synchronized (Suo.o1) {
                        System.out.println("o1 "+Thread.currentThread().getName());}}}}}}classSuo{staticObject o1 =newObject();staticObject o2 =newObject();}

當我啟動該程式時,我們看一下控制檯:

我們發現,程式只輸出了兩行內容,然後程式就不再列印其它的東西了,但是程式並沒有停止。這樣就產生了死鎖。 當執行緒1使用synchronized鎖住了o1的同時,執行緒2也是用synchronized鎖住了o2。當兩個執行緒都執行完第一個列印任務的時候,執行緒1想鎖住o2,執行緒2想鎖住o1。但是,執行緒1當前鎖著o1,執行緒2鎖著o2。所以兩個想成都無法繼續執行下去,就造成了死鎖。

然後,我們使用jstack來看一下執行緒堆疊資訊:

Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x00007f0134003ae8 (object 0x00000007d6aa2c98, a java.lang.Object),
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x00007f0134006168 (object 0x00000007d6aa2ca8, a java.lang.Object),
  which is held by "Thread-1"

Java stack information for the threads listed above:
===================================================
"Thread-1":
    at javaCommand.DeadLockclass.run(JStackDemo.java:40)
    - waiting to lock <0x00000007d6aa2c98> (a java.lang.Object)
    - locked <0x00000007d6aa2ca8> (a java.lang.Object)
    at java.lang.Thread.run(Thread.java:745)
"Thread-0":
    at javaCommand.DeadLockclass.run(JStackDemo.java:27)
    - waiting to lock <0x00000007d6aa2ca8> (a java.lang.Object)
    - locked <0x00000007d6aa2c98> (a java.lang.Object)
    at java.lang.Thread.run(Thread.java:745)

Found 1 deadlock.

哈哈,堆疊寫的很明顯,它告訴我們Found one Java-level deadlock,然後指出造成死鎖的兩個執行緒的內容。然後,又通過Java stack information for the threads listed above來顯示更詳細的死鎖的資訊。 他說

Thread-1在想要執行第40行的時候,當前鎖住了資源<0x00000007d6aa2ca8>,但是他在等待資源<0x00000007d6aa2c98>Thread-0在想要執行第27行的時候,當前鎖住了資源<0x00000007d6aa2c98>,但是他在等待資源<0x00000007d6aa2ca8>由於這兩個執行緒都持有資源,並且都需要對方的資源,所以造成了死鎖。 原因我們找到了,就可以具體問題具體分析,解決這個死鎖了。

其他

虛擬機器執行Full GC時,會阻塞所有的使用者執行緒。因此,即時獲取到同步鎖的執行緒也有可能被阻塞。在檢視執行緒Dump時,首先檢視記憶體使用情況。

Jmap

jmap是JDK自帶的工具軟體,主要用於列印指定Java過程(或核心檔案,遠端除錯伺服器)的共享物件記憶體對映或堆記憶體細節。可以使用jmap生成堆轉儲。在Java命令學習系列(零) -常見命令及Java轉儲介紹狀語從句:Java的命令學習系列(二) - Jstack。中分別有關於Java轉儲以及執行緒轉儲介紹的這篇文章主要介紹的Java的堆轉儲以及JAMP命令

什麼是堆

堆Dump是反應Java堆使用情況的記憶體容量,其中主要包括系統資訊虛擬機器屬性完整的執行緒Dump所有類和物件的狀態等。一般,在記憶體不足,GC異常等情況下,我們就會懷疑有記憶體容量。這個時候我們就可以製作堆Dump來檢視具體情況。分析原因。

基礎知識

Java虛擬機器的記憶體組成以及堆記憶體介紹Java GC工作原理常見記憶體錯誤:

的OutOfMemoryError年老代記憶體不足。
的OutOfMemoryError:PermGen的空間永久代記憶體不足。
OutOfMemoryError異常:GC開銷超限垃圾回收時間佔用系統執行時間的98%或以上。

對映

用法摘要

Usage:
    jmap [option] <pid>
        (to connect to running process)
    jmap [option] <executable <core>
        (to connect to a core file)
    jmap [option] [server_id@]<remote server IP or hostname>
        (to connect to remote debug server)

where <option> is one of:
    <none>               to print same info as Solaris pmap
    -heap                to print java heap summary
    -histo[:live]        to print histogram of java object heap; if the "live"
                         suboption is specified, only count live objects
    -permstat            to print permanent generation statistics
    -finalizerinfo       to print information on objects awaiting finalization
    -dump:<dump-options> to dump java heap in hprof binary format
                         dump-options:
                           live         dump only live objects; if not specified,
                                        all objects in the heap are dumped.
                           format=b     binary format
                           file=<file>  dump heap to <file>
                         Example: jmap -dump:live,format=b,file=heap.bin <pid>
    -F                   force. Use with -dump:<dump-options> <pid> or -histo
                         to force a heap dump or histogram when <pid> does not
                         respond. The "live" suboption is not supported
                         in this mode.
    -h | -help           to print this help message
    -J<flag>             to pass <flag> directly to the runtime system

指定程序號(pid)的程序jmap [選項]指定核心檔案jmap [選項]指定遠端除錯伺服器jmap [選項] [server-id @]


引數:

選項選項引數是互斥的(不可同時使用)。想要使用選項引數,直接跟在命令名稱後即可。
PID需要列印配置資訊的程序ID。該程序必須是一個爪哇程序。想要獲取執行的Java程序列表,您可以使用jps。
可執行檔案生成
核心轉儲的Java替換檔案。core需要列印配置資訊的核心檔案。remote
-hostname-or-IP遠端除錯伺服器的(請檢視jsadebugd)主機名或IP地址。
server-id可選的唯一id,如果相同的遠端主機上運行了多臺除錯伺服器,用此選項引數標識伺服器。

選項:

<no option>如果使用不帶選項引數的jmap列印共享物件對映,將列印目標虛擬機器中載入的每個共享物件的起始地址,對映大小以及共享物件檔案的路徑全稱。這與Solaris的pmap工具比較相似。
-dump:[live,]format=b,file=<filename>以hprof二進位制格式轉儲Java堆到指定filename的檔案中。live子選項是可選的。如果指定了live子選項,堆中只有活動的物件會被轉儲。想要瀏覽堆轉儲,你可以使用jhat (Java堆分析工具)讀取生成的檔案。
-finalizerinfo列印等待終結的物件資訊。
-heap列印一個堆的摘要資訊,包括使用的GC演算法,堆配置資訊和生成明智的堆用法。
-histo[:live]列印堆的柱狀圖。其中包括每一個Java類,物件數量,記憶體大小(單位:位元組),完全限定的類名。列印的虛擬機器內部的類名稱將帶有一個'*'替換。如果指定了live子選項,則只計算活動的物件。
-permstat列印Java堆記憶體的永久儲存區域的類載入器的智慧統計資訊。對於每個類載入器而言,它的名稱,活動度,地址,父類載入器,其所載入的類的數量和大小都會被列印。此外,包含的字串數量和大小也會被列印
-F強制模式。如果指定的PID沒有響應,請使用JMAP突降或JMAP -histo選項。此模式下,不支援實時子選項。
-h列印幫助資訊。
-help列印幫助資訊。
-J<flag>指定傳遞給執行jmap的JVM的引數。

體現

檢視java堆(heap)使用情況,執行命令:hollis@hos:~/workspace/design_apaas/apaasweb/control/bin$ jmap -heap 31846

Attaching to process ID 31846, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 24.71-b01

using thread-local object allocation.
Parallel GC with 4 thread(s)//GC 方式

Heap Configuration: //堆記憶體初始化配置
   MinHeapFreeRatio = 0 //對應jvm啟動引數-XX:MinHeapFreeRatio設定JVM堆最小空閒比率(default 40)
   MaxHeapFreeRatio = 100 //對應jvm啟動引數 -XX:MaxHeapFreeRatio設定JVM堆最大空閒比率(default 70)
   MaxHeapSize      = 2082471936 (1986.0MB) //對應jvm啟動引數-XX:MaxHeapSize=設定JVM堆的最大大小
   NewSize          = 1310720 (1.25MB)//對應jvm啟動引數-XX:NewSize=設定JVM堆的‘新生代’的預設大小
   MaxNewSize       = 17592186044415 MB//對應jvm啟動引數-XX:MaxNewSize=設定JVM堆的‘新生代’的最大大小
   OldSize          = 5439488 (5.1875MB)//對應jvm啟動引數-XX:OldSize=<value>:設定JVM堆的‘老生代’的大小
   NewRatio         = 2 //對應jvm啟動引數-XX:NewRatio=:‘新生代’和‘老生代’的大小比率
   SurvivorRatio    = 8 //對應jvm啟動引數-XX:SurvivorRatio=設定年輕代中Eden區與Survivor區的大小比值 
   PermSize         = 21757952 (20.75MB)  //對應jvm啟動引數-XX:PermSize=<value>:設定JVM堆的‘永生代’的初始大小
   MaxPermSize      = 85983232 (82.0MB)//對應jvm啟動引數-XX:MaxPermSize=<value>:設定JVM堆的‘永生代’的最大大小
   G1HeapRegionSize = 0 (0.0MB)

Heap Usage://堆記憶體使用情況
PS Young Generation
Eden Space://Eden區記憶體分佈
   capacity = 33030144 (31.5MB)//Eden區總容量
   used     = 1524040 (1.4534378051757812MB)  //Eden區已使用
   free     = 31506104 (30.04656219482422MB)  //Eden區剩餘容量
   4.614088270399305% used //Eden區使用比率
From Space:  //其中一個Survivor區的記憶體分佈
   capacity = 5242880 (5.0MB)
   used     = 0 (0.0MB)
   free     = 5242880 (5.0MB)
   0.0% used
To Space:  //另一個Survivor區的記憶體分佈
   capacity = 5242880 (5.0MB)
   used     = 0(0.0MB)
   free     =5242880(5.0MB)0.0% used
PS OldGeneration//當前的Old區記憶體分佈
   capacity =86507520(82.5MB)
   used     =0(0.0MB)
   free     =86507520(82.5MB)0.0% used
PS PermGeneration//當前的 “永生代” 記憶體分佈
   capacity =22020096(21.0MB)
   used     =2496528(2.3808746337890625MB)
   free     =19523568(18.619125366210938MB)11.337498256138392% used

670 interned Strings occupying 43720 bytes.

檢視堆記憶體(直方圖)中的物件數量及大小。執行命令:hollis@hos:~/workspace/design_apaas/apaasweb/control/bin$ jmap -histo 3331

num     #instances         #bytes  class name
編號     個數                位元組     類名
----------------------------------------------
   1:             7        1322080  [I
   2:          5603         722368  <methodKlass>
   3:          5603         641944  <constMethodKlass>
   4:         34022         544352  java.lang.Integer
   5:           371         437208  <constantPoolKlass>
   6:           336         270624  <constantPoolCacheKlass>
   7:           371         253816  <instanceKlassKlass>

jmap -histo:live這個命令執行,JVM會先觸發gc,然後再統計資訊。

將記憶體使用的詳細情況輸出到檔案,執行命令:hollis@hos:~/workspace/design_apaas/apaasweb/control/bin$ jmap -dump:format=b,file=heapDump 6900

然後用jhat命令可以參見jhat -port 5000 heapDump在瀏覽器中訪問:http://localhost:5000/檢視詳細資訊

這個命令執行,JVM重新整堆堆的資訊轉儲寫入一個檔案,堆如果比較大的話,就會導致這個過程比較耗時,並且執行的過程中為了保證轉儲的資訊是可靠的,所以會暫停應用。

總結

1.如果程式記憶體不足或替換GC,很有可能存在記憶體不足情況,這時候就要重新使用Java堆Dump檢視物件的情況
。2.要製作堆Dump可以直接使用jvm自帶的jmap命令
3.可以先使用jmap -heap命令檢視堆的使用情況,看一下各個堆空間的佔用情況。
4.使用jmap -histo:[live]檢視堆記憶體中的物件的情況。如果有大量物件在持續被引用,並沒有被釋放掉,那就產生了記憶體洩露,就要結合程式碼,把不用的物件釋放掉。
5.可以也。使用jmap -dump:format=b,file=<fileName>命令將堆資訊儲存到一個檔案中,再借助與jHat命令檢視詳細內容
6.在記憶體出現洩露,溢位或者其它前提條件下,建議多轉儲先前的記憶體,把記憶體檔案進行編號擴充套件,並進行後續的記憶體整理分析。

附加到程序時出錯:sun.jvm.hotspot.debugger.DebuggerException:無法附加到程序

在Ubuntu的中第一次使用JMAP會報錯:Error attaching to process: sun.jvm.hotspot.debugger.DebuggerException: Can't attach to the process,這是oracla文件中提到的一個錯誤:http://bugs.java.com/bugdatabase/view_bug.do?bug_id=7050524,解決方式如下:

    1. 回聲0 |sudo tee / proc / sys / kernel / yama / ptrace_scope該方法在重新啟動前有效。

    2. 永久有效方法sudo vi /etc/sysctl.d/10-ptrace.conf編輯下面這行:kernel.yama.ptrace_scope = 1修改為:kernel.yama.ptrace_scope = 0重新啟動系統,使修改生效。

jstat(JVM Statistics Monitoring Tool)是用於監控虛擬機器各種執行狀態資訊的命令列工具。他可以顯示本地或遠端虛擬機器程序中的類裝載、記憶體、垃圾收集、JIT編譯等執行資料,在沒有GUI圖形的伺服器上,它是執行期定位虛擬機器效能問題的首選工具。

jstat位於java的bin目錄下,主要利用JVM內建的指令對Java應用程式的資源和效能進行實時的命令列的監控,包括了對Heap size和垃圾回收狀況的監控。可見,Jstat是輕量級的、專門針對JVM的工具,非常適用。

jstat 命令格式

 jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]

引數解釋:

Option — 選項,我們一般使用 -gcutil 檢視gc情況

vmid — VM的程序號,即當前執行的java程序號

interval– 間隔時間,單位為秒或者毫秒

count — 列印次數,如果預設則列印無數次

引數interval和count代表查詢間隔和次數,如果省略這兩個引數,說明只查詢一次。假設需要每250毫秒查詢一次程序5828垃圾收集狀況,一共查詢5次,那命令列如下:

jstat -gc 5828 250 5

對於命令格式中的VMIDLVMID需要特別說明下:如果是本地虛擬機器程序,VMID(Virtual Machine IDentifier,虛機識別符號)和LVMID(Local Virtual Machine IDentifier,虛機識別符號)是一致的,如果是遠端虛擬機器程序,那VMID的格式應當是:[protocol:][//] lvmid [@hostname[:port]/servername]

option

選項option代表這使用者希望查詢的虛擬機器資訊,主要分為3類:類裝載、垃圾收集和執行期編譯狀況,具體選項及作用如下:

class監視類裝載、解除安裝數量、總空間及類裝載所耗費的時間 –gc監視Java堆狀況,包括Eden區、2個Survivor區、老年代、永久代等的容量 –gccapacity監視內容與-gc基本相同,但輸出主要關注Java堆各個區域使用到的最大和最小空間 –gcutil監視內容與-gc基本相同,但輸出主要關注已使用空間佔總空間的百分比 –gccause與-gcutil功能一樣,但是會額外輸出導致上一次GC產生的原因 –gcnew監視新生代GC的狀況 –gcnewcapacity監視內容與-gcnew基本相同,輸出主要關注使用到的最大和最小空間 –gcold監視老年代GC的狀況 –gcoldcapacity監視內容與——gcold基本相同,輸出主要關注使用到的最大和最小空間 –gcpermcapacity輸出永久代使用到的最大和最小空間 –compiler輸出JIT編譯器編譯過的方法、耗時等資訊 –printcompilation輸出已經被JIT編譯的方法

常見術語

1、jstat –class<pid> :顯示載入class的數量,及所佔空間等資訊。

Loaded裝載的類的數量Bytes裝載類所佔用的位元組數Unloaded解除安裝類的數量Bytes解除安裝類的位元組數Time裝載和解除安裝類所花費的時間

2、jstat -compiler <pid>顯示VM實時編譯的數量等資訊。

Compiled編譯任務執行數量Failed編譯任務執行失敗數量Invalid編譯任務執行失效數量Time編譯任務消耗時間FailedType最後一個編譯失敗任務的型別FailedMethod最後一個編譯失敗任務所在的類及方法

3、jstat -gc <pid>: 可以顯示gc的資訊,檢視gc的次數,及時間。

S0C年輕代中第一個survivor(倖存區)的容量 (位元組)S1C年輕代中第二個survivor(倖存區)的容量 (位元組)S0U年輕代中第一個survivor(倖存區)目前已使用空間 (位元組)S1U年輕代中第二個survivor(倖存區)目前已使用空間 (位元組)EC年輕代中Eden(伊甸園)的容量 (位元組)EU年輕代中Eden(伊甸園)目前已使用空間 (位元組)OCOld代的容量 (位元組)OUOld代目前已使用空間 (位元組)PCPerm(持久代)的容量 (位元組)PUPerm(持久代)目前已使用空間 (位元組)YGC從應用程式啟動到取樣時年輕代中gc次數YGCT從應用程式啟動到取樣時年輕代中gc所用時間(s)FGC從應用程式啟動到取樣時old代(全gc)gc次數FGCT從應用程式啟動到取樣時old代(全gc)gc所用時間(s)GCT從應用程式啟動到取樣時gc用的總時間(s)

4、jstat -gccapacity <pid>:可以顯示,VM記憶體中三代(young,old,perm)物件的使用和佔用大小

NGCMN年輕代(young)中初始化(最小)的大小(位元組)NGCMX年輕代(young)的最大容量 (位元組)NGC年輕代(young)中當前的容量 (位元組)S0C年輕代中第一個survivor(倖存區)的容量 (位元組)S1C年輕代中第二個survivor(倖存區)的容量 (位元組)EC年輕代中Eden(伊甸園)的容量 (位元組)OGCMNold代中初始化(最小)的大小 (位元組)OGCMXold代的最大容量(位元組)OGCold代當前新生成的容量 (位元組)OCOld代的容量 (位元組)PGCMNperm代中初始化(最小)的大小 (位元組)PGCMXperm代的最大容量 (位元組)
PGCperm代當前新生成的容量 (位元組)PCPerm(持久代)的容量 (位元組)YGC從應用程式啟動到取樣時年輕代中gc次數FGC從應用程式啟動到取樣時old代(全gc)gc次數

5、jstat -gcutil <pid>:統計gc資訊

S0年輕代中第一個survivor(倖存區)已使用的佔當前容量百分比S1年輕代中第二個survivor(倖存區)已使用的佔當前容量百分比E年輕代中Eden(伊甸園)已使用的佔當前容量百分比Oold代已使用的佔當前容量百分比Pperm代已使用的佔當前容量百分比YGC從應用程式啟動到取樣時年輕代中gc次數YGCT從應用程式啟動到取樣時年輕代中gc所用時間(s)FGC從應用程式啟動到取樣時old代(全gc)gc次數FGCT從應用程式啟動到取樣時old代(全gc)gc所用時間(s)GCT從應用程式啟動到取樣時gc用的總時間(s)

6、jstat -gcnew <pid>:年輕代物件的資訊。

S0C年輕代中第一個survivor(倖存區)的容量 (位元組)S1C年輕代中第二個survivor(倖存區)的容量 (位元組)S0U年輕代中第一個survivor(倖存區)目前已使用空間 (位元組)S1U年輕代中第二個survivor(倖存區)目前已使用空間 (位元組)TT持有次數限制MTT最大持有次數限制EC年輕代中Eden(伊甸園)的容量 (位元組)EU年輕代中Eden(伊甸園)目前已使用空間 (位元組)YGC從應用程式啟動到取樣時年輕代中gc次數YGCT從應用程式啟動到取樣時年輕代中gc所用時間(s)

7、jstat -gcnewcapacity<pid>: 年輕代物件的資訊及其佔用量。

NGCMN年輕代(young)中初始化(最小)的大小(位元組)NGCMX年輕代(young)的最大容量 (位元組)NGC年輕代(young)中當前的容量 (位元組)S0CMX年輕代中第一個survivor(倖存區)的最大容量 (位元組)S0C年輕代中第一個survivor(倖存區)的容量 (位元組)S1CMX年輕代中第二個survivor(倖存區)的最大容量 (位元組)S1C年輕代中第二個survivor(倖存區)的容量 (位元組)ECMX年輕代中Eden(伊甸園)的最大容量 (位元組)EC年輕代中Eden(伊甸園)的容量 (位元組)YGC從應用程式啟動到取樣時年輕代中gc次數FGC從應用程式啟動到取樣時old代(全gc)gc次數

8、jstat -gcold <pid>:old代物件的資訊。

PCPerm(持久代)的容量 (位元組)PUPerm(持久代)目前已使用空間 (位元組)OCOld代的容量 (位元組)OUOld代目前已使用空間 (位元組)YGC從應用程式啟動到取樣時年輕代中gc次數FGC從應用程式啟動到取樣時old代(全gc)gc次數FGCT從應用程式啟動到取樣時old代(全gc)gc所用時間(s)GCT從應用程式啟動到取樣時gc用的總時間(s)

9、stat -gcoldcapacity <pid>: old代物件的資訊及其佔用量。

OGCMNold代中初始化(最小)的大小 (位元組)OGCMXold代的最大容量(位元組)OGCold代當前新生成的容量 (位元組)OCOld代的容量 (位元組)YGC從應用程式啟動到取樣時年輕代中gc次數FGC從應用程式啟動到取樣時old代(全gc)gc次數FGCT從應用程式啟動到取樣時old代(全gc)gc所用時間(s)GCT從應用程式啟動到取樣時gc用的總時間(s)

10、jstat -gcpermcapacity<pid>: perm物件的資訊及其佔用量。

PGCMNperm代中初始化(最小)的大小 (位元組)PGCMXperm代的最大容量 (位元組)
PGCperm代當前新生成的容量 (位元組)PCPerm(持久代)的容量 (位元組)YGC從應用程式啟動到取樣時年輕代中gc次數FGC從應用程式啟動到取樣時old代(全gc)gc次數FGCT從應用程式啟動到取樣時old代(全gc)gc所用時間(s)GCT從應用程式啟動到取樣時gc用的總時間(s)

11、jstat -printcompilation <pid>:當前VM執行的資訊。

Compiled編譯任務的數目Size方法生成的位元組碼的大小Type編譯型別Method類名和方法名用來標識編譯的方法。類名使用/做為一個名稱空間分隔符。方法名是給定類中的方法。上述格式是由-XX:+PrintComplation選項進行設定的

jhat(Java堆分析工具),是一個用來分析java的堆情況的命令。之前的文章講到過,使用jmap可以生成Java堆的Dump檔案。生成轉儲檔案之後就可以用jhat命令,將轉儲檔案轉成html的形式,然後通過http訪問可以檢視堆情況。

jhat命令解析會Java堆dump並啟動一個web伺服器,然後就可以在瀏覽器中檢視堆的dump檔案了。

例項

一,匯出轉儲檔案

關於轉儲檔案的生成可以看jmap命令的詳細介紹。

1,執行java程式

/**
 * Created by hollis on 16/1/21.
 */
public class JhatTest {

    public static void main(String[] args) {
        while(true) {
            String string = new String("hollis");
            System.out.println(string);
        }
    }
}

2,檢視該程序的ID

HollisMacBook-Air:apaas hollis$ jps -l
68680 org.jetbrains.jps.cmdline.Launcher
62247 com.intellij.rt.execution.application.AppMain
69038 sun.tools.jps.Jps

使用jps命令檢視發現有三個java程序在執行,一個是我的IDEA使用的程序68680,一個是JPS命令使用的程序69038,另外一個就是上面那段程式碼執行的程序62247。

3,生成轉儲檔案

HollisMacBook-Air:test hollis$ jmap -dump:format=b,file=heapDump 62247
Dumping heap to /Users/hollis/workspace/test/heapDump ...
Heap dump file created

以上命令可以將程序6900的堆dump檔案匯出到heapDump檔案中。
檢視當前目錄可以看到heapDump檔案。

除了使用jmap命令,還可以通過以下方式:

1,使用jconsole選項通過HotSpotDiagnosticMXBean從執行時獲得堆轉儲(生成轉儲檔案),

2,虛擬機器啟動時如果指定了-XX:+ HeapDumpOnOutOfMemoryError選項,則在引發OutOfMemoryError時,會自動執行堆轉儲。

3,使用hprof命令

二,解析Java堆轉儲檔案,並啟動一個Web伺服器

HollisMacBook-Air:apaas hollis$ jhat heapDump
Reading from heapDump...
Dump file created Thu Jan 21 18:59:51 CST 2016
Snapshot read, resolving...
Resolving 341297 objects...
Chasing references, expect 68 dots....................................................................
Eliminating duplicate references....................................................................
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.

使用jhat命令,就啟動了一個http服務,埠是7000

然後在訪問http:// localhost:7000 /

頁面如下:

三,分析

在瀏覽器裡面看到dump檔案之後就可以進行分析了。這個頁面會列出當前程序中的所有對像情況。

該頁面提供了幾個查詢功能可以使用:

All classes including platform//
Show all members of the rootset
Show instance counts for all classes (including platform)
Show instance counts for all classes (excluding platform)
Show heap histogram
Show finalizer summary
Execute Object Query Language (OQL) query

一般檢視堆異常情況主要看這個兩個部分:

顯示所有類的例項計數(不包括平臺),平臺外的所有物件資訊。如下圖:

顯示堆直方圖以樹狀圖形式展示堆情況。如下圖:

具體排查時需要結合程式碼,觀察是否大量應該被回收的物件在一直被引用或者是否有佔用記憶體特別大的物件無法被回收。

用法摘要

這一部分放在後面介紹的原因是一般不太使用。

HollisMacBook-Air:~ hollis$ jhat -help
Usage:  jhat [-stack <bool>] [-refs <bool>] [-port <port>] [-baseline <file>] [-debug <int>] [-version] [-h|-help] <file>

    -J<flag>          Pass <flag> directly to the runtime system. For
              example, -J-mx512m to use a maximum heap size of 512MB
    -stack false:     Turn off tracking object allocation call stack.
    -refs false:      Turn off tracking of references to objects
    -port <port>:     Set the port for the HTTP server.  Defaults to 7000
    -exclude <file>:  Specify a file that lists data members that should
              be excluded from the reachableFrom query.
    -baseline <file>: Specify a baseline object dump.  Objects in
              both heap dumps with the same ID and same class will
              be marked as not being "new".
    -debug <int>:     Set debug level.
                0:  No debug output
                1:  Debug hprof file parsing
                2:  Debug hprof file parsing, no server
    -version          Report version number
    -h|-help          Print this help and exit
    <file>            The file to read

-stack false | true

如果分配位置資訊在堆轉儲中不可用。則必須符合標誌設定為false。預設值true。

-refs false | true

關閉物件引用跟蹤(對物件的引用的跟蹤)。預設值為true。預設情況下,返回的指標是指向其他特定物件的物件,如反向連結或輸入引用(引薦來源網址或傳入引用),會統計/計算堆中的所有物件。

-port埠號

設定jhat HTTP伺服器的埠號。預設值7000。

-排除排除檔案

指定物件查詢時需要排除的資料成員列表檔案(列出應從可達物件查詢中排除的資料成員的檔案)。例如,如果檔案列列出了java.lang.String.value,那麼當從某個地方特定物件Object o計算可達性的物件列表時,引用路徑涉及java.lang.String.value的都會被排除。

-基線排除檔案

在兩個堆轉儲中有相同的物件ID的物件會被標記為不是新的(標記為不是新的)。其他物件被標記為新的(新)。在比較兩個不同的堆轉儲時很有用。

-除錯int

設定除錯等級。0表示不輸出除錯資訊。

-版

啟動後只顯示版本資訊就退出

-J <標誌>

因為jhat命令實際上會啟動一個JVM來執行,通過-J可以在啟動JVM時插入一些啟動引數。例如,-J-Xmx512m則指定執行jhat的Java虛擬機器使用的最大堆記憶體為512 MB。如果需要使用多個JVM啟動引數,則可以使用多個-Jxxxxxx。

OQL

jhat還提供了一種物件查詢語言(Object Query Language),OQL有點類似SQL,可以用來查詢。

OQL語句的執行頁面:http:// localhost:7000 / oql /

OQL幫助資訊頁面為:http:// localhost:7000 / oqlhelp /

OQL的預發可以在幫助頁面檢視,這裡就不詳細講解了。

這些配置資訊包括JAVA系統引數和命令列引數,如果執行在64位虛擬機器上執行,​​需要指定-J-d64引數,如:jinfo -J-d64 -sysprops pid

另外,Java7的官方文件指出,該命令在後續的版本中可能不再使用。筆者使用的版本(jdk8)中已經不支援該命令(筆者翻閱了java8中該命令的文件,其中已經明確說明不再支援)。提示如下:

HollisMacBook-Air:test-workspace hollis$ jinfo 92520
Attaching to process ID 92520, please wait...
^@

Exception in thread "main" java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at sun.tools.jinfo.JInfo.runTool(JInfo.java:97)
    at sun.tools.jinfo.JInfo.main(JInfo.java:71)
Caused by: sun.jvm.hotspot.runtime.VMVersionMismatchException: Supported versions are 24.79-b02. Target VM is 25.40-b25
    at sun.jvm.hotspot.runtime.VM.checkVMVersion(VM.java:234)
    at sun.jvm.hotspot.runtime.VM.<init>(VM.java:297)
    at sun.jvm.hotspot.runtime.VM.initialize(VM.java:368)
    at sun.jvm.hotspot.bugspot.BugSpotAgent.setupVM(BugSpotAgent.java:598)
    at sun.jvm.hotspot.bugspot.BugSpotAgent.go(BugSpotAgent.java:493)
    at sun.jvm.hotspot.bugspot.BugSpotAgent.attach(BugSpotAgent.java:331)
    at sun.jvm.hotspot.tools.Tool.start(Tool.java:163)
    at sun.jvm.hotspot.tools.JInfo.main(JInfo.java:128)...6 more

由於列印jvm常用資訊可以使用Jps命令,並且在後續的java版本中可能不再支援,所以這個命令筆者就不詳細介紹了。下面稱為幫助資訊,讀者可以自行閱讀使用。(這好像上高中,老師講到一些難點的時候說,不明白也不要緊,知道有這麼一回事就可以了!!)

用法摘要

以鍵值對的形式打印出JAVA系統引數及命令列引數的名稱和內容。

-flag name
prints the name and value of the given command line flag.
-flag [+|-]name
enables or disables the given boolean command line flag.
-flag name=value
sets the given command line flag to the specified value.
-flags
prints command line flags passed to the JVM. pairs.
-sysprops
prints Java System properties as name, value pairs.
-h
prints a help message
-help
prints a help message

javap是jdk自帶的一個工具,可以對程式碼反編譯,也可以檢視java編譯器生成的位元組碼。

一般情況下,很少有人使用javap對class檔案進行反編譯,因為有很多成熟的反編譯工具可以使用,比如jad。但是,javap還可以檢視java編譯器為我們生成的位元組碼。通過它,可以對照原始碼和位元組碼,從而瞭解很多編譯器內部的工作。

例項

javap命令分解一個class檔案,它根據options來決定到底輸出什麼。如果沒有使用options,那麼javap將會輸出包,類裡的protected和public域以及類裡的所有方法。javap將會把它們輸出在標準輸出上。來看這個例子,先編譯(javac)下面這個類。

import java.awt.*;
import java.applet.*;

public class DocFooter extends Applet {
        String date;
        String email;

        public void init() {
                resize(500,100);
                date = getParameter("LAST_UPDATED");
                email = getParameter("EMAIL");
        }

        public void paint(Graphics g) {
                g.drawString(date + " by ",100, 15);
                g.drawString(email,290,15);
        }
}

在命令列上鍵入javap DocFooter後,輸出結果如下

Compiled from "DocFooter.java"
public class DocFooter extends java.applet.Applet {
  java.lang.String date;
  java.lang.String email;
  public DocFooter();
  public void init();
  public void paint(java.awt.Graphics);
}

如果加入了-c,即javap -c DocFooter,那麼輸出結果如下

Compiled from "DocFooter.java"
public class DocFooter extends java.applet.Applet {
  java.lang.String date;

  java.lang.String email;

  public DocFooter();
    Code:
       0: aload_0       
       1: invokespecial #1                  // Method java/applet/Applet."<init>":()V
       4: return        

  public void init();
    Code:
       0: aload_0       
       1: sipush        500
       4: bipush        100
       6: invokevirtual #2                  // Method resize:(II)V
       9: aload_0       
      10: aload_0       
      11: ldc           #3                  // String LAST_UPDATED
      13: invokevirtual #4                  // Method getParameter:(Ljava/lang/String;)Ljava/lang/String;
      16: putfield      #5                  // Field date:Ljava/lang/String;
      19: aload_0       
      20: aload_0       
      21: ldc           #6                  // String EMAIL
      23: invokevirtual #4                  // Method getParameter:(Ljava/lang/String;)Ljava/lang/String;
      26: putfield      #7                  // Field email:Ljava/lang/String;
      29: return        

  public void paint(java.awt.Graphics);
    Code:
       0: aload_1       
       1: new           #8                  // class java/lang/StringBuilder
       4: dup           
       5: invokespecial #9                  // Method java/lang/StringBuilder."<init>":()V
       8: aload_0       
       9: getfield      #5                  // Field date:Ljava/lang/String;
      12: invokevirtual #10                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      15: ldc           #11                 // String  by 
      17: invokevirtual #10                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      20: invokevirtual #12                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      23: bipush        100
      25: bipush        15
      27: invokevirtual #13                 // Method java/awt/Graphics.drawString:(Ljava/lang/String;II)V
      30: aload_1       
      31: aload_0       
      32: getfield      #7                  // Field email:Ljava/lang/String;
      35: sipush        290
      38: bipush        15
      40: invokevirtual #13                 // Method java/awt/Graphics.drawString:(Ljava/lang/String;II)V
      43: return        
}

上面輸出的內容就是位元組碼。

用法摘要

-help 幫助
-l 輸出行和變數的表
-public 只輸出public方法和域
-protected 只輸出publicprotected類和成員
-package 只輸出包,publicprotected類和成員,這是預設的
-p -private 輸出所有類和成員
-s 輸出內部型別簽名
-c 輸出分解後的程式碼,例如,類中每一個方法內,包含java位元組碼的指令,
-verbose 輸出棧大小,方法引數的個數
-constants 輸出靜態final常量

總結

javap可以用於反編譯和檢視編譯器編譯後的位元組碼。平時一般用javap -c比較多,該命令用於列出每個方法所執行的JVM指令,並顯示每個方法的位元組碼的實際作用。可以通過位元組碼和原始碼的對比,深入分析java的編譯原理,瞭解和解決各種Java原理級別的問題。