上節學習回顧
從課本章節划分,《垃圾收集器》和《內存分配策略》這兩篇隨筆同屬一章節,主要是從理論+實驗的手段來講解JVM的內存處理機制。好讓我們對JVM運行機制有一個良好的概念,才能繼續往下學習。
本節學習重點
本節主要是針對JVM內存管理機制的一些監控手段,例如堆情況使用的監控,線程棧情況的監控等。有幾句廢話還是有必要在這里強調的,工具是人類思維的工具,例如Java語言是人類滿足需求的一種技術手段,而監控工具只是維護程序應用的一種手段。所以,思考的邏輯思維要清晰,是問題引導工具,而不是工具引導問題。
在接下來我們要學習JVM性能監控工具之前,先理清一點,這些工具的服務對象是JVM,那么這些工具肯定跟JVM的結構脫不了關系,所以熟悉JVM結構等理論知識是必要的。例如JVM的區域組成部分中有虛擬機棧、方法區、堆等,當問題發生在虛擬機棧的時候(線程阻塞),如果異常信息不能協助定位問題的情況下,那么接下來肯定是得借助監控虛擬機棧工具或監控虛擬機棧插件來協助解決問題。如果對JVM沒有很好的認識,這些工具都是雞肋。工具只是協助作用,不是解決問題的根本。
注意:下面描述的所有場景都是基於JDK1.6.0_45測試的。
虛擬機性能監控工具:JDK命令行工具
在JDK的bin目錄中,有我們熟知的Java語言編程編譯器命令“javac”以及執行命令“java”,此外里面還有一堆比較少用的命令,而其中有許多是JDK自帶的性能監控和故障處理命令,下面逐一介紹,bin目錄如下圖所示:
名稱 |
主要作用 |
jps |
JVM Process Status Tool,顯示指定系統內所有的HotSpot虛擬機進程 |
jstat |
JVM Statistics Monitoring 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 Processs Status Tool)是其中的典型,除了名字像UNIX的ps命令之外,它的功能也和ps命令類似:可以列出正在運行的虛擬機進程,並顯示虛擬機執行主類(Main Class,main()函數所在的類)名稱以及這些進程的本地虛擬機唯一ID(Local Virtual Machine Identifier,LVMID)。LVMID與操作系統的進程ID是一致的。
jps命令格式:jsp [options] [hostid]
jps執行樣例:
[root@WC01 bin 16:02 #18]$ jps -l 3850 org.apache.catalina.startup.Bootstrap 10579 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毫秒查詢一次進程ID為2764的垃圾收集狀況,一共查詢20次,那命令應當是:
jstat –gc 2764 250 20
選項option代表着用戶希望查詢的虛擬機信息,主要分為3類:類裝載、垃圾收集、運行期編譯狀況,具體選項作用請參考以下表格描述:
選項 |
作用 |
-class |
監視類裝載,卸載數量、總空間以及類裝載所耗費的時間 |
-gc |
監視Java堆情況,包括Eden區、兩個survivor區、老年代、永久代等的容量、已用空間、GC時間合計等信息 |
-gccapacity |
監視內容與-gc基本相同,但輸出主要關注Java堆各個區域使用到的最大、最小空間 |
-gcutil |
監視內容與-gc基本相同,但輸出主要關注已使用空間占總空間的百分比 |
-gccause |
與-gcutil功能一樣,但是會額外輸出導致上一次GC產生的原因 |
-gcnew |
監視新生代GC情況 |
-gcnewcapacity |
監視內容與-gcnew基本相同,輸出主要關注使用到的最大、最小空間 |
-gcold |
監視老年代GC狀況 |
-gcoldcapacity |
監視內容與-gcold基本相同,輸出主要關注使用到的最大、最小空間 |
-gcpermcapacity |
輸出永久代使用到的最大、最小空間 |
-compiler |
輸出JIT編譯器編譯過的方法、耗時等信息 |
-printcompilation |
輸出已經被JIT編譯的方法 |
jstat執行樣例:
[root@WC01 bin 17:59 #35]$ jstat -gcutil 3850
S0 S1 E O P YGC YGCT FGC FGCT GCT
0.00 50.00 71.32 42.89 50.56 639 1.727 5 0.327 2.054
S0、S1表示Survivor1、Survivor2,分別使用了0%和50%的空間;
E表示Eden區,使用了71.32%的空間;
O表示老年代Old區,使用了42.89%的空間;
P表示永久代Pernament區,使用了50.56的空間;
YGC表示執行Minor GC的次數,一共執行了639次;
YGCT表示執行Minor GC的總耗時為1.727秒
FGC表示執行Full GC的次數,一共執行了5次;
FGCT表示執行Full GC的總耗時為0.327秒;
GCT表示所有GC總耗時為2.054秒。
- jinfo:Java配置信息工具
jinfo(Configuration Info for Java)的作用是實時地查看和調整虛擬機各項參數。
jinfo命令格式:jinfo [option] pid
其中option選項可參考一下描述:
選項 |
作用 |
-flag |
輸出指定虛擬機參數,如jinfo -flag MaxHeapSize pid |
-sysprops |
輸出虛擬機進程的System.getProperties()的內容 |
-flag[+|-] name |
修改虛擬機參數值 |
-flag nam=value |
同上 |
注:JDK1.6中,jinfo對於Windows平台功能仍然有較大的限制,只提供最基本的-flag選擇。
jinfo執行樣例:
[root@WC01 bin 18:21 #47]$ jinfo -flag MaxHeapSize 3850 -XX:MaxHeapSize=492830720
- jmap:Java內存映像工具
jmap(Memory Map for Java)命令用於生成堆轉儲快照(一般稱為heapdump或dump文件)。如果不使用jmap命令,要想獲得Java堆轉儲快照,還有一些比較“暴力”的手段;
1、通過-XX:+HeapDumpOnOutOfMemoryError參數,可以讓虛擬機在OOM異常出現之后自動生成dump文件;
2、通過-XX:HeapDumpOnCtrlBreak參數則可以使用[Ctrl]+[Break]鍵讓虛擬機生成dump文件;
3、可在Linux系統下通過kill -3命令發送進程退出信號“嚇唬”一下虛擬機,也能拿到dump文件。
jmap命令格式:jmap [option] vmid
jmap的作用並不僅僅是為了獲取dump文件,它還可以查詢finalize執行隊列、Java堆和永久代的詳細信息,如果空間使用率、當前用的是哪種收集器等。請參考以下選項參數描述:
選項 |
作用 |
-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平台下有效 |
jmap執行樣例:
[root@WC01 dumpfile 18:49 #66]$ jmap -dump:format=b,file=tomcat.bin 3850 Dumping heap to /usr/local/apache-tomcat-6.0.43/logs/dumpfile/tomcat.bin ... Heap dump file created [root@WC01 dumpfile 18:49 #67]$ ls tomcat.bin [root@WC01 dumpfile 18:49 #68]$
- jhat:虛擬機堆轉儲快照分析工具
Sun JDK提供jhat(JVM Heap Analysis Tool)命令與jmap搭配使用,來分析jmap生成的堆轉儲快照。Jhat內置了一個微型的HTTP/HTML服務器。生產dump文件的分析結果后,可以在瀏覽器中查看。
jhat執行樣例:
[root@WC01 dumpfile 12:35 #9]$ jhat tomcat.bin Reading from tomcat.bin... Dump file created Wed Jul 20 18:49:38 CST 2016 Snapshot read, resolving... Resolving 280853 objects... Chasing references, expect 56 dots........................................................ Eliminating duplicate references........................................................ Snapshot resolved. Started HTTP server on port 7000 Server is ready.
用戶在瀏覽器中輸入http://localhost:7000/就可以看到分析結果了,如下圖所示:
注意:如果有其它工具可以分析,否則不建議使用jhat。首先,一般不會直接在生產環境直接分析dump文件,因為分析dump文件是一件耗時耗資源的事情,條件允許的話首選圖形分析工具(后面會介紹);其次是jhat的分析功能過於簡陋。
- jstack:Java堆棧跟蹤工具
jstack(Stack Trace for Java)命令用於生產虛擬機當前時刻的線程快照(一般稱為threaddump或者javacore文件)。線程快照就是當虛擬機內每一條線程正在執行的方法堆棧集合,生產線程快照的主要目的是定位線程出現長時間停頓的原因,如線程間死鎖、死循環、請求外部資源導致長時間等待等都是導致線程長時間停頓的常見原因。線程出現停頓的時候通過jstack來查看各個線程的調用堆棧,就可以知道沒有響應的線程到底在后台做些什么事情,或者等待着什么資源。
jstack命令格式:jstack [option] vmid
option選擇的合法值域具有含有請看下表:
選項 |
作用 |
-F |
當正常輸出的請求不被響應時,強制輸出線程堆棧 |
-l |
除堆棧外,顯示關於鎖的附加信息 |
-m |
如果調用到本地方法的話,可以顯示C/C++的堆棧 |
jstack執行樣例:
[root@WC01 dumpfile 12:51 #13]$ jstack -l 3845
2016-07-21 12:51:53 Full thread dump Java HotSpot(TM) Server VM (20.45-b01 mixed mode): "Attach Listener" daemon prio=10 tid=0x0852b000 nid=0x1103 waiting on condition [0x00000000] java.lang.Thread.State: RUNNABLE Locked ownable synchronizers: - None ...... ...... ...... "main" prio=10 tid=0x08058400 nid=0xf06 runnable [0xf6940000] java.lang.Thread.State: RUNNABLE at java.net.PlainSocketImpl.socketAccept(Native Method) at java.net.PlainSocketImpl.accept(PlainSocketImpl.java:408) - locked <0xea12b248> (a java.net.SocksSocketImpl) at java.net.ServerSocket.implAccept(ServerSocket.java:462) at java.net.ServerSocket.accept(ServerSocket.java:430) at org.apache.catalina.core.StandardServer.await(StandardServer.java:430) at org.apache.catalina.startup.Catalina.await(Catalina.java:676) at org.apache.catalina.startup.Catalina.start(Catalina.java:628) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:289) at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:414) Locked ownable synchronizers: - None "VM Thread" prio=10 tid=0x080d4000 nid=0xf09 runnable "GC task thread#0 (ParallelGC)" prio=10 tid=0x0805f400 nid=0xf07 runnable "GC task thread#1 (ParallelGC)" prio=10 tid=0x08060c00 nid=0xf08 runnable "VM Periodic Task Thread" prio=10 tid=0xd1978400 nid=0xf13 waiting on condition JNI global references: 1089
- HSDIS:JIT生成代碼反匯編
由於本人沒有學習過匯編,所以在此章節部分的匯編內容看不懂,所以在這里也只簡單介紹一下這個虛擬機插件的大概使用,后續本人學習了匯編語言后有機會再寫相關的學習資料。
HSDIS是一個Sun官方推薦的HotSpot虛擬機JIT編譯代碼的反匯編插件。在這先插兩句對JIT的簡單描述,JIT是講.class字節碼翻譯成本機的機器代碼(就是0和1),至於為什么這么做,肯定是提高效率,更多JIT知識可自行學習。 而HSDIS就是把這些被JIT翻譯過的機器碼(0和1)反編譯為匯編(面向IT人員的開發語言)。為什么要無端端把機器語言翻譯會開發人員看得懂的匯編語言,是因為當我們需要檢查程序性能的時候,希望更能接近計算機語言的本質去分析,由於匯編是直接面向硬件的,而機器語言我們又看不懂,所以最接近本質的還是匯編語言。當然,我們可以基於字節碼(.class)層面上進行分析,但隨着技術的發展,這些字節碼指令的執行過程等細節與虛擬機規范所描述的相差越來越遠,就是字節碼的行為跟機器碼的行為有可能差異很大。所以通過匯編語言分析可以更接近計算機語言的本質。
這個HSDIS JIT反匯編插件包含在HotSpot虛擬機的源碼之中,但沒有提供編譯后的程序。在Project Kenai的網站也可以下載到單獨的源碼。它的作用是讓HotSpot的-XX:PrintAssembly指令調用它來把動態生成的本地代碼還原為匯編代碼輸出,同時還生成了大量有價值的注釋,這樣我們就可以通過輸出代碼來分析問題。讀者可以根據自己的操作系統和CPU類型從Project Kenai的網站上下載編譯好的插件,直接放到JDK_HOME/jre/bin/client和JDK_HOME/jre/bin/server目錄中即可。如果沒有找到所需操作系統的成品,那就得自己使用源碼編譯一下。
更多詳情可以自行參考《深入理解Java虛擬機》P112部分內容。
虛擬機性能監控工具:JDK的可視化工具
JDK中除了提供大量的命令行工具外,還有兩個功能強大的可視化工具:JConsole和VisualVM,這兩個工具是JDK的正式成員。
- JConsole:Java監視與管理控制台
JConsole(Java Monitoring and Management Console)是一種基於JMX的可視化監視、管理工具。它管理部分的功能是針對JMX Mbean進行管理。知識擴展:JMX(Java Management Extensions)即ava管理擴展,MBean(managed beans)即被管理的Beans。一個MBean是一個被管理的Java對象,有點類似於JavaBean,一個設備、一個應用或者任何資源都可以被表示為MBean,MBean會暴露一個接口對外,這個接口可以讀取或者寫入一些對象中的屬性。
通過JDK_HOME/bin目錄下的“jconsole.exe”啟動JConsole后,講自動搜索出本機運行的所有虛擬機進程,不需要用戶自己再用JPS來查詢了。也可以使用下面的“遠程進程”功能來連接遠程服務器,對遠程虛擬機進行監控,如下圖所示:
從以上截圖可以看出,我本地的機器運行了MyEclipse(7888)、JConsole(6084)和Tomcat(2496)三個虛擬機,而下面是我的一個遠程虛擬機,需要開啟JMX服務才能連接。下圖為進入JConsole的主頁:
“概述”頁簽顯示的是整個虛擬機主要運行數據的概覽,其中包括“堆內存使用情況”、“線程”、“類”、“CPU使用情況”4種信息的曲線圖,這些曲線圖是后面“內存”、“線程”、“類”頁簽的信息匯總。其它詳細的頁簽都不加以說明,自行學習吧。
- VisualVM:多合一故障處理工具
VisualVM(ALL-in-One Java Troubleshooting Tool)是到目前為止隨JDK發布的功能最強大的運行監控和故障處理程序,並且可以預見在未來的一段時間內都是官方主力發展的虛擬機故障處理工具。VisuaVM是基於NetBean平台開發的,因此它一開始就具備了插件擴展功能的特性,通過插件擴展支持,VisualVM幾乎可以做到所欲命令行工具的功能和其它Plugins的無限可能性。
首次啟動VisualVM后,讀者先不必着急找應用程序進行監控,因為現在VisualVM還沒有加載任何插件,雖然基本的監控、線程面板的功能主程序都以默認插件的形式提供了,但是不給VisualVM裝任何擴展插件,就相當於放棄了它最精華的功能,和沒有安裝任何應用軟件的操作系統差不多。下圖為進入VisualVM的主頁:
插件可以進行手工安裝,在相關網站上下載*.nbm包后,點擊“工具—插件—已下載”菜單,然后在彈出的對話框中指定nbm包枯井變可以進行安裝,插件安裝后存放在JDK_HOME/lib/visualvm/visualvm中。當然同樣可以在有網絡連接的環境下選擇自動安裝,點擊“工具—插件—可用插件”彈出如下圖所示的插件頁簽中選擇合適的插件安裝即可。
從VisualVM主頁的左菜單欄可以看到,顯示的虛擬機進程跟JConsole顯示的是一樣的,還有一個遠程虛擬機進程。當我點擊進入一個虛擬機進程后的進程主頁如以下所示(不同版本可以會有所差異):
虛擬機進程主頁包含了“概述”、“監視”、“抽樣器”、“Visual GC”頁簽,其中“Visual GC”是我自己安裝的插件,更詳細的VisualVM使用說明就不多說了,想了解的可自行學習。
總結
其實,在學習本章知識之前,我是沒有在任何系統的生成環境使用過這些JVM性能監控工具的,因為不同虛擬機的JDK分析都有自己的分析工具,況且不同企業的開發環境也不一樣。比如我所開發系統的生成環境,完全與外網隔離,只能在一個密封的環境通過內網終端去連接生成環境進行維護。而且我們使用的是WAS中間件(內置IBM J9虛擬機),完全沒有以上介紹的命令行工具。WAS自帶了一個性能監控控制台,同樣是插件化形式的可視化監控工具。關於一些異常分析,我是通過kill -3生成多個短時間間隔的dump文件通過 IBM Thread and Monitor Dump Analyzer for Java工具進行詳細分析。隨讓工具不一樣,但問題分析思路是一樣的,就像我文章開頭所強調的,是問題引導工具而不是工具引導問題。