JDK自帶工具之排查問題示例
最近看到了大量關于java性能調優、故障排查的文章,自己也寫了一篇Java調優經驗談。接著此篇文章,其實一直打算寫寫一些常用調優的工具以及慣常用法的。后來在 http://java-performance.info 這個站點上看到了類似的一篇博文,自我感覺很有指導意義。于是決定翻譯+重組織一下此篇文章: Java server application troubleshooting using JDK tools 。
引言
在Java世界中,我們的很多開發工作從編碼、調試到調優都是在使用GUI工具進行。我們經常嘗試在本地構建一套和生產環境一樣的環境從而使得問題能夠重現,進而使用我們常用的工具來排查定位故障。但不幸的是,很多情況下我們是無法在本地重現線上問題的。例如,我們是沒有權限獲取線上真實客戶端提交到服務端的數據的。
由于上文提到的問題,很多時候都是需要遠程來排查線上服務器上發生的問題。但是如果單單只有一個JRE的話,你也是無法有合適的方案來進行排查的。你需要JDK或者第三方的工具。有時候使用JDK提供的工具就是最可取的方案,畢竟,在線上環境使用第三方工具有時候會牽扯到權限的問題。
一般情況下,在線上環境安裝JDK發布版本可以讓排查進行的更高效。建議安裝使用最新的Java7/8 JDK或者構建與線上JRE匹配的一些工具。(原文作者貌似不建議安裝jdk的發布版本,而是建議逐漸地根據需求安裝這些)
問題排查場景
獲取正在運行的JVM列表
為了開始排查工作,我們首先需要獲取正在運行的jvm進程列表,包括進程id、命令行參數等。有時候僅僅這一步就可以定位到問題,例如,同樣的app被多啟動一次在并發做同樣的事情(破壞輸出文件、重新打開sockets后者其他愚蠢的事情)。
使用 jcmd 不加任何參數即可獲取jvm進程列表
25691 org.apache.catalina.startup.Bootstrap start
20730 org.apache.catalina.startup.Bootstrap start
26828 sun.tools.jcmd.JCmd
3883 org.apache.catalina.startup.Bootstrap start
使用 jcmd <PID> help 能夠獲取某個jvm進程其他可用的診斷命令。例如:
[root@test-172-16-0-34-ip ~]# jcmd 3883 help
3883:
The following commands are available:
VM.commercial_features
ManagementAgent.stop
ManagementAgent.start_local
ManagementAgent.start
Thread.print
GC.class_histogram
GC.heap_dump
GC.run_finalization
GC.run
VM.uptime
VM.flags
VM.system_properties
VM.command_line
VM.version
help
輸入 jcmd <PID> <COMMAND_NAME> 可以運行一個診斷命令或者獲取到參數錯誤信息。
[root@test-172-16-0-34-ip ~]# jcmd 3883 GC.heap_dump
3883:
java.lang.IllegalArgumentException: Missing argument for diagnostic command
通過 jcmd <PID> help <COMMAND_NAME> 你能夠獲取此診斷命令更多的信息。如下是 GC.heap_dump 命令的help。
[root@test-172-16-0-34-ip ~]# jcmd 3883 help GC.heap_dump
3883:
GC.heap_dump
Generate a HPROF format dump of the Java heap.
Impact: High: Depends on Java heap size and content. Request a full GC unless the '-all' option is specified.
Syntax : GC.heap_dump [options] <filename>
Arguments:
filename : Name of the dump file (STRING, no default value)
Options: (options must be specified using the <key> or <key>=<value> syntax)
-all : [optional] Dump all objects, including unreachable objects (BOOLEAN, false)
Java堆的DUMP
jcmd提供了輸出HPROF格式的堆dump接口。運行 jmcd <PID> GC.heap_dump <FILENAME> 即可。注意這里的FILENaME是相對于運行中的jvm目錄在說的,因此推薦使用絕對路徑。此外,也建議使用.hprof作為輸出文件的擴展名。
在堆dump完成之后,你可以復制此文件到本地用VisualVM或者用jmc的JOverflow插件打開,進而通過分析堆的狀況定位內存問題。
需要注意的兩點:
- 還有很多可以打開分析hprof文件的工具:NetBeans, Elipse的MAT,jhat等等。用你最熟悉的即可。
- 同樣可以使用 jmap -dump:live,file=<FILE_NAME> <PID> 來產生堆dump文件,但是官方文檔標注了此工具為unsupported的。雖然我們絕大多數人都會認為JDK中unsupported的特性會永遠存在,但是事實并非這樣: JEP 240 , JEP 241 。
分析類柱狀圖
如果正在排查內存泄漏問題,你可能想要知道堆中某種類型的存活對象數目。例如,某一時刻某些類應該只有一個實例(單例模式),但是此類的另外一個或者多個實例卻已經到了老年代,但是它們不應該能被GC roots訪問到。
運行以下命令可以打印出類柱狀圖(同時也打印出存活對象的數目):
jcmd <PID> GC.class_histogram
jmap -histo:live <PID>
輸入如下:
num #instances #bytes class name
----------------------------------------------
1: 37083 48318152 [B
2: 235781 22496784 [C
3: 103958 16069448 <constMethodKlass>
4: 482361 15435552 java.util.HashMap$Entry
5: 103958 14152480 <methodKlass>
6: 9576 11192168 <constantPoolKlass>
7: 186264 10430784 com.mysql.jdbc.ConnectionPropertiesImpl$BooleanConnectionProperty
8: 274109 8771488 java.util.Hashtable$Entry
9: 9576 7210152 <instanceKlassKlass>
10: 7972 6404256 <constantPoolCacheKlass>
11: 229637 5511288 java.lang.String
12: 48471 5428752 java.net.SocksSocketImpl
13: 21599 3859672 [Ljava.util.HashMap$Entry;
這里的以byte為單位的占用大小是淺的(shallow size),并沒有包括子對象的大小。其實很容易注意到char[]和String的統計數據:這倆的實例數目是差不多的,但是char[]的占用大小要大很多,這在于String并未包含下面的char[]的大小。
有了類柱狀圖信息,你就可以grep/search類的名字從而獲取存活實例的數目。如果你發現比期望的數目要大很多,你就可以做heap dump,然后用任意的heap分析工具來分析問題。
線程Dump
很多時候,應用會呈現出“卡在那里”的情形。這里有很多種卡住的狀況:死鎖、cpu密集運算。為了定位到問題所在需要知道線程在做什么、持有了什么鎖等等。
Java中有兩種鎖:sychronized和Object.wait/notifyAll方法的原始鎖以及java5引入的java.util.concurrent鎖。這倆種鎖的不同之處主要在于前者是限制在你進入synchronies塊的地方的棧幀(stack frame)中,并且會一直在線程dump中存在。后者卻并不限制在棧幀中-你可以在一個方法中進入鎖,在另一方法中解鎖。因此,thread dump有時候并沒有包含這些信息。盡管如此,還是應該使用thread dump來查看線程信息排查問題。
這里有三種方法可以打印應用的thread dump。
kill -3 <PID> #僅限Linux平臺
jstack <PID>
jcmd <PID> Thread.print
運行Java飛行記錄器(Java Flight Recorder)
上面講到的工具都是作為快速的查看診斷工具的。如果要深入分析問題,可以選擇使用內置的Java飛行記錄器: Java Mission Control 。
運行JFR需要三步:
-
創建一個包含了你自己配置的JFR模板文件。運行 jmc , 然后 Window->Flight Recording Template Manage 菜單。準備好檔案后,就可以導出文件,并移動到要排查問題的環境中。
-
由于JFR需要JDK的商業證書,這一步需要解鎖jdk的商業特性。
jcmd <PID> VM.unlock_commercial_features
-
最后你就可以啟動JFR。
jcmd <PID> JFR.start name=test duration=60s settings=template.jfc filename=output.jfr
上述命令立即啟動JFR并開始使用 templayte.jfc 的配置收集60s的JVM信息,輸出到 output.jfr 中。
一旦記錄完成之后,就可以復制.jfr文件到你的工作環境使用jmc GUI來分析。它幾乎包含了排查jvm問題需要的所有信息,包括堆dump時的異常信息。
后記
本文基本上是對英文原文的翻譯,主要描述了幾個常見問題的排查場景。
不得不說的是,JDK自帶的工具是非常強大的。用好了這些工具其實已經足以應付絕大多數的Java問題排查場景。
來自:http://www.rowkey.me/blog/2016/11/16/java-trouble-shooting/