1. 程式人生 > >JVM虛擬機器效能監控與調優(JDK命令列、JConsole)

JVM虛擬機器效能監控與調優(JDK命令列、JConsole)

很多資料在介紹JDK命令列工具時並不是在Java8環境下,因此還在使用過時的永久區系列的引數,給一些讀者造成困難。

Java8使用Metaspace(元空間)代替永久區,對於64位平臺,為了壓縮JVM物件中的_klass指標的大小,引入了類指標壓縮空間(Compressed Class Pointer Space) 。關於這點,可以參考部落格https://blog.csdn.net/liang0000zai/article/details/51168095。

1. JDK命令列工具

在JDK的開發包中,除了大家熟知的java.exe和javac.exe外,還有一系列輔助工具。這些工具在JDk安裝目錄下的bin目錄中。如圖:

雖然乍看之下,這些工作都是exe的可執行檔案。但事實上,它們只是Java程式的一層包裝,其真正實現是在 tools.jar 中。

以jps工具為例,在控制檯執行jps命令和java -classpath %Java_HOME%/lib/tools.jar sun.tools.jps.Jps命令是等價的,即jps.exe只是這個命令的一層包裝。

在學習以下命令之前,不妨使用IDEA寫個不會退出的小程式,方便測試。示例程式碼:

package cn.zyzpp.jConsole;

import java.text.SimpleDateFormat;
import java.util.Date;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        while (true){
            Thread.sleep(10000);
            //Byte[] bytes = new Byte[1024];
            //bytes = null;
            //System.gc();
            System.out.println(new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));
        }
    }
}

1.1 jps命令

命令jps用於列出java程序,直接執行jps不加任何引數,可以列出Java程式的程序ID以及Main函式等名稱。

從這個輸出中可以看到,當前系統中共存在4個Java應用程式,其中第一個輸出jps就是jps命令本身,這個更加證明此命令本質也是一個Java程式。此外,jps還提供了一系列引數來控制它的輸出內容。

引數-q指定jps只輸出程序ID,而不輸出類的短名稱:

引數-m用於輸出傳遞給Java程序(主函式)的引數:

引數 -l用於輸出主函式的完整路徑:

引數 -v可以顯示傳遞給JVM的引數:

1.2 jstat命令

jstat是一個可以用於觀察Java應用程式執行時資訊的工具。它的功能非常強大,可以通過它,檢視堆資訊的詳細使用情況。它的基本使用語法為:

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

選項option可以由以下值構成:

  • -class:顯示ClassLoader的相關資訊。
  • -compiler:顯示JIT編譯的相關資訊。
  • -gc:顯示與GC相關的堆資訊。
  • -gccapacity:顯示各個代的容量及使用情況。
  • -gccause:顯示垃圾收集相關資訊(同-gcutil),同時顯示最後一次或當前正在發生的垃圾收集的誘發原因。
  • -gcnew:顯示新生代資訊。
  • -gcnewcapacity:顯示新生代大小與使用情況。
  • -gcold:顯示老年代與永久代的資訊。
  • -gcoldcapacity:顯示老年代的大小。
  • -gcmetacapacity:顯示元空間的大小。(在java8之前是使用-gcpermcapacity顯示永久代的大小
  • -gcutil:顯示垃圾收集資訊。
  • -printcompilation:輸出JIT編譯的方法資訊。

以上選項可以輸入 jstat -options 檢視。

-t 引數可以在輸出資訊前加一個 Timestamp 列,顯示程式的執行時間。

-h 引數可以在週期性資料輸出時,輸出多少行資料後,跟著輸出一個表頭資訊。

vmid 引數就是Java程序id。

interval 引數用於指定輸出統計資料的週期,單位為毫秒。

count 用於指定一共輸出多少次資料。

示例

1.2.1 -classs

輸出java程序13516的ClassLoader相關資訊。每秒鐘統計一次資訊,一共輸出2次:

在-class的輸出中,Loaded 表示載入了類的數量,Bytes表示載入類的合計大小,Unloaded 表示解除安裝類的數量,第2個Bytes表示解除安裝類的大小,Time表示在載入和解除安裝類上所花的時間。

1.2.2 -compiler

下例顯示了檢視JIT編譯的資訊:

Compiled 表示編譯任務執行的次數,Failed表示編譯失敗的次數,Invalid 表示編譯不可用的次數,Time 表示編譯後的總耗時,FailedType 表示最後一次編譯失敗的型別,FailedMethod 表示最後一次編譯失敗的類名和方法名。

1.2.3 -gc

下例顯示了與GC相關的堆資訊的輸出:

各項引數的含義如下:

  • S0C:s0(from)的大小(KB)。
  • S1C:s1(from)的大小(KB)。
  • S0U:s0(from)已使用的空間(KB)。
  • S1U:s1(from)已經使用的空間(KB)
  • EC:eden區的大小(KB)
  • EU:eden區已經使用的空間(KB)
  • OC:老年代大小(KB)
  • OU:老年代已經使用的空間(KB)
  • MC:元空間的大小(Metaspace)(位元組)
  • MU:元空間已使用大小(位元組)
  • CCSC:壓縮類空間大小(compressed class space)(位元組)
  • CCSU:壓縮類空間已使用大小
  • YGC:新生代gc次數
  • YGCT:新生代gc耗時
  • FGC:Full gc次數
  • FGCT:Full gc耗時
  • GCT:gc總耗時

1.2.4 -gccapacity

下例顯示了各個代的資訊,與-gc相比,它不僅輸出了各個代的當前大小,也包含了各個代的最大值和最小值。

各引數含義:

  • NGCMN:新生代最小(初始化)容量(位元組)
  • NGCMX:新生代最大容量(位元組)
  • NGC:當前新生代容量(位元組)
  • OGCMN:老年代最小容量(位元組)
  • OGCMX:老年代最大容量(位元組)
  • MCMN:metaspace(元空間)中初始化(最小)的大小 (位元組)
  • MCMX :metaspace(元空間)的最大容量 (位元組)
  • CCSMN:最小壓縮類空間大小(位元組)
  • CCSMX:最大壓縮類空間大小(位元組)

1.2.5 -gccause

下列顯示了最近一次GC的原因以及當前GC的原因:

各項引數如下:

  • LGCC:上次GC的原因。
  • GCC:當前GC的原因。

1.2.6 -gcnew

-gcnew 引數用於檢視新生代的一些詳細資訊:

各項引數的含義如下:

  • TT:新生代物件晉升到老年代物件的年齡。
  • MTT:新生代物件晉升到老年代物件的年齡最大值。
  • DSS:所需的survivor區大小。

1.2.7 -gcnewcapacity

-gcnewcapacity 引數可以詳細輸出新生代各個區的大小資訊:

各項引數的含義如下:

  • S0CMX:s0區的最大值(KB)。
  • S1CMX:s1區的最大值(KB)。
  • ECMX:eden區的最大值(KB)。

1.2.8 -gcold

-gcold 可以用於展現老年代GC的概況。

1.2.9 -gcoldcapacity

-gcoldcapacity 用於展現老年代的容量資訊:

1.2.10 -gcmetacapacity與-gcpermcapacity

-gcpermcapacity 用於展示永久區的使用情況,但是在Java8環境下使用會報錯找不到。因為java8的永久區被元空間取而代之。所以要使用 -gcmetacapacity:

1.2.11 -gcutil

-gcutil 用於展示GC回收相關資訊:

各項引數如下:

  • S0:s0區使用的百分比。
  • S1:s1區使用的百分比。
  • E:eden 區使用的百分比。
  • O:old區使用的百分比。
  • M:元空間使用的百分比。
  • CCS:壓縮類空間使用的百分比。

1.3 jinfo命令

jinfo 可以用來檢視正在執行的Java執行程式的擴充套件引數,甚至支援在執行時修改部分引數。它的基本語法為:

jinfo <option> <pid>

其中option可以為以下資訊:

  • -flag : 列印指定java虛擬機器的引數值。
  • -flag [+|-]< name >:設定或取消指定java虛擬機器引數的布林值。
  • -flag < name >=< value >:設定指定java虛擬機器的引數的值。
    在很多情況下,Java應用程式不會指定所有的JVM引數。而此時,開發人員可能不知道某一個具體的JVM引數的預設值。有了 jinfo 工具,開發人員可以很方便地找到JVM引數的當前值。

1)下例顯示了新生代物件晉升到老年代物件的最大年齡。在應用程式執行時並沒有指定這個引數,但是通過jinfo,可以檢視這個引數的當前的值。

2)顯示是否列印GC詳細資訊。

3)修改部分引數的值,下面是對PrintGCDetails引數的修改。

1.4 jmap命令

jmap 可以生成Java應用程式的堆快照和物件的統計資訊。基本語法為:

jmap [option] vmid

option 選項如下:

下例使用jmap生成PID為9440的Java應用程式的物件統計資訊,並輸入到 s.txt 檔案中。

jmap -histo 9440 >c:\s.txt

輸出檔案有如下結構:

可以看到,這個輸出顯示了記憶體中的例項數量和合計。

另一個更為重要的功能是得到Java程式的當前堆快照:

本例中,將應用程式的堆快照輸出到E盤的heap.bin檔案中。之後,可以通過多種工具分析檔案。比如,下文中提到的jhat工具。也可以使用 Visual VM工具開啟這個快照檔案。

1.5 jhat命令

使用 jhat 工具可以用於分析Java應用程式的堆快照內容。以前文中jmap的輸出對檔案 heap.hprof 為例:

jhat 在分析完成後,使用HTTP伺服器展示其分析結果。在瀏覽器中訪問http://localhost:7000/,結果如圖所示。

在預設頁中,jhat 伺服器顯示了所有的非平臺類資訊。單擊連結進入,可以檢視選中類的超類、ClassLoader 以及該類的例項等資訊。此外,在頁面底部,jhat還為開發人員提供了其他查詢方式(Other Queries)。

通過這些連結,開發者可以進一步檢視所有類資訊(包括Java平臺的類)。所有類的例項數量以及例項的具體資訊。最後,還有一個連結指向OQL查詢介面。

圖中顯示了在jhat中,檢視Java應用程式裡java.lang.String類的例項數量:

單擊 instances 連結可以進一步檢視 String 物件的例項,如圖所示:

通常,匯出的堆快照資訊可以非常大,由於資訊太多,可能很難通過頁面上簡單的連結索引找到想要的資訊。為此,jhat還支援使用OQL語句對堆快照進行查詢。執行 OQL 語言的介面非常簡潔,如圖所示。使用OQL查詢出當前Java程式中所有java.io.File物件的路徑。OQL如下:

select file.path.value.toString() from java.io.File file

1.6 jstack命令

jstack 可用於匯出Java應用程式的執行緒堆疊。語法為:

jstack [-l] <pid>

-l選項用於列印鎖的附加資訊。

jstack 工具會在控制檯輸出程式中所有的鎖資訊,可以使用重定向將輸出儲存到檔案,如:

jstat -l 16196 >e\deadlock.txt

通過 jstack 工具不僅可以得到執行緒堆疊,它還能自動進行死鎖檢查,輸出找到的死鎖資訊。

1.7 jstatd命令

之前所述的工具中,只涉及到監控本機的Java應用程式。而在這些工具中,一些監控工具也支援對遠端計算機的監控(如:jps、jstat)。為了啟用遠端監控,則需要配合使用jstatd工具。

命令jstatd是一個RMI服務端程式,它的作用相當於代理伺服器,建立本地計算機與遠端監控工具的通訊。jstatd伺服器將本機的Java應用程式資訊傳遞到遠端計算機。

直接開啟jstatd伺服器可能會丟擲訪問拒絕異常:

這是由於jstatd程式沒有足夠的許可權所致,可以使用Java的安全策略,為其分配相應的許可權,下面程式碼為jststd分配了最大的許可權,將其儲存在jstatd.all.policy檔案中:

grant codebase "file:${java.home}/../lib/tools.jar" {
permission java.security.AllPermission;
};

然後,使用以下命令再次開啟jstatd伺服器:

 jstatd -J-Djava.security.policy=c:\jstatd.all.policy

伺服器即可開啟成功。

-J引數是一個公共的引數,如jps、jstat等命令都可以接受這個引數。由於jsp、jstat命令本身也是Java應用程式,-J引數可以為jps等命令本身設定其JVM引數。

預設情況下,jstatd 將在1099埠開啟RMI伺服器:

使用jps命令顯示遠端計算機的Java程序:

jps localhost:1099

使用jstat命令顯示遠端程序460的GC情況:

jstat -gcutil [email protected]:1099

1.8 hprof工具

hprof不是獨立的監控工具,它只是一個Java agent工具,它可以用於監控Java應用程式在執行時的CPU資訊和堆資訊。使用 java -agentlib:hprof=help 命令可以檢視hprof 的幫助文件。下面是 hropf 工具幫助資訊的輸出:

使用hprof工具可以檢視程式中各個函式的CPU佔用時間。以下程式碼包含3個方法,分別佔用不同的CPU時間:

public class HProfTest {
    public void slowMethod(){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void slowerMethod(){
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void fastMethod(){
        try {
            Thread.yield();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        HProfTest hProfTest = new HProfTest();
        hProfTest.fastMethod();
        hProfTest.slowMethod();
        hProfTest.slowerMethod();
    }
}

使用引數-agentlib:hprof=cpu=times,interval=10執行以上程式碼。times選項將會在Java函式的呼叫前後記錄函式的執行時間,進而計算函式的執行時間。hprof=cpu是針對cpu統計時間。interval=10 取樣10次。程式執行後會發現多了一個文字檔案java.hprof.txt,開啟後檢視部分輸出如下,可以很容易看到執行時間最長的函式:

使用引數-agentlib:hprof=heap=dump,format=b,file=e:\core.hprof 執行程式,可以將應用程式的堆快照儲存在指定檔案 e:\core.hprof 中。使用MAT或者Visual VM等工具可以拆這個堆檔案。

使用引數 -agentlib:hprof=heap=sites 執行程式,可以輸出Java應用程式中各個類所佔的記憶體百分比,部分輸出如下:

2.JConsole工具

 JConsole(Java Monitoring and ManagementConsole)工具時JDK自帶的圖形化效能監控工具。通過JConsole工具,可以檢視Java應用程式的執行概況,監控堆資訊、永久區使用情況、類載入情況等。本節主要介紹JConsole工具的基本使用方法。

2.1 JConsole連線Java程式

JConsole 程式在%JAVA_HOME%/bin目錄下,雙擊啟動後,程式便要求指定連線Java應用程式,如圖所示。

在羅列的本地Java應用程式選擇PID為15908的程式,連線。

如果需要使用JConsole連線遠端程序,則需要在遠端Java應用程式啟動時,加上如下引數:

-Djava.rmi.server.hostname=127.0.0.1       #遠端伺服器的ip地址
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=8888   #指定jmx監聽的埠
-Dcom.sun.management.jmxremote.authenticate=false  #是否開啟認證
-Dcom.sun.management.jmxremote.ssl=false   #是否開啟ssl

基於以上配置啟動的Java應用程式,通過JConsole在遠端連線時,只需要填寫如下遠端程序即可:

127.0.0.1:8888

2.2 Java程式概況

在連線上Java應用程式後,便可以檢視應用程式概況。

2.3 記憶體監控

切換到記憶體監控頁面,JConsole 可以顯示當前記憶體的詳細資訊。這不僅是包括堆記憶體的整體資訊,更細化到eden區、survivior 區、老年代的使用情況。同時,也包括非堆區,即永久代的使用情況,如圖所示,單機介面右上角的“執行GC”按鈕,可以強制應用程式進行一次Full GC。

2.4 執行緒監控

JConsole 中的執行緒選項卡允許開發人員監控程式內的執行緒,如圖所示。JConsole 顯示了系統內的執行緒數量,並在螢幕下方,顯示了程式中所有的執行緒。單擊執行緒名稱,便可以檢視執行緒的棧資訊。

使用最下方的“監測死鎖”按鈕。還可以自動監測多執行緒應用程式的死鎖情況。

2.5 類載入情況

JConsole的類頁面如圖所示,顯示了系統以及裝載的類數量。在詳細資訊欄中,還顯示了已解除安裝的類數量。

2.6 虛擬機器資訊

在VM摘要頁面,JConsole 顯示了當前應用程式的執行環境。包括虛擬機器型別、版本、堆資訊以及虛擬機器引數等。

2.7 MBean管理

MBean頁面允許通過JConsole進行MBean的管理,包括檢視或者設定MBean的屬性、執行MBean的方法等。下圖是MBean的管理介面,這裡選中了Memory的Verbose屬性。通過修改Verbose的屬性值,可以在程式執行時動態開啟或者關閉GC的輸出資訊。

MBean種類繁多。主要的操作如下:

2.8 使用外掛

除了基本功能之外,JConsole還支援外掛擴充套件。在JDK的安裝目錄下,就有一個自帶的JConsole外掛。使用以下命令可以讓JConsole載入外掛並啟動:

jconsole -pluginpath C:\Java\jdk1.6.0_22\demo\management\JTop\JTop.jar

JConsole啟動後,連線到任意Java應用程式,便可以進入JTop頁面。

參考

《Java程式效能優化》葛一鳴著