1 概述
在之前的文章,我們了解了Java內(nèi)存布局百匆、內(nèi)存模型、對象模型和垃圾回收的知識呜投。在實際生產(chǎn)中加匈,絕大多數(shù)Java開發(fā)者都不會參與到JVM的開發(fā)中,那我們?yōu)槭裁匆斫釰VM呢仑荐?因為我們要排查雕拼、解決生產(chǎn)中出現(xiàn)的問題,在理解了理論知識的基礎上粘招,使用一些輔助的工具啥寇,才能更快、更準的定位問題,并且更好的解決問題辑甜。所以衰絮,接下來我們一起討論一下一些常見的性能監(jiān)控和分析工具。
2 JDK自帶的工具
JDK自帶了很多優(yōu)秀磷醋,使用簡單的工具猫牡,例如Jps,Jconsole子檀,Jstat镊掖,Jstack等乃戈,都能在JDK目錄下的bin目錄找到褂痰,windows下的是.exe可執(zhí)行文件,在unix系系統(tǒng)下的是.sh腳本文件症虑,可以直接用vim打開缩歪。
筆者使用的windows操作系統(tǒng),下面的相關工具的使用都是在windows下進行的谍憔,可能和unix系統(tǒng)有些許差異匪蝙。
2.1 Jps
Jps即Java Process Status Tool的簡稱,從名稱可以看到习贫,應該是類似進程管理器的東西逛球,實際上差不多,但是從功能上來看苫昌,還不足以稱做“管理器”颤绕,因為Jps沒有提供關閉,殺死進程等功能祟身,僅僅提供了查看Java虛擬機進程的功能奥务。
Jps的命令格式如下所示:
usage: jps [-help]
jps [-q] [-mlvV] [<hostid>]
Definitions:
<hostid>: <hostname>[:<port>]
試著執(zhí)行一下:
10320 sun.tools.jps.Jps
8756 org.jetbrains.jps.cmdline.Launcher
11288
2572 org/netbeans/Main
最前面的是虛擬機進程號,這個比較重要袜硫,因為后面的各種工具要連接的時候都需要知道虛擬機進程號氯葬,所以一般Jps除了查看當前有哪些進程之外,還有一個目的就是獲取進程號婉陷,通過-l參數(shù)列出主類帚称,可以知道該進程是由哪個主類(含有main的類)啟動的。
上圖是options的可選項秽澳,大家試試就知道了世杀,比較簡單,就不再贅述了肝集。
2.2 Jstat
Jstat是用于監(jiān)視虛擬機各種狀態(tài)信息的命令行工具瞻坝,包括類加載,GC,內(nèi)存等信息所刀,沒有提供GUI界面衙荐。其格式如下所示:
invalid argument count
Usage: jstat -help|-options
jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]
Definitions:
<option> An option reported by the -options option
<vmid> Virtual Machine Identifier. A vmid takes the following form:
<lvmid>[@<hostname>[:<port>]]
Where <lvmid> is the local vm identifier for the target
Java virtual machine, typically a process id; <hostname> is
the name of the host running the target Java virtual machine;
and <port> is the port number for the rmiregistry on the
target host. See the jvmstat documentation for a more complete
description of the Virtual Machine Identifier.
<lines> Number of samples between header lines.
<interval> Sampling interval. The following forms are allowed:
<n>["ms"|"s"]
Where <n> is an integer and the suffix specifies the units as
milliseconds("ms") or seconds("s"). The default units are "ms".
<count> Number of samples to take before terminating.
-J<flag> Pass <flag> directly to the runtime system.
在使用之前,我們得先用Jps獲取虛擬機進程ID浮创,我在本機上啟動了一個Java進程忧吟,用Jps獲取的進程ID是14220。接下來執(zhí)行jstat命令:
> jstat -gcutil 14220
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 0.00 0.00 1.04 71.42 74.38 2 0.046 1 0.002 0.048
其中S0和S1是Survivor區(qū)斩披,E是Eden溜族,O是Old,M是MetaSpace(Java8之后有的元空間垦沉,就是以前的方法區(qū))煌抒,CCS壓縮類空間,YGC是新生代GC的次數(shù)厕倍,YGCT是新生代GC消耗的時間寡壮,F(xiàn)GC是Full GC的次數(shù),F(xiàn)GCT是Full GC消耗的時間讹弯,GCT是GC消耗的總時間(FGCT + YGCT)况既。
其他主要的選項如下圖所示:
2.3 Jinfo
Jinfo的作用是實時的查看和修改虛擬機的各項參數(shù)。其格式如下所示:
Usage:
jinfo [option] <pid>
(to connect to running process)
jinfo [option] <executable <core>
(to connect to a core file)
jinfo [option] [server_id@]<remote server IP or 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
主要就是-flag參數(shù)组民,下面是一個使用示例:
> jinfo -flag CMSInitiantingOccupancyFraction 14220
-XX:CMSInitiatingOccupancyFraction=-1
2.4 Jmap
Jmap用于生成堆轉(zhuǎn)儲快照棒仍,即dump文件,除了獲取dump文件臭胜,Jmap還可以查看堆莫其,垃圾收集器等信息。其格式如下所示:
Usage:
jmap [option] <pid>
(to connect to running process)
jmap [option] <executable <core>
(to connect to a core file)
jmap [option] [server_id@]<remote server IP or hostname>
(to connect to remote debug server)
where <option> is one of:
<none> to print same info as Solaris pmap
-heap to print java heap summary
-histo[:live] to print histogram of java object heap; if the "live"
suboption is specified, only count live objects
-clstats to print class loader statistics
-finalizerinfo to print information on objects awaiting finalization
-dump:<dump-options> to dump java heap in hprof binary format
dump-options:
live dump only live objects; if not specified,
all objects in the heap are dumped.
format=b binary format
file=<file> dump heap to <file>
Example: jmap -dump:live,format=b,file=heap.bin <pid>
-F force. Use with -dump:<dump-options> <pid> or -histo
to force a heap dump or histogram when <pid> does not
respond. The "live" suboption is not supported
in this mode.
-h | -help to print this help message
-J<flag> to pass <flag> directly to the runtime system
執(zhí)行如下命令:
> jmap -heap 14220
Mark Sweep Compact GC
Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 104857600 (100.0MB)
NewSize = 34930688 (33.3125MB)
MaxNewSize = 34930688 (33.3125MB)
OldSize = 69926912 (66.6875MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
New Generation (Eden + 1 Survivor Space):
capacity = 31457280 (30.0MB)
used = 0 (0.0MB)
free = 31457280 (30.0MB)
0.0% used
Eden Space:
capacity = 27983872 (26.6875MB)
used = 0 (0.0MB)
free = 27983872 (26.6875MB)
0.0% used
From Space:
capacity = 3473408 (3.3125MB)
used = 0 (0.0MB)
free = 3473408 (3.3125MB)
0.0% used
To Space:
capacity = 3473408 (3.3125MB)
used = 0 (0.0MB)
free = 3473408 (3.3125MB)
0.0% used
tenured generation:
capacity = 69926912 (66.6875MB)
used = 725560 (0.6919479370117188MB)
free = 69201352 (65.99555206298828MB)
1.03759765625% used
1764 interned Strings occupying 157712 bytes.
第一行就顯示了使用哪種垃圾收集器庇楞,在我這里是Mark Sweep Compact榜配,即CMS。其他信息包含了堆的新生代吕晌,老年代的空間大小蛋褥,使用量等。下圖是其他的一些主要參數(shù):
2.5 Jhat
Jhat用來配合Jmap使用的睛驳,用于分析Jmap生成的堆快照烙心。命令格式如下所示:
Usage: jhat [-stack <bool>] [-refs <bool>] [-port <port>] [-baseline <file>] [-debug <int>] [-version] [-h|-help] <file>
-J<flag> Pass <flag> directly to the runtime system. For
example, -J-mx512m to use a maximum heap size of 512MB
-stack false: Turn off tracking object allocation call stack.
-refs false: Turn off tracking of references to objects
-port <port>: Set the port for the HTTP server. Defaults to 7000
-exclude <file>: Specify a file that lists data members that should
be excluded from the reachableFrom query.
-baseline <file>: Specify a baseline object dump. Objects in
both heap dumps with the same ID and same class will
be marked as not being "new".
-debug <int>: Set debug level.
0: No debug output
1: Debug hprof file parsing
2: Debug hprof file parsing, no server
-version Report version number
-h|-help Print this help and exit
<file> The file to read
For a dump file that contains multiple heap dumps,
you may specify which dump in the file
by appending "#<number>" to the file name, i.e. "foo.hprof#3".
All boolean options default to "true"
這個是提供GUI界面的,不過一般不會用在生產(chǎn)環(huán)境乏沸,一個原因分析dump文件時一件非常消耗資源的事淫茵,如果在生產(chǎn)環(huán)境上做了可能會導致一些資源方面的問題,另一個原因是Jhat分析的內(nèi)容比較簡陋蹬跃,有用信息比較少匙瘪。
在使用之前,我們得先使用Jmap生成一份dump文件,如下所示:
> jmap -dump:format=b,file=test.bin 14220
Dumping heap to C:\Users\72419\Desktop\test.bin ...
Heap dump file created
然后使用jhat讀入該文件丹喻,并分析:
> jhat test.bin
Reading from test.bin...
Dump file created Mon Sep 24 14:50:11 CST 2018
Snapshot read, resolving...
Resolving 10303 objects...
Chasing references, expect 2 dots..
Eliminating duplicate references..
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.
從日志可以看到薄货,Jhat啟動了一個HTTP服務,在7000端口監(jiān)聽碍论,啟動瀏覽器谅猾,輸入http://localhost:7000/ 即可進入GUI界面。如下所示:
點擊進去慢慢探索吧鳍悠。
2.6 Jstack
Jstack用于生成虛擬機當前時刻的線程快照税娜,生成該快照的目的是為了定位線程問題,例如死鎖藏研,活鎖敬矩,死循環(huán)或者請求外部資源導致的長時間等待等,通過分析快照的堆棧信息遥倦,就可以快速定位代碼中的哪個類谤绳,哪個方法占锯,甚至哪行出現(xiàn)了問題袒哥。下面是我寫的一個死鎖程序,啟動之后消略,我們通過Jstack來查看線程快照堡称,然后定位問題:
public class DeadLockTest {
static class DeadLock extends Thread {
private final String lockA;
private final String lockB;
public DeadLock(String lockA, String lockB) {
this.lockA = lockA;
this.lockB = lockB;
}
@Override
public void run() {
synchronized (lockA) {
System.out.println(Thread.currentThread().getName() + " get lock " + lockA);
try {
Thread.sleep(500); //停頓一下,讓另一個線程運行
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB) {
System.out.println(Thread.currentThread().getName() + " get lock " + lockB);
}
}
}
}
public static void main(String[] args) {
String lockA = "lockA";
String lockB = "lockB";
Thread thread1 = new DeadLock(lockA, lockB);
Thread thread2 = new DeadLock(lockB, lockA);
thread1.start();
thread2.start();
}
}
簡單解釋一下程序艺演,啟動兩個線程却紧,這兩個線程都要獲取A和B兩把鎖,但是獲取的順序不同胎撤。這樣就導致了線程1獲取了A鎖晓殊,等待B鎖,但是B鎖已經(jīng)被線程2獲取了伤提,并且線程2也在等待A鎖巫俺,最終導致他們都無法獲取到鎖,也就無法繼續(xù)執(zhí)行肿男,導致了死鎖介汹。
使用Jstack來生成快照嘗試分析:
> jstack -l 12432
......
"Thread-1" #12 prio=5 os_prio=0 tid=0x00000000195bd000 nid=0x5e90 waiting for monitor entry [0x000000001a20f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at top.yeonon.ch10.DeadLockTest$DeadLock.run(DeadLockTest.java:29)
- waiting to lock <0x00000000d5fc0588> (a java.lang.String)
- locked <0x00000000d5fc05c0> (a java.lang.String)
Locked ownable synchronizers:
- None
"Thread-0" #11 prio=5 os_prio=0 tid=0x00000000195ba000 nid=0x5ed4 waiting for monitor entry [0x000000001a10f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at top.yeonon.ch10.DeadLockTest$DeadLock.run(DeadLockTest.java:29)
- waiting to lock <0x00000000d5fc05c0> (a java.lang.String)
- locked <0x00000000d5fc0588> (a java.lang.String)
Locked ownable synchronizers:
- None
......
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x000000000357ccf8 (object 0x00000000d5fc0588, a java.lang.String),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x000000000357b858 (object 0x00000000d5fc05c0, a java.lang.String),
which is held by "Thread-1"
Java stack information for the threads listed above:
===================================================
"Thread-1":
at top.yeonon.ch10.DeadLockTest$DeadLock.run(DeadLockTest.java:29)
- waiting to lock <0x00000000d5fc0588> (a java.lang.String)
- locked <0x00000000d5fc05c0> (a java.lang.String)
"Thread-0":
at top.yeonon.ch10.DeadLockTest$DeadLock.run(DeadLockTest.java:29)
- waiting to lock <0x00000000d5fc05c0> (a java.lang.String)
- locked <0x00000000d5fc0588> (a java.lang.String)
Found 1 deadlock.
信息比較多,而且我已經(jīng)省略了一些舶沛,主要看Found one Java-level deadlock后面的內(nèi)容吧嘹承,這說明Jstack發(fā)現(xiàn)了一個死鎖,并且將死鎖相關的線程堆棧信息打印出來了如庭,從上面可以看到“Thread-1”線程拿到了<0x00000000d5fc05c0>鎖叹卷,正在等待 <0x00000000d5fc0588>鎖,“Thread-0”拿著<0x00000000d5fc0588>鎖,正在等待 <0x00000000d5fc05c0>骤竹,顯然就是死鎖了餐胀,同時看堆棧可以看到問題發(fā)生在DeadLockTest.java:29瘤载,即該類的29行出現(xiàn)問題(雖然這個值不一定完全對應源碼位置否灾,但基本相差不大,也算是有用的信息)鸣奔,接下來就是到源碼相應的位置去分析代碼了墨技。
Jstack的其他主要參數(shù)選項如下所示:
2.7 Jconsole
Jconsole是一個可視化工具,命令行輸入jconsole挎狸,打開之后就看到GUI界面了扣汪,界面大致如下所示:
每個標簽的意義也比較明顯,可以試著自己玩玩锨匆,比較簡單崭别,不再贅述。
我之前有一篇文章中簡單提到過一些Jconsole的使用恐锣,可以去看看茅主。理解Java內(nèi)存泄露
3 第三方工具
3.1 VisualVM
VisualVM是一款非常強大的性能監(jiān)控和分析工具,集合了多項功能土榴,包括導出dump文件诀姚,分析dump文件,監(jiān)控線程狀態(tài)玷禽,監(jiān)控虛擬機各項狀態(tài)指標等赫段,還提供了諸多豐富的插件給用戶使用。
使用之前得先去官網(wǎng)下載矢赁,下載安裝糯笙、簡單使用就不多說了,網(wǎng)上有很多文章介紹撩银,本文只討論一個插件:BTrace给涕。
Btrace是一個很有趣的插件,他可以在運行時插入一些調(diào)試代碼蜒蕾,利用這個插件我們就可以實現(xiàn)不停止程序就能進行簡單調(diào)試的功能稠炬。先寫一些代碼用來測試:
public class BTraceTest {
public int add(int a, int b) {
return a + b;
}
public static void main(String[] args) {
BTraceTest bTraceTest = new BTraceTest();
Scanner scanner = new Scanner(System.in);
for (int i = 0; i < 10; i++) {
scanner.nextLine();
int a = (int) Math.round(Math.random() * 1000);
int b = (int) Math.round(Math.random() * 1000);
System.out.println(bTraceTest.add(a, b));
}
}
}
就是加分運算而已,使用Scanner是為了慢慢的看日志變化咪啡,感受BTrace的功能首启。啟動程序之后,到VisualVM的側(cè)邊欄看看Local選項撤摸,點擊后選中剛剛運行的程序毅桃,如下所示:
右鍵點擊褒纲,選中Trace application,之后可以在右邊看到一個編譯器钥飞,可以在里面編寫Java代碼莺掠,這些代碼就是用于跟蹤調(diào)試源代碼的,如下圖所示:
加入如下代碼:
/* BTrace Script Template */
import com.sun.btrace.annotations.*;
import static com.sun.btrace.BTraceUtils.*;
@BTrace
public class TracingScript {
/* put your code here */
@OnMethod(clazz="top.yeonon.ch10.BTraceTest", method="add", location=@Location(Kind.RETURN))
public static void func(@Self top.yeonon.ch10.BTraceTest instance, int a, int b, @Return int result) {
println("調(diào)用堆棧:");
jstack();
println(strcat("方法參數(shù)A:",str(a)));
println(strcat("方法參數(shù)B:",str(b)));
println(strcat("返回值result:",str(result)));
}
}
然后點擊上方的start按鈕读宙,啟動BTrace彻秆,等看到下方控制臺中出現(xiàn)Done字樣的時候,我們就到IDE里隨便輸入字符(多輸入幾次)结闸,然后再回到VisualVM控制臺中看看結(jié)果唇兑,大致內(nèi)容如下:
這就完成了一次代碼動態(tài)調(diào)試,是不是感覺很屌桦锄。BTrace還有很多其他功能扎附,詳細可以到網(wǎng)上去搜索。
4 小結(jié)
對虛擬機進行監(jiān)控结耀,調(diào)優(yōu)以及出現(xiàn)問題的時候定位問題留夜,解決問題是我們學習Java虛擬機的目的,在掌握了虛擬機相關的知識理論之后再利用輔助工具图甜,才能更好的發(fā)現(xiàn)問題碍粥,解決問題。本文介紹了諸多工具具则,每種工具都有優(yōu)點和不足即纲,了解這些工具的特性具帮,多多嘗試使用博肋,才能更好的掌握它們。
5 參考資料
《深入理解Java虛擬機》