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 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)