JDK命令行工具
在JDK的開發包中,除了大家熟知的JAVA.exe和javac.exe外,還有一系列輔助工具。這些輔助工具位于JDK安裝目錄下的bin目錄中,可以幫助開發人員很好地解決Java應用程序的一些“疑難雜癥”。圖6.16顯示了部分輔助工具。
乍看之下,雖然這些工具都是.exe的可執行文件,但事實上它們只是Java程序的一層包裝,其真正的實現是在tools.jar中,如圖6.17所示。


以jps工具為例,在控制臺執行jps命令和
java-classpath%Java_HOME%/lib/tools.jarsun.tools.jps.Jps命令是等價的,即jps.exe只是這個命令的一層包裝。
jps命令
jps命令類似于linux下的ps,但它只用于列出Java的進程。直接運行jps命令不加任何參數,可以列出Java程序進程ID及Main函數短名稱,例如:

從這個輸出結果中可以看到,當前系統中共存在3個Java應用程序,其中第一個輸出Jps就是jps命令本身,這也證明了此命令的本質是一個Java程序。此外,jps還提供了一系列參數來控制它的輸出內容。
參數-q可以指定jps只輸出進程ID,而不輸出類的短名稱,例如:

參數-m可以用于輸出傳遞給Java進程(主函數)的參數,例如:

參數-l可以用于輸出主函數的完整路徑,例如:

參數-v可以顯示傳遞給JVM的參數,例如:

注意:jps命令類似于ps命令,但是它只列出系統中所有的Java應用程序。通過jps命令可以方便地查看Java進程的啟動類、傳入參數和JVM參數等信息。
jstat命令
jstat是一個可用于觀察Java應用程序運行信息的工具。它的功能非常強大,可以通過它查看堆信息的詳細情況。其基本使用語法如下:

其中,選項option可以由以下值構成。
·-class:顯示ClassLoader的相關信息。
·-compiler:顯示JIT編譯的相關信息。
·-gc:顯示與GC操作相關的堆信息。
·-gccapacity:顯示各個代的容量及使用情況。
·-gccause:顯示垃圾收集的相關信息(同-gcutil),同時顯示最后一次或當前正在發生的垃圾收集的誘發原因。
·-gcnew:顯示新生代信息。
·-gcnewcapacity:顯示新生代的大小與使用情況。
·-gcold:顯示老年代和永久代的信息。
·-gcoldcapacity:顯示老年代的大小。
·-gcpermcapacity:顯示永久代的大小。
·-gcutil:顯示垃圾收集信息。
·-printcompilation:輸出JIT編譯的方法信息。相關參數含義如下:
·-t:可以在輸出信息前加上一個Timestamp列,顯示程序的運行時間。
·-h:可以設定在周期性數據輸出時,當輸出多少行數據后跟著輸出一個表頭信息。
·interval:用于指定輸出統計數據的周期,單位為ms。
·count:用于指定一共輸出多少次數據。
如下示例輸出Java進程2972的ClassLoader相關信息。每秒統計一次信息,一共輸出2次。

在以上的輸出結果中,Loaded表示載入類的數量,第1個Bytes表示載入類的合計大小,Unloaded表示卸載類的數量,第2個Bytes表示卸載類的大小,Time表示在加載類和卸載類上所花的時間。
下例顯示了查看JIT編譯的信息。

其中,Compiled表示編譯任務執行的次數,Failed表示編譯失敗的次數,Invalid表示編譯不可用的次數,Time表示編譯的總耗時,FailedType表示最后一次編譯失敗的類型,FailedMethod表示最后一次編譯失敗的類名和方法名。
下例顯示了與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)。
·PC:永久區的大小(KB)。
·PU:永久區已使用的空間(KB)。
·YGC:新生代GC操作次數。
·YGCT:新生代GC操作耗時。
·FGC:FullGC操作次數。
·FGCT:FullGC操作耗時。
·GCT:GC操作總耗時。
下例顯示了各個代的信息,與-gc相比,它不僅輸出了各個代的當前大小,也包含各個代的最大值和最小值。

其中部分列的信息含義如下:
·NGCMN:新生代最小值(KB)。
·NGCMX:新生代最大值(KB)。
·NGC:當前新生代大小(KB)。
·OGCMN:老年代最小值(KB)。
·OGCMX:老年代最大值(KB)。
·PGCMN:永久代最小值(KB)。
·PGCMX:永久代最大值(KB)。
下例顯示了最近一次執行GC操作的原因及當前執行GC操作的原因。

其中部分列的信息含義如下:
·LGCC:上次執行GC操作的原因。
·GCC:當前執行GC操作的原因。
以上輸出結果顯示,最近一次執行GC操作是由于顯式的System.gc()調用所引起的,當前時刻未執行GC操作。
-gcnew參數可以用于查看新生代的一些詳細信息。

其中部分列的信息含義如下:
·TT:新生代對象晉升到老年代對象的年齡。
·MTT:新生代對象晉升到老年代對象的年齡最大值。
·DSS:所需的survivor區大小。
-gcnewcapacity參數可以詳細地輸出新生代各個區的大小信息。

其中部分列信息如下:
·S0CMX:s0區的最大值(KB)。
·S1CMX:s1區的最大值(KB)。
·ECMX:eden區的最大值(KB)。
-gcold參數用于展現老年代GC操作的概況。

-gcoldcapacity參數用于展現老年代的容量信息。

-gcpermcapacity參數用于展示永久區的使用情況。


-gcutil參數用于展示GC回收的相關信息。

其中部分列的信息如下:
·S0:s0區使用的百分比。
·S1:s1區使用的百分比。
·E:eden區使用的百分比。
·O:old區使用的百分比。
·P:永久區使用的百分比。
注意:jstat命令可以非常詳細地查看Java應用程序的堆使用情況及GC操作情況。
jinfo命令
jinfo可以用來查看正在運行的Java應用程序的擴展參數,甚至支持在運行時修改部分參數。其基本語法如下:

其中,option可以為以下信息:
·-flag<name>:打印指定JVM的參數值。
·-flag[+|-]<name>:設置指定JVM參數的布爾值。
·-flag<name>=<value>:設置指定JVM參數的值。
在很多情況下,Java應用程序不會指定所有的JVM參數,此時,開發人員可能不知道某一個具體的JVM參數的默認值。在這種情況下,需要通過查找文檔獲取某個參數的默認值。這個查找過程可能是非常艱難的,但有了jinfo工具,開發人員可以很方便地找到JVM參數的當前值。
例如,下例顯示了新生代對象晉升到老年代對象的最大年齡。在應用程序啟動時并沒有指定這個參數,但通過jinfo可以查看這個參數的當前數值。

下例顯示了是否打印GC的詳細信息。

除了查找參數的值,jinfo也支持修改部分參數的數值,當然這個修改能力是極其有限的。下例中通過調用jinfo對PrintGCDetails參數進行修改,jinfo可以在Java程序運行時關閉或者打開這個開關。

注意:jinfo不僅可以查看運行時某一個JVM參數的實際取值,甚至可以在運行時修改部分參數,并使之立即生效。
jmap命令
jmap命令可以生成Java應用程序的堆快照和對象的統計信息。
下例使用jmap命令生成PID為2972的Java程序的對象統計信息,并輸出到s.txt文件中。

可以看到,這個輸出結果顯示了內存中的實例數量和合計。
jmap命令的另一個更為重要的功能是得到Java程序的當前堆快照,例如執行以下命令:

本例中,將應用程序的堆快照輸出到C盤的heap.bin文件中。之后,便可以通過多種工具分析該文件,例如6.3.5節中將要介紹的jhat工具。這里使用VisualVM工具打開這個快照文件,如圖6.18所示。
注意:jmap命令可用于導出Java應用程序的堆快照。

jhat命令
使用jhat命令可以分析Java應用程序的堆快照內容。這里分析6.3.4節中使用jmap命令生成的堆文件heap.hprof,如下:

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

在默認頁中,jhat服務器顯示了所有的非平臺類信息。單擊鏈接進入,可以查看選中類的超類、ClassLoader及該類的實例等信息。此外,在頁面的底部,jhat還為開發人員提供了其他查詢方式,如圖6.20所示。

通過這些鏈接,開發者可以進一步查看所有類的信息(包括Java平臺的類)、所有類的實例數量及實例的具體信息。最后,還有一個鏈接指向OQL查詢界面。
圖6.21顯示了在jhat中查看Java應用程序中java.lang.String類的實例數量。

單擊instances鏈接可以進一步查看String對象的實例,如圖6.22所示。

通常,導出的堆快照信息非常多,因此可能很難通過頁面上簡單的鏈接索引找到想要的信息。為此,jhat還支持使用OQL語句對堆快照進行查詢。執行OQL語句的界面非常簡潔,如圖6.23所示。例如,使用OQL查詢出當前Java程序中所有java.io.File對象的路徑,則OQL語句如下:

jhat的OQL語句與VisualVM的OQL非常接近,有興趣的讀者可以查閱本書中的相關章節。
注意:jhat命令可以對堆快照文件進行分析,它啟動一個HTTP服務器,開發人員可以通過瀏覽器瀏覽Java堆快照。

jstack命令
jstack命令可用于導出Java應用程序的線程堆棧。其語法如下:

其中,-l選項用于打印鎖的附加信息。
jstack工具會在控制臺輸出程序中所有的鎖信息,并可以使用重定向將輸出結果保存到文件中。例如:

下例演示了一個簡單的死鎖,兩個線程分別占用south鎖和north鎖,并同時請求對方占用的鎖,導致死鎖發生。


使用jstack工具打印上例的輸出結果,部分結果如下:


從jstack工具的輸出結果中可以很容易地找到發生死鎖的兩個線程及死鎖線程的持有對象和等待對象,從而幫助開發人員解決死鎖問題。
注意:通過jstack工具不僅可以得到線程堆棧,還可以自動進行死鎖檢查,并輸出找到的死鎖信息。
jstatd命令
在本節之前所述的工具中,只涉及監控本機的Java應用程序,而在這些工具中,一些監控工具也支持對遠程計算機的監控(如jps、jstat)。為了啟用遠程監控,則需要配合使用jstatd命令。
jstatd命令是一個RMI服務端程序,它的作用相當于代理服務器,建立本地計算機與遠程監控工具的通信。jstatd服務器將本地的Java應用程序信息傳遞到遠程計算機上,如圖6.24所示。

直接打開jstatd服務器可能會拋出拒絕訪問的異常:

這是由于jstatd程序沒有足夠的權限所致。可以使用Java的安全策略,為其分配相應的權限。下面的代碼為jstatd分配了最大的權限,并將其保存在jstatd.all.policy文件中。

使用以下命令再次開啟jstatd服務器:

這樣,服務器開啟成功。
注意:-J參數是一個公共參數,如jps和jstat等命令都可以接受這個參數。由于jps和jstat命令本身也是Java應用程序,-J參數可以為jps等命令本身設置其JVM參數。
默認情況下,jstatd將在1099端口開啟RMI服務器。

以上命令行顯示,本機的1099端口處于監聽狀態,相關進程號是3656。使用jsp命令查看3656進程正是jstatd,說明jstatd啟動成功。
下面使用jps顯示遠程計算機的Java進程,執行命令如下:

使用jstat顯示遠程進程460的GC操作情況,執行命令如下:

hprof工具
與前文中介紹的監控工具不同,hprof不是獨立的監控工具,它只是一個Javaagent工具,可以用于監控Java應用程序在運行時的CPU信息和堆信息。使用java-agentlib:hprof=help命令可以查看hprof的幫助文檔。下面是hprof工具幫助信息的輸出結果,加粗部分是常用的參數。

使用hprof工具可以查看程序中各個函數的CPU占用時間。以下代碼包含3個方法,分別占用不同的CPU時間。


使用參數-agentlib:hprof=cpu=times,interval=10運行以上代碼。times選項將會在Java函數的調用前后記錄函數的執行時間,進而計算函數的執行時間。程序運行的部分輸出結果如下:

可以很容易地看到運行時間最長的函數。
使用參數-agentlib:hprof=heap=dump,format=b,file=c:core.hprof運行程序,可以將應用程序的堆快照保存在指定文件c:core.hprof中。使用MAT或者VisualVM等工具可以分析這個堆文件。
使用參數-agentlib:hprof=heap=sites運行程序,可以輸出Java應用程序中各個類所占的內存百分比。部分輸出結果如下:

注意:使用hprof工具可以監控Java應用程序各個函數的運行時間,或導出程序的堆快照。
jcmd命令
在JDK7之后,JDK新增了一個命令行工具jcmd。不同于之前的命令行工具,jcmd是一個多功能工具,它幾乎包括之前介紹的所有命令的功能。
如下命令類似于jps,列出當前正在運行的Java程序。

下面的命令顯示了給定程序的啟動時間。

不同于之前的命令行工具,jcmd的使用更加友好,它不僅支持pid作為輸入,也可以使用主程序類名作為其輸入,例如:

它足夠“聰明”,甚至可以只寫類的短名稱,例如:

還可以通過jcmd很輕松地打印線程堆棧(類似于jstack),例如:

也可以通過jcmd打印類的柱狀圖信息(類似于jmap-histo),例如:

同樣支持dump整個堆數據,例如:

上述命令會將整個堆信息轉存到D:d.dump文件中。
還可以查看進程啟動時的虛擬機參數,例如:
