在我們的日常開發中,當程式出現bug的時候我們通常做的就是debug在控制檯看異常資訊,但是對於線上的問題以及一些更深層次的比如虛擬機器的問題,其實瞭解了jdk常用的命令列工具,更有利於我們找到程式發生的問題。
複製程式碼
常用命令
jps:虛擬機器程式狀態工具
簡介:jps(Java Virtual Machine Process Status Tool)是jdk自帶的命令工具,用來顯示java程式的一些情況。
複製程式碼
使用
jps
jps -q 只輸出程式的pid
jps -l 輸出類的全名,如果執行的是jar包,則執行jar包的路徑
下面我們給TestJpaCommandDemo的main函式String[] args在啟動時新增引數 test jps
public class TestJpaCommandDemo {
public static void main(String[] args) {
while ( true ) {
try {
Thread.sleep(1000);
System.out.println("jps");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
複製程式碼
執行程式(這裡我們只關注TestJpaCommandDemo的main方法),可以看到我們配置的引數
jps -m 輸出虛擬機器程式啟動時傳遞給main函式的方法
jps -v 輸出虛擬機器陳谷啟動時jvm的引數
jinfo:java配置資訊工具
簡介:jinfo的作用是實時的檢視和調整虛擬機器執行的引數。前面我們知道jps-v也可以檢視虛擬機器引數,但是那個引數是顯示的指定的,一些預設的引數我們是看不出來的。jinfo 系列的命令可以檢視,而且還可以在程式執行期間修改允許被修改的引數。
複製程式碼
使用
jinfo pid 檢視所有的引數 由於篇幅較長一些執行的jar包完整路徑就不擷取了
下面舉幾個例子
jinfo -flag MaxHeapSize pid
jinfo -flag MaxTenuringThreshold 22224( 新生代物件晉升到老年代物件的最大年齡)
jinfo -flag PrintGCDetails 22224(是否列印gc詳細資訊)
通過命令修改開啟列印GC資訊
可以看到變為開啟的狀態了jstat: 虛擬機器統計資訊監視工具
簡介:jstat(JVM Statistics Monitoring Tool)是用於監視虛擬機器各種執行狀態資訊的命令列工具。它可以顯示本地或者遠端[1]虛擬機器程式中的類裝載、記憶體、垃圾收集、JIT編譯等執行資料,在沒有GUI圖形介面,只提供了純文字控制檯環境的伺服器上,它將是執行期定位虛擬 機效能問題的首選工具。
複製程式碼
使用
引數解釋:- option 代表我們所要查詢的虛擬機器細資訊 主要包括:類裝載,垃圾回收,執行期編譯情況
- vmid 若是執行在本地的虛擬機器程式那就是程式執行的pid,若是遠端虛擬機器,格式應該是 [protocol:][//]lvmid[@hostname[:port]/servername]
- interval 查詢間隔
- count 查詢次數
忽略interval和count引數,那麼命令只會執行一次 比如 jstat -gc 3776 200 5 表示每200ms查詢一次3776程式的垃圾回收情況,一共執行5次
常見的option如圖:
jstat -class pid :監視類裝載,解除安裝數量,總空間以及類裝載多需要的時間。
loaded:已裝載的類的數量; Bytes:裝在類的位元組數; Unloaded:解除安裝類的數量; Bytes:解除安裝類的位元組數;Time:裝載以及解除安裝類所花費時間jstat -compiler pid :輸出JIT編譯器編譯過的方法,耗時等資訊。
compiled:編譯任務執行數量;failed:編譯失敗的數量;Invalid:變異任務執行失效數量;Time:編譯時間; FailedType:最後一個編譯失敗的型別;FailedMethod:最後一個編譯失敗的任務所在的類和方法jstat -printcompilation pid :輸出已經被JIT編譯器編譯過的方法。
Complied:編譯任務的數量;size:發放生成的位元組碼的大小;Type:編譯型別;Method:類名和方法名。jstat -gc pid :監視java堆情況,包括Eden區,兩個survivor區,老年代,永久代等容量,已用時間,gc合計時間等
S0C: 年輕代中第一個survivor(倖存區)的容量(位元組);
S1C:年輕代中第二個survivor(倖存區)的容量;
S0U:年輕代中第一個survivor(倖存區)已使用空間;
S1U:年輕代中第二個survivor(倖存區)已使用空間;
EC:年輕代中Eden(伊甸園)的容量 (位元組);
EU:年輕代中Eden(伊甸園)的已使用的空間 (位元組);
OC:老年代(old)的容量(位元組);
OU:老年代(old)已使用空間;
PC:Perm(持久代)的容量 (位元組);
PU:Perm(持久代)的已使用空間 (位元組);
YGC:從應用程式啟動到執行命令時年輕代中gc次數;
YGCT:從應用程式啟動到執行命令時年輕代中gc所佔用時間;
FGC:從應用程式啟動到執行命令時老年代gc次數;
FGCT:從應用程式啟動到執行命令時老年代gc佔用時間;
GCT:從應用程式啟動到執行命令時總gc佔用時間
複製程式碼
jstat -gccapacity pid :監視內容與 jstat -gc 基本相同,但輸出主要關注java堆各個區域用到的最大最小空間
NGCMN:年輕代(young)中初始化(最小)的大小(位元組);
NGCMX:年輕代(young)的最大容量 (位元組) ;
NGC:年輕代(young)中當前的容量 (位元組) ;
S0C:年輕代中第一個survivor(倖存區)的容量 (位元組) ;
S1C:年輕代中第二個survivor(倖存區)的容量 (位元組) ;
OGCMN:old代中初始化(最小)的大小 (位元組) ;
OGCMX:old代最大容量(位元組);
OGC:old代當前新生成的容量 (位元組) ;
OC:old代的容量(位元組);
PGCMN:perm代中初始化(最小)的大小 (位元組) ;
PGCMX:perm代的最大容量 (位元組)
PC:Perm(持久代)的容量 (位元組) ;
YGC:從應用程式啟動到執行命令時年輕代中gc次數;
FGC: 從應用程式啟動到執行命令時old代(全gc)gc次數
複製程式碼
jstat -gccause pid :與jstat -gcutil 一樣,但是會額外輸出上一次導致GC產生的原因。
S0:年輕代中第一個survivor(倖存區)已使用的佔當前容量百分比;
S1:年輕代中第二個survivor(倖存區)已使用的佔當前容量百分比;
E:年輕代中Eden(伊甸園)已使用的佔當前容量百分比;
O:old代已使用的佔當前容量百分比;
P:perm代已使用的佔當前容量百分比;
YGC:從應用程式啟動到執行命令時年輕代中gc次數;
YGCT:從應用程式啟動到執行命令時年輕代中gc所佔用時間;
FGC:從應用程式啟動到執行命令時老年代gc次數;
FGCT:從應用程式啟動到執行命令時老年代gc佔用時間;
GCT:從應用程式啟動到執行命令時總gc佔用時間
複製程式碼
jstat -gcnew pid :監視新生代GC的情況
S0C:年輕代中第一個survivor(倖存區)的容量 (位元組);
S1C:年輕代中第二個survivor(倖存區)的容量 (位元組);
S0U:年輕代中第一個survicor(倖存區)已用空間 (位元組);
S1U:年輕代中第二個survicor(倖存區)已用空間 (位元組);
TT:物件在新生代存活的次數;
MTT:物件在新生代存活的最大次數;
DSS:期望的倖存區大小;
EC:年輕代中Eden(伊甸園)的容量 (位元組);
EU:年輕代中Eden(伊甸園)的使用空間 (位元組);
YGC:年輕代垃圾回收次數;
YGCT:年輕代垃圾回收所用時間。
複製程式碼
jstat -gcnewcapacity pid :監視內容與jstat -gcnew基本相同,但是主要關注使用到的最大最小空間。
NGCMN:年輕代(young)中初始化(最小)的大小(位元組);
NGCMX:年輕代(young)的最大容量;
NGC:年輕代(young)中當前的容量 (位元組);
S0CMX:年輕代中第一個survivor(倖存區)的最大容量 (位元組);
S0C:年輕代中第一個survivor(倖存區)的容量 (位元組);
S1CMX:年輕代中第二個survivor(倖存區)的最大容量 (位元組);
S1C:年輕代中第二個survivor(倖存區)的容量 (位元組);
ECMX:年輕代中Eden(伊甸園)的最大容量 (位元組);
EC:年輕代中Eden(伊甸園)的容量 (位元組);
YGC:從應用程式啟動到執行命令時年輕代執行GC的次數;
YGCT:從應用程式啟動到執行命令時年輕代執行GC的時間;
複製程式碼
jstat -gcold pid :監視老年代GC情況。
PC:Perm(持久代)的容量 (位元組);
PU:Perm(持久代)目前已使用空間 (位元組) ;
OC:Old代的容量 (位元組);
OU:Old代目前已使用空間 (位元組);
YGC:從應用程式啟動到執行命令時年輕代中gc次數;
FGC:從應用程式啟動到執行命令時old代(全gc)gc次數;
FGCT:從應用程式啟動到執行命令時old代(全gc)gc所用時間(s);
GCT:從應用程式啟動到執行命令時gc用的總時間(s)。
複製程式碼
jstat -gcoldcapacity pid :與jstat -gcold 基本相同,但是主要關注的是使用到的最大最小空間。
OGCMN: old代中初始化(最小)的大小 (位元組);
OGCMX:old代的最大容量(位元組);
OGC:old代當前新生成的容量 (位元組) ;
OC:Old代的容量 (位元組) ;
YGC:從應用程式啟動到執行命令時年輕代中gc次數
FGC:從應用程式啟動到執行命令時old代(全gc)gc次數
FGCT:從應用程式啟動到執行命令時old代(全gc)gc所用時間(s)
GCT:從應用程式啟動到執行命令執行gc總時間。
複製程式碼
jstat -gcpermcapacity pid :輸出永久代用到的最大最小空間
PGCMN:perm代中初始化(最小)的大小
PGCMX:perm代的最大容量 (位元組)
PGC:perm代當前新生成的容量 (位元組)
PC:Perm(持久代)的容量 (位元組);
YGC:從應用程式啟動到執行命令時年輕代中gc次數
FGC:從應用程式啟動到執行命令時old代(全gc)gc次數
FGCT:從應用程式啟動到執行命令時old代(全gc)gc所用時間(s)
GCT:從應用程式啟動到命令執行所用的gc總時間。
複製程式碼
介紹下面的命令之前讓我們先了解幾個概念:
javaDump:Java虛擬機器的執行時快照,將Java虛擬機器執行時的狀態和資訊儲存到檔案。
執行緒Dump,包含所有執行緒的執行狀態。純文字格式。
堆Dump,主要包括系統資訊、虛擬機器屬性、完整的執行緒Dump、所有類和物件的狀態等。二進位制格式。
複製程式碼
jmap: java記憶體映像工具
簡介:jmap(Memory Map for Java)命令用於生成堆轉儲快照(一般稱為heapdump或dump文 件)。如果不使用jmap命令,要想獲取Java
堆轉儲快照,可以用-XX:+HeapDumpOnOutOfMemoryError引數,在虛擬機器在OOM異常出 現之後自動生成dump檔案,通過-XX:+HeapDumpOnCtrlBreak引數則可以使用[Ctrl]+[Break] 鍵讓虛擬機器生成dump檔案,又或者在Linux系統下通過Kill-3命令傳送程式退出訊號生成dump檔案。
jmap的作用並不僅僅是為了獲取dump檔案,它還可以查詢finalize執行佇列、Java堆和永 久代的詳細資訊,如空間使用率、當前用的是哪種收集器等。當虛擬機器出現記憶體不足或者GC異常時,此時就可以檢視heapdump來分析原因。常見的記憶體錯誤包括:
outOfMemoryError 年老代記憶體不足。
outOfMemoryError:PermGen Space 永久代記憶體不足。
outOfMemoryError:GC overhead limit exceed 垃圾回收時間佔用系統執行時間的98%或以上
複製程式碼
使用
其中引數意義如下:option:選項引數是互斥的(不可同時使用)。想要使用選項引數,直接跟在命令名稱後即可。
pid:需要列印配置資訊的程式ID。該程式必須是一個Java程式。想要獲取執行的Java程式列表,你可以使用jp。
executable :產生核心dump的Java可執行檔案。
core :需要列印配置資訊的核心檔案。
remote-hostname-or-IP :遠端除錯伺服器的(請檢視jsadebugd)主機名或IP地址。
server-id:可選的唯一id,如果相同的遠端主機上執行了多臺除錯伺服器,用此選項引數標識伺服器。
複製程式碼
jmap -dump pid :生成java堆快照 dump堆到檔案,format指定輸出格式,live指明是活著的物件,file指定檔名
然後用jhat命令可以參看 jhat -port 500 heapDump 在瀏覽器中訪問:http://localhost:5000/ 檢視詳細資訊 這個命令執行,JVM會將整個heap的資訊dump寫入到一個檔案,heap如果比較大的話,就會導致這個過程比較耗時,並且執行的過程中為了保證dump的資訊是可靠的,所以會暫停應用。jmap -heap pid :檢視java 堆(heap)使用情況
這裡直接參考Hollis的部落格結果 :【http://www.hollischuang.com/archives/303】
Attaching to process ID 31846, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 24.71-b01
using thread-local object allocation.
Parallel GC with 4 thread(s)//GC 方式
Heap Configuration: //堆記憶體初始化配置
MinHeapFreeRatio = 0 //對應jvm啟動引數-XX:MinHeapFreeRatio設定JVM堆最小空閒比率(default 40)
MaxHeapFreeRatio = 100 //對應jvm啟動引數 -XX:MaxHeapFreeRatio設定JVM堆最大空閒比率(default 70)
MaxHeapSize = 2082471936 (1986.0MB) //對應jvm啟動引數-XX:MaxHeapSize=設定JVM堆的最大大小
NewSize = 1310720 (1.25MB)//對應jvm啟動引數-XX:NewSize=設定JVM堆的‘新生代’的預設大小
MaxNewSize = 17592186044415 MB//對應jvm啟動引數-XX:MaxNewSize=設定JVM堆的‘新生代’的最大大小
OldSize = 5439488 (5.1875MB)//對應jvm啟動引數-XX:OldSize=<value>:設定JVM堆的‘老生代’的大小
NewRatio = 2 //對應jvm啟動引數-XX:NewRatio=:‘新生代’和‘老生代’的大小比率
SurvivorRatio = 8 //對應jvm啟動引數-XX:SurvivorRatio=設定年輕代中Eden區與Survivor區的大小比值
PermSize = 21757952 (20.75MB) //對應jvm啟動引數-XX:PermSize=<value>:設定JVM堆的‘永生代’的初始大小
MaxPermSize = 85983232 (82.0MB)//對應jvm啟動引數-XX:MaxPermSize=<value>:設定JVM堆的‘永生代’的最大大小
G1HeapRegionSize = 0 (0.0MB)
Heap Usage://堆記憶體使用情況
PS Young Generation
Eden Space://Eden區記憶體分佈
capacity = 33030144 (31.5MB)//Eden區總容量
used = 1524040 (1.4534378051757812MB) //Eden區已使用
free = 31506104 (30.04656219482422MB) //Eden區剩餘容量
4.614088270399305% used //Eden區使用比率
From Space: //其中一個Survivor區的記憶體分佈
capacity = 5242880 (5.0MB)
used = 0 (0.0MB)
free = 5242880 (5.0MB)
0.0% used
To Space: //另一個Survivor區的記憶體分佈
capacity = 5242880 (5.0MB)
used = 0 (0.0MB)
free = 5242880 (5.0MB)
0.0% used
PS Old Generation //當前的Old區記憶體分佈
capacity = 86507520 (82.5MB)
used = 0 (0.0MB)
free = 86507520 (82.5MB)
0.0% used
PS Perm Generation//當前的 “永生代” 記憶體分佈
capacity = 22020096 (21.0MB)
used = 2496528 (2.3808746337890625MB)
free = 19523568 (18.619125366210938MB)
11.337498256138392% used
670 interned Strings occupying 43720 bytes.
複製程式碼
jmap -histo pid :檢視堆記憶體中的數量及大小
jmap -histo:live pid :和jmap -histo pid查詢資訊相同,但是JVM會先觸發gc,然後再統計資訊。
由於數量太多我們只列印一部分。jhat:虛擬機器堆轉儲快照分析工具
簡介:jhat(JVM Heap Analysis Tool))命令與jmap搭配使用,來分析jmap生成的堆轉儲快照。jhat內建了一個微型的HTTP/HTML伺服器,生成dump檔案的分析結果後,可以在瀏覽器中檢視。
複製程式碼
在瀏覽器中輸入入http://localhost:5000/ 顯示如下圖
由於VisualVM可以更直觀的顯示堆dump的情況,這裡不再過多的研究。
複製程式碼
jstack:Java堆疊跟蹤工具
簡介:jstack(Stack Trace for Java)命令用於生成虛擬機器當前時刻的執行緒快照(一般稱為 threaddump或者javacore檔案)。執行緒快照就是當前執行緒執行的每一條方法的集合,
生成執行緒快照的主要目的是定位執行緒出現長時間停頓的原因,如執行緒間死鎖、死循 環、請求外部資源導致的長時間等待等都是導致執行緒長時間停頓的常見原因。
執行緒出現停頓 的時候通過jstack來檢視各個執行緒的呼叫堆疊,就可以知道沒有響應的執行緒到底在後臺做些 什麼事情,或者等待著什麼資源。
複製程式碼
使用
jstack[option]vmid
現在我們來複習一下執行緒先關的知識點:
執行緒狀態
- NEW,未啟動的。不會出現在Dump中。
- RUNNABLE,在虛擬機器正在執行的。
- BLOCKED,受阻塞並等待監視器鎖。
- WATING,無限期等待另一個執行緒執行特定操作。
- TIMED_WATING,有時限的等待另一個執行緒的特定操作。
- TERMINATED,已退出的。
monitor
Java中的每個物件都有一個監視器,來監測併發程式碼的重入。在非多執行緒編碼時該監視器不發揮作用,反之如果在synchronized 範圍內,監視器發揮作用。monitor可以理解為每一個物件的鎖。
複製程式碼
- 進入區(Entrt Set):表示執行緒通過synchronized要求獲取物件的鎖。如果物件未被鎖住,則迚入擁有者;否則則在進入區等待。一旦物件鎖被其他執行緒釋放,立即參與競爭。
- 擁有者(The Owner):表示某一執行緒成功競爭到物件鎖。
- 等待區(Wait Set):表示執行緒通過物件的wait方法,釋放物件的鎖,並在等待區等待被喚醒。
例如:在多執行緒環境下,synchronized塊中的方法獲取了lock例項的monitor,類似於程式碼:
public class Thread1 implements Runnable {
Object lock;
public void run() {
synchronized(lock){
..do something
}
}
}
複製程式碼
或者直接作用於方法的synchroized,相當於獲取Thread1的monitor,
public class Thread1 implements Runnable {
public synchronized void run() {
..do something
}
}
複製程式碼
呼叫修飾
表示執行緒在方法呼叫時,額外的重要的操作
複製程式碼
- locked <地址> 目標:使用synchronized申請物件鎖成功,監視器的擁有者。
-
waiting to lock <地址> 目標:使用synchronized申請物件鎖未成功,在進入區等待。
-
waiting on <地址> 目標:使用synchronized申請物件鎖成功後,釋放鎖幵在等待區等待。
- parking to wait for <地址> 目標:park是基本的執行緒阻塞原語,不通過監視器在物件上阻塞
執行緒動作
- runnable:狀態一般為RUNNABLE。
- in Object.wait():等待區等待,狀態為WAITING或TIMED_WAITING。
- waiting for monitor entry:進入區等待,狀態為BLOCKED。
- waiting on condition:等待區等待、被park。
- sleeping:休眠的執行緒,呼叫了Thread.sleep()。
模擬了資料庫表的死鎖,用jstack -l 27992檢視:
可以看到這裡發生了資料庫死鎖
下面我們通過程式死鎖的例子看一下命令如何使用:
public class TestJstackCommandDemo {
public static void main(String[] args) {
Thread t1 = new Thread(new DeadLockclass(true));//建立一個執行緒
Thread t2 = new Thread(new DeadLockclass(false));//建立另一個執行緒
t1.start();//啟動一個執行緒
t2.start();//啟動另一個執行緒
}
}
class DeadLockclass implements Runnable {
public boolean falg;// 控制執行緒
DeadLockclass(boolean falg) {
this.falg = falg;
}
public void run() {
/**
* 如果falg的值為true則呼叫t1執行緒
*/
if (falg) {
while (true) {
synchronized (Suo.o1) {
System.out.println("o1 " + Thread.currentThread().getName());
synchronized (Suo.o2) {
System.out.println("o2 " + Thread.currentThread().getName());
}
}
}
}
/**
* 如果falg的值為false則呼叫t2執行緒
*/
else {
while (true) {
synchronized (Suo.o2) {
System.out.println("o2 " + Thread.currentThread().getName());
synchronized (Suo.o1) {
System.out.println("o1 " + Thread.currentThread().getName());
}
}
}
}
}
}
class Suo {
static Object o1 = new Object();
static Object o2 = new Object();
}
複製程式碼
可以看到控制檯輸出:
此時通過jstack 29400檢視執行緒堆疊資訊: 可以很清楚的看出來,兩個執行緒被阻塞,並且發現了一個死鎖: 可以看出Thread1在執行第45行的時候,<0x00000007abe3ed98>被鎖住,於是它等在這個資源;Thread0在執行第32行時,也在等待資源<0x00000007abe3ed98>,但是<0x00000007abe3ed98>被鎖住,由此發生死鎖,就看的很清晰了。以上就是JDK常用的命令列工具,有的時候通過這些命令可以很清晰的找到問題,需要多練習熟悉,共勉。
本文參考: 《深入理解java虛擬機器》
[Java命令學習系列](http://www.hollischuang.com/archives/110)
可點選底部看其他命令
複製程式碼
[Java中的多執行緒你只要看這一篇就夠了](http://www.importnew.com/21089.html)