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

JVM虛擬機性能監控與調優(JDK命令行、JConsole)

tp服務器 失敗 工具 strong 工作 rbo 介紹 人員 年齡

很多資料在介紹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 460@localhost: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程序性能優化》葛一鳴著

JVM虛擬機性能監控與調優(JDK命令行、JConsole)