《深刻理解Java虛擬機》——JDK自帶命令行工具

給一個系統定位問題的時候,知識、經驗是關鍵基礎,數據是依據,工具是運用知識處理數據的手段。這裏的數據包括:運行日誌、異常堆棧、GC日誌、線程快照(threaddump/javacore文件)、堆轉儲快照(heapdump/hprof文件)等。常用適當的虛擬機監控和分析的工具能夠加快咱們分析數據和定位解決問題的速度,但咱們在學習工具前,也應當意識到工具永遠都是知識技能的一層包裝,沒有什麼工具是「祕密武器」,學會了就能包醫百病。html

JDK的命令行工具

Java開發人員確定都知道JDK的bin目錄下有「java.exe「和」javac.exe「這兩個命令行工具,但並不是全部程序員都瞭解過JDK的bin下其餘命令行程序的做用。每逢JDK更新版本之時,bin目錄下命令行工具的數量和功能總會不知不覺地增長和加強。
java

做者介紹了這些工具中的一部分,主要用於監視虛擬機和故障處理的工具。在軟件的使用說明中這些故障處理工具被聲明爲」沒有技術支持而且是實驗性質的「(unsupported and experimental)的產品,但事實上這些工具都很是穩定而且功能強大,能在處理應用程序性能問題、定位故障時發揮很大的做用。程序員

這些工具體積都異常的小,大多都在30KB左右。並不是JDK開發團隊刻意將他們製做得如此精煉來炫技,而是這些命令行工具大多數是jdk\lib\tools.jar類庫的一層薄封裝而已。它們主要的功能代碼是在tools類庫中實現的。spring

JDK團隊選擇採用Java代碼來實現這些監控工具是有特別用意的:當應用程序部署到生產環境後,不管是直接接觸物理服務器仍是遠程Telnet到服務器上均可能會受到限制。藉助tools.jar類庫裏面的接口,咱們能夠直接在應用程序中實現功能強大的監控分析功能。shell

本章介紹的工具所有基於windows平臺下的JDK 1.6,版本和操做系統不一樣會有不一樣。windows

表 Sun JDK監控和故障處理工具瀏覽器

名稱 主要功能
jps JVM Process Status Tool,顯示指定系統內全部HotSpot虛擬機進程
jstat JVM Statistics Minitoring Tool,用於收集HotSpot虛擬機各方面的運行數據
jinfo Configuration Info for Java,顯示虛擬機配置信息
jmap Memory Map for Java,生成虛擬機的內存轉儲快照(heapdump)文件
jhat JVM Heap Dump Browser,用於分析heapdump文件,它會創建一個HTTP/HTML服務器,讓用戶能夠在瀏覽器上查看分析結果
jstack Stack Trace for Java,顯示虛擬機的線程快照

jps:虛擬機進程情況工具

JDK的不少小工具的名稱都參考了Unix命令的命名方式,jps(JVM Process Status Tool)是其中的典型。除了名字像Unix的ps命令外,功能也和ps相似:能夠列出正在運行的虛擬機進程,並顯示虛擬機執行主類(Main Class,main()函數所在的類)的名稱,以及這些進程的本地虛擬機的惟一ID(LVMID,Local Virtual Machine Identifier)。雖然功能比較單一,但它是使用頻率最高的JDK命令行工具,由於其餘JDK工具大多須要輸入它查詢到的LVMID來肯定要監控的是哪個虛擬機進程。對於本地虛擬機進程來講,LVMID與操做系統的進程ID(PID,Process Identifier)是一致的,使用Windows的任務管理器或Unix的ps命令也能夠查詢到虛擬機進程的LVMID,但若是使用了多個虛擬機進程,沒法根據進程名稱定位時,那就只能依賴jps命令顯示主類的功能區才能區分了。服務器

jps命令格式:eclipse

jps [option] [hostid]函數

jps執行樣例:

C:\Users\Administrator>jps -l
5828 D:\Work\Develop\SpringToolSuite\springsource\sts-3.3.0.RELEASE\\plugins/org
.eclipse.equinox.launcher_1.3.0.v20130327-1440.jar
5680 sun.tools.jps.Jps

jps能夠經過RMI協議查詢開啓了RMI服務的遠程虛擬機進程狀態,hostid爲RMI註冊表中註冊的主機名。jps的其餘經常使用選項見下表

選項 做用
-q 只輸出LVMID,省略主類的名稱
-m 輸出虛擬機進程啓動時傳遞給主類的main()函數的參數
-l 輸出主類的全名,若是進程執行的是jar包,輸出jar路徑
-v 輸出虛擬機進程啓動時JVM參數

jstat:虛擬機統計信息監控工具

jstat(JVM Statistics Monitoring Tool)是用於監控虛擬機各類運行狀態信息的命令行工具。它能夠顯示本地或遠程虛擬機進程中的類裝載、內存、垃圾收集、JIT編譯等運行數據,在沒有GUI圖像界面,只提升了純文本控制檯環境的服務器上,它將是運行期定位虛擬機性能問題的首選工具。

jstat命令格式:

jstat [option vmid [interval[s|ms] [count]] ]

對於命令格式中的VMID與LVMID須要特別說明下:若是是本地虛擬機進程,VMID和LVMID是一致的,若是是遠程虛擬機進程,那VMID的格式應當是:

[protocol:][//] lvmid [@hostname[:port]/servername]

參數interval和count表明查詢間隔和次數,若是省略這兩個參數,說明只查詢一次。假設須要每250毫秒查詢一次進程5828垃圾收集情況,一共查詢5次,那命令行以下:

jstat -gc 5828 250 5

選項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編譯的方法

舉個例子:

C:\Users\Administrator>jstat -gcutil 5828
  S0     S1     E      O      P     YGC     YGCT    FGC    FGCT     GCT
  0.00   0.00   1.82  52.18  99.91    329    4.894   269   80.244   85.139

這是我監控到個人eclipse的內存情況。查詢結果代表:新生代Eden區(E,表示Eden)使用了1.82%的空間,兩個Survivor區(S0、S1,表示Survivor0、Survivor1)裏面都是空的,老年代(O,表示Old)和永久代(P,表示Permanent)則分別使用了52.18%和99.91%的空間。程序運行以來共發生Minor GC(YGC,Young GC)329次,總耗時(YGCT,Young GC Time)4.894秒,發生Full GC(FGC)269次,總耗時(FGCT)80.244秒,全部GC總耗時(GCT)85.139秒。

使用jstat工具在純文本狀態下監視虛擬機狀態的變化,確實不如後面將會提到的VisualVM等可視化的監視工具直接以圖表展示的那樣直觀。但不少服務器管理員都習慣了在文本控制檯共組哦,直接在控制檯中使用jstat命令已然是一種經常使用 的監控方式。

jinfo: Java配置信息工具

jinfo(Configuration Info for Java)的做用是實時地查看和調整虛擬機的各項參數。使用jps的命令的-v參數能夠查看虛擬機啓動時顯示指定的參數列表,但若是想知道未被顯示指定的參數的系統默認值,除了去找資料外,就只能使用jinfo的-flag選項進行查詢了(若是隻限於JDK1.6或以上版本的話,使用java -XX:+PrintFlagsFinal查看參數默認值也是一個很好的選擇),jinfo還可使用-sysprops選項把虛擬機進程的System.getProperties()的內容打印出來。這個命令在JDK1.5時期已經隨着Linux版的JDK發佈,當時只提供了信息查詢的功能,JDK1.6以後,jinfo在Windows和Linux平臺都有提供,而且加入了運行期修改參數的能力,可使用-flag[+|-]name或-flag name=valule修改一部分運行期可寫的虛擬機參數值。JDK1.6中,jinfo對於Windows平臺的功能仍然有較大的限制,只提供了最基本的-flag選項。

jinfo命令格式:

jinfo [option] pid

執行樣例:查詢CMSInitiatingOccupancyFraction參數值。

C:\Users\Administrator>jinfo -flag CMSInitiatingOccupancyFraction 5828
-XX:CMSInitiatingOccupancyFraction=-1

jmap: Java內存映像工具

jmap(Memory Map for Java)命令用於生產堆轉儲快照(通常稱爲heapdump或dump文件)。若是不使用jmap命令,要向獲取Java堆轉儲快照還有一些比較」暴力「的手段:譬如-XX:+HeapDumpOnOutOfMemoryError參數,可讓虛擬機在OOM異常出現以後自動生生成dump文件,經過-XX:+HeapDumpOnCtrlBreak參數則可使用[Ctrl]+[Break]鍵讓虛擬機生成dump文件,又或者在Linux系統下經過Kill -3命令發送進程退出信號」恐嚇「一下虛擬機,也能拿到dump文件。

jmap的做用並不只僅是爲了獲取dump文件,它還能夠查詢finalize執行隊列,Java堆和永久代的詳細信息,如空間使用率、當前用的是那種收集器等。

和jinfo命令同樣,jmap有很多功能在Windows平臺下是受限的,除了生成dump文件的-dump選項和用於查看每一個類的實例、空間佔用統計的-histo選項全部操做系統都提供外,其他選項只能在Linux/Solaris下使用。

jmap命令格式:

jmap [option] vmid

option選項合法值與具體含義:

選項 做用
-dump 生成Java堆轉儲快照。格式爲:-dump:[live,]format=b,file=<filename>,其中live子參數說明是否只dump出存活的對象
-finalizerinfo 顯示在F-Queue中等待Finalizer線程執行finalize()方法的對象。只在Linux/Solaris平臺下有效
-heap 顯示Java堆詳細信息,如使用哪一種回收器、參數配置、分代情況等。只在Linux/Solaris平臺下有效
-histo 顯示堆中對象統計信息,包括類、實例數量和合計容量
-permstat 以ClassLoader爲統計口徑顯示永久代內存狀態。只在Linux/Solaris平臺下有效
-F 當虛擬機進程對-dump選項沒有響應時,可以使用這個選項強制生成dump快照。只在Linux/Solaris平臺下有效

樣例:

C:\Users\Administrator>jmap -dump:format=b,file=eclipse.bin 5828
Dumping heap to C:\Users\Administrator\eclipse.bin ...
Heap dump file created

這是使用jmap生成一個正在運行的Eclipse的dump快照文件的例子,5828爲jps查詢到的LVMID。

jhat:虛擬機堆轉儲快照分析工具

Sun JDK提供了jhat(JVM Heap Analysis Tool)命令與jmap搭配使用,來分析jmap生成的堆轉儲快照。jhat內置了一個微型的HTTP/HTML服務器,生成dump文件的分析結果後,能夠在瀏覽器中查看,不過實事求是地說,在實際工做中,除非真的沒有別的工具可用,不然通常不會去直接使用jhat命令來分析demp文件,主要緣由有二:意識通常不會在部署應用程序的服務器上直接分析dump文件,即便能夠這樣作,也會盡可能將dump文件拷貝到其餘機器上進行分析,由於分析工做時一個耗時且消耗硬件資源的過程,既然都要在其餘機器上進行,就不必收到命令行工具的限制了。另一個緣由是jhat的分析功能相對來講很簡陋,VisualVM以及專門分析dump文件的Eclipse Memory Analyzer、IBM HeapAnalyzer等工具,都能實現比jhat更強大更專業的分析功能。

C:\Users\Administrator>jhat eclipse.bin
Reading from eclipse.bin...
Dump file created Tue Aug 19 16:01:10 CST 2014
Snapshot read, resolving...
Resolving 2126401 objects...
Chasing references, expect 425 dots...................................
......................................................................
......................................................................
......................................................................
......................................................................
............................................................
Eliminating duplicate references......................................
......................................................................
......................................................................
......................................................................
......................................................................
.........................................................
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.

"Server is ready."出現後就能夠在瀏覽器中鍵入http://localhost:7000 查看分析結果了。

分析結果默認以包爲單位進行分組顯示,分析內存泄露問題主要會使用到其中的」Heap Histogram「(與jmap-hosto功能同樣)與OQL頁籤的功能。前者能夠找到內存總容量最大的對象,後者是標準的對象查詢語句,使用相似SQL的語法對內存中的對象進行查詢統計。

jstack: Java堆棧跟蹤工具

jstack(Stack Trace for Java)命令用於生成虛擬機當前時刻的線程快照(通常稱爲threaddump或javacore文件)。線程快照就是當前虛擬機內每一條線程正在執行的方法堆棧的集合,生成線程快照的主要目的是定位線程出現長時間停頓的緣由,如線程間死鎖、死循環、請求外部資源致使的長時間等待等都是致使線程長時間停頓的常見緣由。線程出現停頓的時候經過jstack來查看各個線程的調用堆棧,就能夠知道沒有響應的線程到底在後臺作些什麼事情,或者等待着什麼資源。

jstack命令格式:

jstack [option] vmid

option選項的合法值與具體意義以下:

選項 做用
-F 當正常輸出的請求不被響應時,強制輸出線程堆棧
-l 除堆棧外,顯示關於鎖的附加信息
-m 若是調用到本地方法的話,能夠顯示C/C++的堆棧

實例(用jstack查看eclipse線程堆棧):

C:\Users\Administrator>jstack -l 5828

運行結果太長,不截取了。

在JDK1.5中,java.lang.Thread類新增了一個getAllStackTraces()方法用於獲取虛擬機中全部線程的StackTraceElement對象。使用這個方法能夠簡單的幾行代碼就完成jstack的大部分功能,在實際項目中不妨調用這個方法作個管理員頁面,能夠隨時使用瀏覽器來查看線程堆棧,代碼以下:

<%@ page import="java.util.Map"%>
<html>
<head>
<title>服務器線程信息</title>
</head>
<body>
<pre>
<% 
for(Map.Entry<Thread, StackTraceElement[]> stackTrace : Thread.getAllStackTraces().entrySet()){
    Thread thread = (Thread)stackTrace.getKey();
    StackTraceElement[] stack = (StackTraceElement[])stackTrace.getValue();
    if(thread.equals(Thread.currentThread())){
        continue;
    }
    out.print("\n線程:"+thread.getName()+"\n");
    for(StackTraceElement element : stack){
        out.print("\t"+element+"\n");
    }
}
%>
</pre>
</body>
</html>
相關文章
相關標籤/搜索