給一個系統定位問題的時候,知識、經驗是關鍵基礎,數據是依據,工具是運用知識處理數據的手段
數據 運行日誌、異常堆棧、GC日誌、線程快照(threaddump/javacore文件)、堆轉儲快照(heapdump/hprof文件)等。
JDK命令行 | 位置bin下的程序 |
---|---|
特點 | 27KB 是jdk/lib/tools.jar的封裝,核心實現再tools.jar中,tool.jar只支持hotpot |
jps | JVM Process Status Tool 顯示指定系統內所有的HotSpot虛擬機進程 |
jstat | JVM statistics Monitoring Tool 用於收集HotSpot虛擬機各方面的運行數據 |
jinfo | Configuration Info for jva 顯示虛擬機配置信息 |
jmap | Memory Map for java 生成虛擬機的內存轉儲快照(headdump文件) |
jhat | JVM Heap Dump Browser 用於分析headdump文件,它會建立一個HTTP/HTML服務器,讓用戶可以在瀏覽器上查看分析結果 |
jstack | Stack Trace for java 顯示虛擬機的線程快照 |
- JPS
除了名字像UNIX的ps命令之外,它的功能也和ps命令類似:可以列出正在運行的虛擬機進程,並顯示虛擬機執行主類(Main Class,main()函數所在的類)名稱以及這些進程的本地虛擬機唯一ID(Local Virtual Machine Identifier,LVMID)
命令格式
jps[options][hostid]
- jstat:虛擬機統計信息監視工具
命令格式
jstat[option vmid[interval[s|ms][count]]]
eg
參數interval和count代表查詢間隔和次數,如果省略這兩個參數,說明只查詢一次。假設需要每250毫秒查詢一次進程2764垃圾收集狀況,一共查詢20次
jstat -gc 2764 250 20
執行樣例
這臺服務器的新生代Eden區(E,表示Eden)使用了6.2%的空間,兩個Survivor區(S0、S1,表示Survivor0、Survivor1)裏面都是空的,老年代(O,表示Old)和永久代(P,表示Permanent)則分別使用了41.42%和47.20%的空間。程序運行以來共發生Minor GC(YGC,表示Young GC)16次,總耗時0.105秒,發生Full GC(FGC,表示Full GC)3次,Full GC總耗時(FGCT,表示Full GC Time)爲0.472秒,所有GC總耗時(GCT,表示GC Time)爲0.577秒
- jmap
命令用於生成堆轉儲快照(一般稱爲heapdump或dump文件)。jmap的作用並不僅僅是爲了獲取dump文件,它還可以查詢finalize執行隊列、Java堆和永久代的詳細信息,如空間使用率、當前用的是哪種收集器等。
命令格式
jmap[option]vmid
- jhat 虛擬機堆轉儲快照分析工具
Sun JDK提供jhat(JVM Heap Analysis Tool)命令與jmap搭配使用,來分析jmap生成的堆轉儲快照。jhat內置了一個微型的HTTP/HTML服務器,生成dump文件的分析結果後,可以在瀏覽器中查看。不過實事求是地說,在實際工作中,除非筆者手上真的沒有別的工具可用,否則一般都不會去直接使用jhat命令來分析dump文件,主要原因有二:一是一般不會在部署應用程序的服務器上直接分析dump文件,即使可以這樣做,也會盡量將dump文件複製到其他機器[1]上進行分析,因爲分析工作是一個耗時而且消耗硬件資源的過程,既然都要在其他機器進行,就沒有必要受到命令行工具的限制了;另一個原因是jhat的分析功能相對來說比較簡陋,後文將會介紹到的VisualVM,以及專業用於分析dump文件的Eclipse Memory Analyzer、IBM HeapAnalyzer[2]等工具,都能實現比jhat更強大更專業的分析功能。代碼清單[…]
屏幕顯示"Server is ready.“的提示後,用戶在瀏覽器中鍵入http://localhost:7000/就可以看到分析結果,如圖4-3所示。
分析結果默認是以包爲單位進行分組顯示,分析內存泄漏問題主要會使用到其中的"Heap Histogram”(與jmap-histo功能一樣)與OQL頁籤的功能,前者可以找到內存中總容量最大的對象,後者是標準的對象查詢語言,使用類似SQL的語法對內存中的對象進行查詢統計,讀者若對OQL有興趣的話,可以參考本書附錄D的介紹。
- jstack
jstack(Stack Trace for Java)命令用於生成虛擬機當前時刻的線程快照(一般稱爲threaddump或者javacore文件)。線程快照就是當前虛擬機內每一條線程正在執行的方法堆棧的集合,生成線程快照的主要目的是定位線程出現長時間停頓的原因,如線程間死鎖、死循環、請求外部資源導致的長時間等待等都是導致線程長時間停頓的常見原因。線程出現停頓的時候通過jstack來查看各個線程的調用堆棧,就可以知道沒有響應的線程到底在後臺做些什麼事情,或者等待着什麼資源
jstack命令格式:
jstack[option]vmid
eg:
使用jstack查看Eclipse線程堆棧的例子,例子中的3500是通過jps命令查詢到的LVMID
在JDK 1.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>”
- HSDIS JIT生成代碼反彙編
JIT 編譯器動態生成
分析程序如何執行,通過軟件調試工具(GDB、Windbg等)來斷點調試是最常見的手段,但是這樣的調試方式在Java虛擬機中會遇到很大困難,因爲大量執行代碼是通過JIT編譯器動態生成到CodeBuffer中的,沒有很簡單的手段來處理這種混合模式的調試(不過相信虛擬機開發團隊內部肯定是有內部工具的)。因此,不得不通過一些特別的手段來解決問題,基於這種背景,本節的主角——HSDIS插件就正式登場。
備註對打印的信息看不懂,需要更細的學習,但這次不需要深入,理解即可
##JDK的可視化工具
JDK可視工具 | |
---|---|
JConsole | * |
VisualVM | * |
###JConsole
JConsole(Java Monitoring and Management Console)是一種基於JMX的可視化監視、管理工具。它管理部分的功能是針對JMX MBean進行管理,由於MBean可以使用代碼、中間件服務器的管理控制檯或者所有符合JMX規範的軟件進行訪問,所以本節將會着重介紹JConsole監視部分的功能。
1. 啓動JConsole
通過JDK/bin目錄下的"jconsole.exe"啓動JConsole後,將自動搜索出本機運行的所有虛擬機進程,不需要用戶自己再使用jps來查詢了,如圖4-4所示。雙擊選擇其中一個進程即可開始監控,也可以使用下面的“遠程進程”功能來連接遠程服務器,對遠程虛擬機進行監控。
機器現在運行了Eclipse、JConsole和MonitoringTest三個本地虛擬機進程,其中MonitoringTest就是筆者準備的“反面教材”代碼之一。雙擊它進入JConsole主界面,可以看到主界面裏共包括“概述”、“內存”、“線程”、“類”、“VM摘要”、"MBean"6個頁籤
2. 內存監控
內存”頁籤相當於可視化的jstat命令,用於監視受收集器管理的虛擬機內存(Java堆和永久代)的變化趨勢。我們通過運行代碼清單4-8中的代碼來體驗一下它的監視功能。運行時設置的虛擬機參數爲:-Xms100m-Xmx100m-XX:+UseSerialGC,這段代碼的作用是以64KB/50毫秒的速度往Java堆中填充數據,一共填充1000次,使用JConsole的“內存”頁籤進行監視,觀察曲線和柱狀指示圖的變化。
3. 線程監控
如果上面的“內存”頁籤相當於可視化的jstat命令的話,“線程”頁籤的功能相當於可視化的jstack命令,遇到線程停頓時可以使用這個頁籤進行監控分析。前面講解jstack命令的時候提到過線程長時間停頓的主要原因主要有:等待外部資源(數據庫連接、網絡資源、設備資源等)、死循環、鎖等待(活鎖和死鎖)
VisualVM:多合一故障處理工具
VisualVM(All-in-One Java Troubleshooting Tool)是到目前爲止隨JDK發佈的功能最強大的運行監視和故障處理程序,並且可以預見在未來一段時間內都是官方主力發展的虛擬機故障處理工具。官方在VisualVM的軟件說明中寫上了"All-in-One"的描述字樣,預示着它除了運行監視、故障處理外,還提供了很多其他方面的功能。如性能分析(Profiling),VisualVM的性能分析功能甚至比起JProfiler、YourKit等專業且收費的Profiling工具都不會遜色多少,而且VisualVM的還有一個很大的優點:不需要被監視的程序基於特殊Agent運行,因此它對應用程序的實際性能的影響很小,使得它可以直接應用在生產環境中。這個優點是JProfiler、YourKit等工具無法與之媲美的。
1.VisualVM兼容範圍與插件安裝”
VisualVM基於NetBeans平臺開發,因此它一開始就具備了插件擴展功能的特性,通過插件擴展支持,VisualVM可以做到:
- 監視應用程序的CPU、GC、堆、方法區以及線程的信息(jstat、jstack)。
- dump以及分析堆轉儲快照(jmap、jhat)。
- 方法級的程序運行性能分析,找出被調用最多、運行時間最長的方法。
- 離線程序快照:收集程序的運行時配置、線程dump、內存dump等信息建立一個快照,可以將快照發送開發者處進行Bug反饋。
其他plugins的無限的可能性……
首次啓動VisualVM後,讀者先不必着急找應用程序進行監測,因爲現在VisualVM還沒有加載任何插件,雖然基本的監視、線程面板的功能主程序都以默認插件的形式提供了,但是不給VisualVM裝任何擴展插件,就相當於放棄了它最精華的功能,和沒有安裝任何應用軟件操作系統差不多。
插件可以進行手工安裝,在相關網站[2]上下載*.nbm包後,點擊“工具”→“插件”→“已下載”菜單,然後在彈出的對話框中指定nbm包路徑便可進行安裝,插件安裝後存放在JDK_HOME/lib/visualvm/visualvm中。不過手工安裝並不常用,使用VisualVM的自動安裝功能已經可以找到大多數所需的插件,在有網絡連接的環境下,點擊“工具”→“插件菜單”,彈出如圖4-11所示的插件頁籤,在頁籤的“可用插件”中列舉了當前版本VisualVM可以使用的插件,選中插件後在右邊窗口將顯示這個插件的基本信息,如開發者、版本、功能描述等。
大家可以根據自己的工作需要和興趣選擇合適的插件,然後點擊安裝按鈕,彈出如圖4-12所示的下載進度窗口,跟着提示操作即可完成安裝。
安裝完插件,選擇一個需要監視的程序就進入程序的主界面了,如圖4-13所示。根據讀者選擇安裝插件數量的不同,看到的頁籤可能和圖4-13中的有所不同。
VisualVM中“概述”、“監視”、“線程”、"MBeans"的功能與前面介紹的JConsole差別不大,讀者根據上文內容類比使用即可,下面挑選幾個特色功能、插件進行介紹
2.生成、瀏覽堆轉儲快
“在VisualVM中生成dump文件有兩種方式,可以執行下列任一操作:
在“應用程序”窗口中右鍵單擊應用程序節點,然後選擇“堆Dump”。
在“應用程序”窗口中雙擊應用程序節點以打開應用程序標籤,然後在“監視”標籤中單擊“堆Dump”。
生成了dump文件之後,應用程序頁籤將在該堆的應用程序下增加一個以[heapdump]開頭的子節點,並且在主頁籤中打開了該轉儲快照,如圖4-14所示。如果需要把dump文件保存或發送出去,要在heapdump節點上右鍵選擇“另存爲”菜單,否則當VisualVM關閉時,生成的dump文件會被當做臨時文件刪除掉。要打開一個已經存在的dump文件,通過文件菜單中的“裝入”功能,選擇硬盤上的dump文件即可。
從堆頁籤中的“摘要”面板可以看到應用程序dump時的運行時參數、System.getProperties()的內容、線程堆棧等信息,“類”面板則是以類爲統計口徑統計類的實例數量、容量信息,“實例”面板不能直接使用,因爲不能確定用戶想查看哪個類的實例,所以需要通過“類”面板進入,在“類”中選擇一個關心的類後雙擊鼠標,即可在“實例”裏面看見此類中500個實例的具體屬性信息。“OQL控制檯”面板中就是運行OQL查詢語句的,同jhat中介紹的OQL功能一樣。如果需要了解具體OQL語法和使用,可參見本書附錄D的內容。
3.分析程序性能
在Profiler頁籤中,VisualVM提供了程序運行期間方法級的CPU執行時間分析以及內存分析,做Profiling分析肯定會對程序運行性能有比較大的影響,所以一般不在生產環境中使用這項功能。
要開始分析,先選擇"CPU"和“內存”按鈕中的一個,然後切換到應用程序中對程序進行操作,VisualVM會記錄到這段時間中應用程序執行過的方法。如果是CPU分析,將會統計每個方法的執行次數、執行耗時;如果是內存分析,則會統計每個方法關聯的對象數以及這些對象所佔的空間。分析結束後,點擊“停止”按鈕結束監控過程,如圖4-15所示。
注意 在JDK 1.5之後,在Client模式下的虛擬機加入並且自動開啓了類共享——這是一個在多虛擬機進程中共享rt.jar中類數據以提高加載速度和節省內存的優化,而根據相關Bug報告的反映,VisualVM的Profiler功能可能會因爲類共享而導致被監視的應用程序崩潰,所以讀者進行Profiling前,最好在被監視程序中使用-Xshare:off參數來關閉類共享優化。
圖4-15中是對Eclipse IDE一段操作的錄製和分析結果,讀者分析自己的應用程序時,可以根據實際業務的複雜程度與方法的時間、調用次數做比較,找到最有優化價值的方法。
4.BTrace動態日誌跟蹤
BTrace[3]是一個很“有趣”的VisualVM插件,本身也是可以獨立運行的程序。它的作用是在不停止目標程序運行的前提下,通過HotSpot虛擬機的HotSwap技術[4]動態加入原本並不存在的調試代碼。這項功能對實際生產中的程序很有意義:經常遇到程序出現問題,但排查錯誤的一些必要信息,譬如方法參數、返回值等,在開發時並沒有打印到日誌之中,以至於不得不停掉服務,通過調試增量來加入日誌代碼以解決問題。當遇到生產環境服務無法隨便停止時,缺一兩句日誌導致排錯進行不下去是一件非常鬱悶的事情。
在VisualVM中安裝了BTrace插件後,在應用程序面板中右鍵點擊要調試的程序,會出現"Trace Application……"菜單,點擊將進入BTrace面板。這個面板裏面看起來就像一個簡單的Java程序開發環境,裏面還有一小段Java代碼,如圖4-16所示。