1. JDK的指令行工具
1.1 jps:虛拟機程序狀況工具
jps(JVM Process Status Tool)可以列出正在運作的虛拟機程序,并顯示虛拟機執行主類名稱以及這些程序的本地虛拟機唯一ID。是使用頻率最高的JDK指令行工具,因為其他的JDK工具大多需要輸入它查詢到的LVMID來确定要監控的是哪一個虛拟機程序。對于本地虛拟機程序來說,LVMID與作業系統的程序ID是一直的,使用任務管理器也能查詢到虛拟機程序的LVMID,但是如果啟動了多個虛拟機程序,無法根據程序名稱定位時,就隻能依賴jps指令來顯示主類的功能才能區分了。 下面是我在linux上執行 jps -l 的例子,可以看到tomcat伺服器的資訊:
[[email protected] bin]# jps -l
7862 org.apache.catalina.startup.Bootstrap
1356 sun.tools.jps.Jps
jps的參數: -q 隻輸出LVMID,省略主類的名稱 -m 輸出虛拟機程序啟動時傳遞給主類main()函數的參數 -l 輸出主類的全名,如果進城執行的Jar包,則輸出Jar路徑 -v 輸出虛拟機程序啟動時JVM參數
1.2 jstat:虛拟機統計資訊監視工具
jstat(JVM Statistics Monitoring Tool) 是用于監視虛拟機各種運作狀态資訊的指令行工具,它可以顯示本地或者遠端虛拟機程序中的類裝載、記憶體、垃圾收集、JIT編譯等運作資料,在沒有GUI圖形界面,隻提供了純文字控制台環境的伺服器上,它将是運作期定位虛拟機性能問題的首選工具。 這裡使用jps查出的 7862 LVMID,看看伺服器的記憶體狀況:
[[email protected] bin]# jstat -gcutil 7862
S0 S1 E O P YGC YGCT FGC FGCT GCT
5.17 0.00 49.73 99.81 82.29 716 7.960 10 4.083 12.044
查詢的各結果意思是:S0,S1表示Survivor0、Survivor1,裡邊Survivor1使用了5.17%,Survivor2為空;E表示Eden區,使用了49.73%的空間;O表示OLD老年代,使用了99.81%的空間(。。。這裡說明程式有大問題了,可能是tomcat上運作了三個同樣的工程的原因,之後在虛拟機的學習中來進行調優),P表示永久代,使用了82.29%的空間。後邊的資料是時間機關的,YGC就是MinorGC,總共716次,耗時7.96秒;FGC當然是FullGC,共進行了10次,耗時4.083s;GCT是所有GC總耗時,前二者的時間和。
jstat的主要工具選項: -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編譯的方法
1.3 jinfo:Java配置資訊工具
jinfo(Configuration Info for Java)實時的檢視和調整虛拟機的各項參數,使用jps指令的-v參數可以檢視虛拟機啟動時顯式指定的參數清單,但如果想知道未被顯式指定的參數的系統預設值,除了找資料外,就隻能使用jinfo的-flag選項進行查詢了。
[[email protected] bin]# jinfo -flag CMSInitiatingOccupancyFraction 7862
-XX:CMSInitiatingOccupancyFraction=-1
1.4 jmap:Java記憶體映像工具
jmap(Memory Map for Java)指令用于生成堆轉儲快照(一般稱為headdump或dump檔案)。還可以查詢finalize執行隊列,Java堆和永久代的詳細資訊,如空間使用率、目前用的是哪種收集器等。 這裡我用jmap生成了tomcat伺服器的快照檔案:
[[email protected] home]# cd yuxi/
[[email protected] my]# jmap -dump:format=b,file=apache.bin 7862
Dumping heap to /home/my/apache.bin ...
Heap dump file created
jmap的選項: -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平台下有效
1.5 jhat:虛拟機堆轉儲快照分析工具
jhat(JVM Heap Analysis Tool)與jmap搭配使用,分析jmap生成的堆轉儲快照。jhat内置了一個微型的HTTP/HTML伺服器,生成dump檔案的分析結果後,可以在浏覽器中檢視。不過在實際中不使用jhat來分析dump,主要原因:1.一般不在部署應用的伺服器上直接分析dump檔案,而要複制到其他機器上進行分析,因為是耗時且消耗硬體資源。2.jhat功能比較簡陋,如VisualVM,Eclipse Momery Analyzer、IBM HeapAnalyzer等都更強大。
下面是剛剛用jmap導出的apache.bin的jhat過程
[[email protected] my]# jhat apache.bin
Reading from apache.bin...
Dump file created Wed Jun 11 19:40:33 CST 2014
Snapshot read, resolving...
Resolving 1422191 objects...
Chasing references, expect 284 dots............................................................................................................................................................................................................................................................................................
Eliminating duplicate references............................................................................................................................................................................................................................................................................................
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.
當螢幕顯示“Server is ready”時,可以在浏覽器中鍵入http://IP:7000來看到分析結果,如圖:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiQ3chVEa0V3bT9CX5RXa2Fmcn9CXwczLcVmds92czlGZvwVP9EUTDZ0aRJkSwk0LcxGbpZ2LcBDM08CXlpXazRnbvZ2LcRlMMVDT2EWNvwFdu9mZvwFNJpmT3dGWlZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39jN0UzMzQDNwEjMxYDM0EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
分組顯示以包為機關,可以點選連結看到每個類的資訊。
1.6 jstack:Java堆棧跟蹤工具
jstack(Stack Trace for Java)指令用于生成虛拟機目前時刻的線程快照(一般稱為threaddump或者javacore檔案)。線程快照就是目前虛拟機内每一條線程正在執行的方法堆棧的集合,生成線程快照的主要目的是定位線程出現長時間停頓的原因,如線程間死鎖、死循環、請求外部資源導緻的長時間等待等都是導緻線程長時間停頓的常見原因。線程出現停頓的時候通過jstack來檢視各個線程的調用堆棧,就可以知道沒有響應的線程到底在背景做些什麼事情,或者等待什麼資源。
[[email protected] yuxi]# jstack -l 7862
2014-06-12 11:04:05
Full thread dump Java HotSpot(TM) 64-Bit Server VM (20.45-b01 mixed mode):
"Attach Listener" daemon prio=10 tid=0x00007f6a84013800 nid=0xa8f waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"http-bio-8080-exec-345" daemon prio=10 tid=0x00007f6a980ee000 nid=0x5e31 waiting on condition [0x00007f6a715d4000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000e22bf280> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:156)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:1987)
at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:399)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:104)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:32)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:957)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:917)
at java.lang.Thread.run(Thread.java:662)
Locked ownable synchronizers:
- None
JDK1.5中,java.lang.Thread類新增了一個getAllStackTrace()方法用于擷取虛拟機中所有線程的StackTraceElement對象。使用這個方法可以通過簡單的幾行代碼就完成jstack的大部分功能,在實際項目可以調用這個方法做個管理者頁面,可以随時使用浏覽器檢視線程堆棧,如代碼所示:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<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>
1.7 HSDIS:JIT生成代碼反彙編
HSDIS是一個Sun官方推薦的HotSpot虛拟機JIT編譯代碼的反彙編插件,它包含在HotSpot虛拟機的源碼之中,但沒有提供編譯後的程式。它的作用是讓HotSpot的-XX:+PrintAssembly指令調用它來把動态生成的本地代碼還原為彙編代碼輸出,同時還生成大量非常有價值的注釋。 這裡,我使用的是Linux作業系統,在https://kenai.com/projects/base-hsdis/downloads網頁中選擇自己的插件,放入jre/bin/server目錄下,就是libjvm.so的同一目錄下,之後通過 -XX:+PrintAssembly指令來使用插件,如果提示這個指令找不到,那麼就要在所有參數之前加入一個 -XX:+UnlockDiagnosticVMOptions參數。 定義一個簡單的java檔案:
public class Bar {
int a = 1;
static int b = 2;
public int sum(int c){
return a+b+c;
}
public static void main(String[] args) {
new Bar().sum(3);
}
}
之後使用javac編譯,編譯後,執行時使用下邊指令:
[[email protected] my]# java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -Xcomp -XX:CompileCommand=dontinline,*Bar.sum -XX:CompileCommand=compileonly,*Bar.sum Bar
這個指令中,參數-Xcomp是讓虛拟機以編譯模式執行代碼,這樣代碼可以“偷懶”,不需要執行足夠次數來預熱就能出發JIT編譯。兩個-XX:CompileCommand的意思是讓編譯器不要内聯sum()并且隻編譯sum(),-XX:+PrintAssembly就是輸出反彙編内容。如果一切順利的話,那麼螢幕上會出現類似下邊的代碼: 我的程式輸出的内容如下:
Java HotSpot(TM) 64-Bit Server VM warning: PrintAssembly is enabled; turning on DebugNonSafepoints to gain additional output
CompilerOracle: dontinline *Bar.sum
CompilerOracle: compileonly *Bar.sum
Loaded disassembler from hsdis-amd64.so
Decoding compiled method 0x00007f89c905fdd0:
Code:
[Disassembling for mach='i386:x86-64']
[Entry Point]
[Constants]
# {method} 'sum' '(I)I' in 'Bar'
# this: rsi:rsi = 'Bar'
# parm0: rdx = int
# [sp+0x20] (sp of caller)
0x00007f89c905ff20: mov 0x8(%rsi),%r10d
0x00007f89c905ff24: cmp %r10,%rax
0x00007f89c905ff27: jne 0x00007f89c9037620 ; {runtime_call}
0x00007f89c905ff2d: xchg %ax,%ax
[Verified Entry Point]
0x00007f89c905ff30: push %rbp
0x00007f89c905ff31: sub $0x10,%rsp
0x00007f89c905ff35: nop ;*synchronization entry
; - Bar::[email protected] (line 7)
0x00007f89c905ff36: mov $0xfb0818c8,%r10 ; {oop('Bar')}
0x00007f89c905ff40: mov 0x260(%r10),%r10d
0x00007f89c905ff47: add 0xc(%rsi),%r10d
0x00007f89c905ff4b: mov %edx,%eax
0x00007f89c905ff4d: add %r10d,%eax ;*iadd
; - Bar::[email protected] (line 7)
0x00007f89c905ff50: add $0x10,%rsp
0x00007f89c905ff54: pop %rbp
0x00007f89c905ff55: test %eax,0xbe130a5(%rip) # 0x00007f89d4e73000
; {poll_return}
0x00007f89c905ff5b: retq
0x00007f89c905ff5c: hlt
0x00007f89c905ff5d: hlt
0x00007f89c905ff5e: hlt
0x00007f89c905ff5f: hlt
[Exception Handler]
[Stub Code]
0x00007f89c905ff60: jmpq 0x00007f89c905cfa0 ; {no_reloc}
[Deopt Handler Code]
0x00007f89c905ff65: callq 0x00007f89c905ff6a
0x00007f89c905ff6a: subq $0x5,(%rsp)
0x00007f89c905ff6f: jmpq 0x00007f89c90387c0 ; {runtime_call}
0x00007f89c905ff74: add %al,(%rax)
0x00007f89c905ff76: add %al,(%rax)
一些指令的意思: 1. push %rbp :儲存上一棧幀基址 2. sub $0x10,%rsp :給新幀配置設定空間 3. mov $0xfb0818,%r10 :取方法區的指針 4. mov 0x260(%r10),%r10d :取類變量b,這裡是通路方法區中的資料 5. add 0xc(%rsi),%r10d :這裡将執行個體變量a和上一句取得的b相加,放入r10d中 6. mov %edx,%eax :在上邊“parm0:rdx=int”,說明c在rdx中,這裡rdx應與edx一緻,将c放入eax中 7. add %r10d, %eax :将r10d中a與b的和加上eax中的c,結果放入eax中,計算a+b+c完成 8. add $0x10,%rsp :對應上文的rsp,這裡是撤銷棧幀 9. pop %rbp :對應上文的push,這裡是恢複上一棧幀 10. test %eax, 2xbe130a5(%rip) :輪詢方法傳回處的Safepoint 11. retq 方法傳回
2. JDK的可視化工具
2.1 Jconsole:Java監視與管理控制台
Jconsole(Java Monitoring and Management Console)是一種基于JMX的可視化監視、管理工具。
2.2 VisualVM:多合一故障處理工具
VisualVM(All-in-One Java Troubleshooting Tool)是到目前為止随JDK釋出的功能最強大的運作監視和故障處理工具。