很多情況下,JVM 執行環境中並沒有趁手的工具,所以掌握基本的內建工具是一項基本功。
JDK 自帶的工具和程式可以分爲 2 大型別:
寫過 Java 程式的同學,對 JDK 中的開發工具應該比較熟悉。 下面 下麪列舉常用的部分:
工具 | 簡介 |
---|---|
java | Java 應用的啓動程式 |
javac | JDK 內建的編譯工具 |
javap | 反編譯 class 檔案的工具 |
javadoc | 根據 Java 程式碼和標準註釋,自動生成相關的 API 說明文件 |
javah | JNI 開發時,根據 Java 程式碼生成需要的 .h 檔案。 |
extcheck | 檢查某個 jar 檔案和執行時擴充套件 jar 有沒有版本衝突,很少使用 |
jdb | Java Debugger 可以偵錯本地和遠端程式,屬於 JPDA 中的一個 Demo 實現,供其他偵錯程式參考。開發時很少使用 |
jdeps | 探測 class 或 jar 包需要的依賴 |
jar | 打包工具,可以將檔案和目錄打包成爲 .jar 檔案;.jar 檔案本質上就是 zip 檔案,只是後綴不同。使用時按順序對應好選項和參數即可。 |
keytool | 安全證書和金鑰的管理工具(支援生成、匯入、導出等操作) |
jarsigner | jar 檔案簽名和驗證工具 |
policytool | 實際上這是一款圖形介面工具,管理本機的 Java 安全策略 |
開發工具此處不做詳細介紹,有興趣的同學請參考文末的鏈接。
下面 下麪介紹診斷和分析工具。
JDK 內建了各種命令列工具,條件受限時我們可以先用命令列工具快速檢視 JVM 範例的基本情況。
macOS X、Windows 系統的某些賬戶許可權不夠,有些工具可能會報錯/失敗,假如出問題了請排除這個因素。
我們知道,操作系統提供一個工具叫做 ps,用於顯示進程狀態(Process Status)。
Java也 提供了類似的命令列工具,叫做 JPS,用於展示 Java 進程資訊(列表)。
需要注意的是,JPS 展示的是當前使用者可看見的 Java 進程,如果看不見某些進程可能需要 sudo、su 之類的命令來切換許可權。
檢視幫助資訊:
$
jps -help
usage: jps [-help]
jps [-q] [-mlvV] [<hostid>]
Definitions:
<hostid>: <hostname>[:<port>]
複製
可以看到, 這些參數分爲了多個組,-help
、-q
、-mlvV
, 同一組可以共用一個 -
。
常用參數是小寫的 -v
,顯示傳遞給 JVM 的啓動參數。
$
jps -v
15883 Jps -Dapplication.home=/usr/local/jdk1.8.0_74 -Xms8m
6446 Jstatd -Dapplication.home=/usr/local/jdk1.8.0_74 -Xms8m
-Djava.security.policy=/etc/java/jstatd.all.policy
32383 Bootstrap -Xmx4096m -XX:+UseG1GC -verbose:gc
-XX:+PrintGCDateStamps -XX:+PrintGCDetails
-Xloggc:/xxx-tomcat/logs/gc.log
-Dcatalina.base=/xxx-tomcat -Dcatalina.home=/data/tomcat
複製
看看輸出的內容,其中最重要的資訊是前面的進程 ID(PID)。
其他參數不太常用:
-q
:只顯示進程號。-m
:顯示傳給 main 方法的參數資訊-l
:顯示啓動 class 的完整類名,或者啓動 jar 的完整路徑-V
:大寫的 V,這個參數有問題,相當於沒傳一樣。官方說的跟 -q
差不多。jstatd
伺服器支援。可以看到,格式爲 [:]
,不能用 IP,範例:jps -v sample.com:1099
。
知道 JVM 進程的 PID 之後,就可以使用其他工具來進行診斷了。
jstat 用來監控 JVM 內建的各種統計資訊,主要是記憶體和 GC 相關的資訊。
檢視 jstat 的幫助資訊,大致如下:
$
jstat -help
Usage: jstat -help|-options
jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]
Definitions:
<option> 可用的選項,檢視詳情請使用 -options
<vmid> 虛擬機器識別符號,格式:<lvmid>[@<hostname>[:<port>]]
<lines> 標題行間隔的頻率.
<interval> 採樣週期,<n>["ms"|"s"],預設單位是毫秒 "ms"
<count> 採用總次數
-J<flag> 傳給jstat底層JVM的 <flag> 參數
複製
再來看看 `` 部分支援哪些選項:
$
jstat -options
-class
-compiler
-gc
-gccapacity
-gccause
-gcmetacapacity
-gcnew
-gcnewcapacity
-gcold
-gcoldcapacity
-gcutil
-printcompilation
複製
簡單說明這些選項,不感興趣可以跳着讀。
-class
:類載入(Class loader)資訊統計。-compiler
:JIT 即時編譯器相關的統計資訊。-gc
:GC 相關的堆記憶體資訊,用法:jstat -gc -h 10 -t 864 1s 20
。-gccapacity
:各個記憶體池分代空間的容量。-gccause
:看上次 GC、本次 GC(如果正在 GC 中)的原因,其他輸出和 -gcutil
選項一致。-gcnew
:年輕代的統計資訊(New = Young = Eden + S0 + S1)。-gcnewcapacity
:年輕代空間大小統計。-gcold
:老年代和元數據區的行爲統計。-gcoldcapacity
:old 空間大小統計。-gcmetacapacity
:meta 區大小統計。-gcutil
:GC 相關區域的使用率(utilization)統計。-printcompilation
:列印 JVM 編譯統計資訊。範例:
jstat -gcutil -t 864
複製
-gcutil
選項是統計 GC 相關區域的使用率(utilization),結果如下:
Timestamp | S0 | S1 | E | O | M | CCS | YGC | YGCT | FGC | FGCT | GCT |
---|---|---|---|---|---|---|---|---|---|---|---|
14251645.5 | 0.00 | 13.50 | 55.05 | 71.91 | 83.84 | 69.52 | 113767 | 206.036 | 4 | 0.122 | 206.158 |
-t
選項的位置是固定的,不能在前也不能在後。可以看出是用於顯示時間戳,即 JVM 啓動到現在的秒數。
簡單分析一下:
可以看到,-gcutil
這個選項出來的資訊不太好用,統計的結果是百分比,不太直觀。
再看看 -gc
選項,GC 相關的堆記憶體資訊。
jstat -gc -t 864 1s
jstat -gc -t 864 1s 3
jstat -gc -t -h 10 864 1s 15
複製
其中的 1s
佔了 `` 這個槽位,表示每 1 秒輸出一次資訊。
1s 3
的意思是每秒輸出 1 次,最多 3 次。
如果只指定重新整理週期,不指定 `` 部分,則會一直持續輸出。 退出輸出按 CTRL+C
即可。
-h 10
的意思是每 10 行輸出一次表頭。
結果大致如下:
Timestamp | S0C | S1C | S0U | S1U | EC | EU | OC | OU | MC | MU | YGC | YGCT | FGC | FGCT |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
14254245.3 | 1152.0 | 1152.0 | 145.6 | 0.0 | 9600.0 | 2312.8 | 11848.0 | 8527.3 | 31616.0 | 26528.6 | 113788 | 206.082 | 4 | 0.122 |
14254246.3 | 1152.0 | 1152.0 | 145.6 | 0.0 | 9600.0 | 2313.1 | 11848.0 | 8527.3 | 31616.0 | 26528.6 | 113788 | 206.082 | 4 | 0.122 |
14254247.3 | 1152.0 | 1152.0 | 145.6 | 0.0 | 9600.0 | 2313.4 | 11848.0 | 8527.3 | 31616.0 | 26528.6 | 113788 | 206.082 | 4 | 0.122 |
上面的結果是精簡過的,爲了排版去掉了 GCT、CCSC、CCSU 這三列。看到這些單詞可以試着猜一下意思,詳細的解讀如下:
最重要的資訊是 GC 的次數和總消耗時間,其次是老年代的使用量。
在沒有其他監控工具的情況下, jstat 可以簡單檢視各個記憶體池和 GC 的資訊,可用於判別是否是 GC 問題或者記憶體溢位。
面試最常問的就是 jmap 工具了。jmap 主要用來 Dump 堆記憶體。當然也支援輸出統計資訊。
官方推薦使用 JDK 8 自帶的 jcmd 工具來取代 jmap,但是 jmap 深入人心,jcmd 可能暫時取代不了。
檢視 jmap 幫助資訊:
$
jmap -help
Usage:
jmap [option] <pid>
(連線到本地進程)
jmap [option] <executable <core>
(連線到 core file)
jmap [option] [server_id@]<remote-IP-hostname>
(連線到遠端 debug 服務)
where <option> is one of:
<none> 等同於 Solaris 的 pmap 命令
-heap 列印 Java 堆記憶體彙總資訊
-histo[:live] 列印 Java 堆記憶體物件的直方圖統計資訊
如果指定了 "live" 選項則只統計存活物件,強制觸發一次 GC
-clstats 列印 class loader 統計資訊
-finalizerinfo 列印等待 finalization 的物件資訊
-dump:<dump-options> 將堆記憶體 dump 爲 hprof 二進制格式
支援的 dump-options:
live 只 dump 存活物件,不指定則導出全部。
format=b 二進制格式(binary format)
file=<file> 導出檔案的路徑
範例:jmap -dump:live,format=b,file=heap.bin <pid>
-F 強制導出,若 jmap 被 hang 住不響應,可斷開後使用此選項。
其中 "live" 選項不支援強制導出。
-h | -help to print this help message
-J<flag> to pass <flag> directly to the runtime system
複製
常用選項就 3 個:
-heap
:列印堆記憶體(/記憶體池)的設定和使用資訊。-histo
:看哪些類佔用的空間最多,直方圖。-dump:format=b,file=xxxx.hprof
:Dump 堆記憶體。範例:看堆記憶體統計資訊。
$
jmap -heap 4524
輸出資訊:
Attaching to process ID 4524, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.65-b01
using thread-local object allocation.
Parallel GC with 4 thread(s)
Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 2069889024 (1974.0MB)
NewSize = 42991616 (41.0MB)
MaxNewSize = 689963008 (658.0MB)
OldSize = 87031808 (83.0MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
PS Young Generation
Eden Space:
capacity = 24117248 (23.0MB)
used = 11005760 (10.49591064453125MB)
free = 13111488 (12.50408935546875MB)
45.63439410665761% used
From Space:
capacity = 1048576 (1.0MB)
used = 65536 (0.0625MB)
free = 983040 (0.9375MB)
6.25% used
To Space:
capacity = 1048576 (1.0MB)
used = 0 (0.0MB)
free = 1048576 (1.0MB)
0.0% used
PS Old Generation
capacity = 87031808 (83.0MB)
used = 22912000 (21.8505859375MB)
free = 64119808 (61.1494140625MB)
26.32600715361446% used
12800 interned Strings occupying 1800664 bytes.
複製
可以看到堆記憶體和記憶體池的相關資訊。當然,這些資訊有多種方式可以得到,比如 JMX。
看看直方圖:
$
jmap -histo 4524
結果爲:
num #instances #bytes class name
----------------------------------------------
1: 52214 11236072 [C
2: 126872 5074880 java.util.TreeMap$Entry
3: 5102 5041568 [B
4: 17354 2310576 [I
5: 45258 1086192 java.lang.String
......
複製
簡單分析,其中 [C
佔用了 11MB 記憶體,沒佔用什麼空間。
[C
表示 chat[]
,[B
表示 byte[]
,[I
表示 int[]
,其他類似。這種基礎數據型別很難分析出什麼問題。
Java 中的大物件、巨無霸物件,一般都是長度很大的陣列。
Dump 堆記憶體:
cd $CATALINA_BASE
jmap -dump:format=b,file=3826.hprof 3826
複製
導出完成後,dump 檔案大約和堆記憶體一樣大。可以想辦法壓縮並傳輸。
分析 hprof 檔案可以使用 jhat 或者 mat 工具。
診斷工具:jcmd 是 JDK 8 推出的一款本地診斷工具,只支援連線本機上同一個使用者空間下的 JVM 進程。
檢視幫助:
$
jcmd -help
Usage: jcmd <pid | main class> <command ...|PerfCounter.print|-f file>
or: jcmd -l
or: jcmd -h
command 必須是指定 JVM 可用的有效 jcmd 命令。
可以使用 "help" 命令檢視該 JVM 支援哪些命令。
如果指定 pid 部分的值爲 0,則會將 commands 發送給所有可見的 Java 進程。
指定 main class 則用來匹配啓動類。可以部分匹配。(適用同一個類啓動多範例)。
If no options are given, lists Java processes (same as -p).
PerfCounter.print 命令可以展示該進程暴露的各種計數器
-f 從檔案讀取可執行命令
-l 列出(list)本機上可見的 JVM 進程
-h this help
複製
檢視進程資訊:
jcmd
jcmd -l
jps -lm
11155 org.jetbrains.idea.maven.server.RemoteMavenServer
複製
這幾個命令的結果差不多。可以看到其中有一個 PID 爲 11155 的進程,下面 下麪看看可以用這個 PID 做什麼。
給這個進程發一個 help 指令:
jcmd 11155 help
jcmd RemoteMavenServer help
複製
pid 和 main-class 輸出資訊是一樣的:
11155:
The following commands are available:
VM.native_memory
ManagementAgent.stop
ManagementAgent.start_local
ManagementAgent.start
GC.rotate_log
Thread.print
GC.class_stats
GC.class_histogram
GC.heap_dump
GC.run_finalization
GC.run
VM.uptime
VM.flags
VM.system_properties
VM.command_line
VM.version
help
複製
可以試試這些命令。檢視 VM 相關的資訊:
# JVM 範例執行時間
jcmd 11155 VM.uptime
9307.052 s
#JVM 版本號
jcmd 11155 VM.version
OpenJDK 64-Bit Server VM version 25.76-b162
JDK 8.0_76
# JVM 實際生效的設定參數
jcmd 11155 VM.flags
11155:
-XX:CICompilerCount=4 -XX:InitialHeapSize=268435456
-XX:MaxHeapSize=536870912 -XX:MaxNewSize=178782208
-XX:MinHeapDeltaBytes=524288 -XX:NewSize=89128960
-XX:OldSize=179306496 -XX:+UseCompressedClassPointers
-XX:+UseCompressedOops -XX:+UseParallelGC
# 檢視命令列參數
jcmd 11155 VM.command_line
VM Arguments:
jvm_args: -Xmx512m -Dfile.encoding=UTF-8
java_command: org.jetbrains.idea.maven.server.RemoteMavenServer
java_class_path (initial): ...(xxx省略)...
Launcher Type: SUN_STANDARD
# 系統屬性
jcmd 11155 VM.system_properties
...
java.runtime.name=OpenJDK Runtime Environment
java.vm.version=25.76-b162
java.vm.vendor=Oracle Corporation
user.country=CN
複製
GC 相關的命令,統計每個類的範例佔用位元組數。
$
jcmd 11155 GC.class_histogram
num #instances #bytes class name
----------------------------------------------
1: 11613 1420944 [C
2: 3224 356840 java.lang.Class
3: 797 300360 [B
4: 11555 277320 java.lang.String
5: 1551 193872 [I
6: 2252 149424 [Ljava.lang.Object;
複製
Dump 堆記憶體:
$
jcmd 11155 help GC.heap_dump
Syntax : GC.heap_dump [options] <filename>
Arguments: filename : Name of the dump file (STRING, no default value)
Options: -all=true 或者 -all=false (預設)
# 兩者效果差不多; jcmd 需要指定絕對路徑; jmap 不能指定絕對路徑
jcmd 11155 GC.heap_dump -all=true ~/11155-by-jcmd.hprof
jmap -dump:file=./11155-by-jmap.hprof 11155
複製
jcmd 坑的地方在於,必須指定絕對路徑,否則導出的 hprof 檔案就以 JVM 所在的目錄計算。(因爲是發命令交給 JVM 執行的)
其他命令用法類似,必要時請參考官方文件。
命令列工具、診斷工具:jstack 工具可以列印出 Java 執行緒的呼叫棧資訊(Stack Trace)。一般用來檢視存在哪些執行緒,診斷是否存在死鎖等。
這時候就看出來給執行緒(池)命名的必要性了(開發不規範,整個專案都是坑),具體可參考阿裡巴巴的 Java 開發規範。
看看幫助資訊:
$
jstack -help
Usage:
jstack [-l] <pid>
(to connect to running process)
jstack -F [-m] [-l] <pid>
(to connect to a hung process)
jstack [-m] [-l] <executable> <core>
(to connect to a core file)
jstack [-m] [-l] [server_id@]<remote server IP or hostname>
(to connect to a remote debug server)
Options:
-F to force a thread dump. Use when jstack <pid> does not respond (process is hung)
-m to print both java and native frames (mixed mode)
-l long listing. Prints additional information about locks
-h or -help to print this help message
複製
選項說明:
-F
:強制執行 Thread Dump,可在 Java 進程卡死(hung 住)時使用,此選項可能需要系統許可權。-m
:混合模式(mixed mode),將 Java 幀和 native 幀一起輸出,此選項可能需要系統許可權。-l
:長列表模式,將執行緒相關的 locks 資訊一起輸出,比如持有的鎖,等待的鎖。常用的選項是 -l
,範例用法。
jstack 4524
jstack -l 4524
複製
死鎖的原因一般是鎖定多個資源的順序出了問題(交叉依賴), 網上範例程式碼很多,比如搜尋「Java 死鎖 範例」。
在 Linux 和 macOS 上,jstack pid
的效果跟 kill -3 pid
相同。
診斷工具:jinfo 用來檢視具體生效的設定資訊以及系統屬性,還支援動態增加一部分參數。
看看幫助資訊:
$
jinfo -help
Usage:
jinfo [option] <pid>
(to connect to running process)
jinfo [option] <executable <core>
(to connect to a core file)
jinfo [option] [server_id@]<remote-IP-hostname>
(to connect to remote debug server)
where <option> is one of:
-flag <name> to print the value of the named VM flag
-flag [+|-]<name> to enable or disable the named VM flag
-flag <name>=<value> to set the named VM flag to the given value
-flags to print VM flags
-sysprops to print Java system properties
<no option> to print both of the above
-h | -help to print this help message
複製
使用範例:
jinfo 36663
jinfo -flags 36663
複製
不加參數過濾,則列印所有資訊。
jinfo 在 Windows 上比較穩定。在 macOS 上需要 root 許可權,或是需要在提示下輸入當前使用者的密碼。
然後就可以看到如下資訊:
jinfo 36663
Attaching to process ID 36663, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.131-b11
Java System Properties:
java.runtime.name = Java(TM) SE Runtime Environment
java.vm.version = 25.131-b11
sun.boot.library.path = /Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib
// 中間省略了幾十行
java.ext.dirs = /Users/kimmking/Library/Java/Extensions:/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/ext:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java
sun.boot.class.path = /Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/sunrsasign.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/classes
java.vendor = Oracle Corporation
maven.home = /Users/kimmking/tools/apache-maven-3.5.0
file.separator = /
java.vendor.url.bug = http://bugreport.sun.com/bugreport/
sun.io.unicode.encoding = UnicodeBig
sun.cpu.endian = little
sun.cpu.isalist =
VM Flags:
Non-default VM flags: -XX:CICompilerCount=3 -XX:InitialHeapSize=134217728 -XX:MaxHeapSize=2147483648 -XX:MaxNewSize=715653120 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=44564480 -XX:OldSize=89653248 -XX:+TraceClassLoading -XX:+TraceClassUnloading -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:+UseParallelGC
Command line: -Dclassworlds.conf=/Users/kimmking/tools/apache-maven-3.5.0/bin/m2.conf -Dmaven.home=/Users/kimmking/tools/apache-maven-3.5.0 -Dmaven.multiModuleProjectDirectory=/Users/kimmking/gateway/spring-cloud-gateway-demo/netty-server
複製
可以看到所有的系統屬性和啓動使用的 VM 參數、命令列參數。非常有利於我們排查問題,特別是去排查一個已經執行的 JVM 裡問題,通過 jinfo 我們就知道它依賴了哪些庫,用了哪些參數啓動。
如果在 Mac 和 Linux 系統上使用一直報錯,則可能是沒有許可權,或者 jinfo 版本和目標 JVM 版本不一致的原因,例如:
Error attaching to process:
sun.jvm.hotspot.runtime.VMVersionMismatchException:
Supported versions are 25.74-b02. Target VM is 25.66-b17
複製
jrunscript 和 jjs 工具用來執行指令碼,只要安裝了 JDK 8+,就可以像 shell 命令一樣執行相關的操作了。這兩個工具背後,都是 JDK 8 自帶的 JavaScript 引擎 Nashorn。
執行互動式操作:
$ jrunscript
nashorn> 66+88
154
複製
或者:
$ jjs
jjs> 66+88
154
複製
按 CTRL+C 或者輸入 exit() 回車,退出互動式命令列。
其中 jrunscript 可以直接用來執行 JS 程式碼塊或 JS 檔案。比如類似 curl 這樣的操作:
jrunscript -e "cat('http://www.baidu.com')"
複製
或者這樣:
jrunscript -e "print('hello,kk.jvm'+1)"
複製
甚至可以執行 JS 指令碼:
jrunscript -l js -f /XXX/XXX/test.js
複製
而 jjs 則只能互動模式,但是可以指定 JavaScript 支援的 ECMAScript 語言版本,比如 ES5 或者 ES6。
這個工具在某些情況下還是有用的,還可以在指令碼中執行 Java 程式碼,或者呼叫使用者自己的 jar 檔案或者 Java 類。詳細的操作說明可以參考:
如果是 JDK 9 及以上的版本,則有一個更完善的 REPL 工具——JShell,可以直接解釋執行 Java 程式碼。
而這些效能診斷工具官方並不提供技術支援,所以如果碰到報錯資訊,請不要着急,可以試試其他工具。不行就換 JDK 版本