虛擬機性能監控與故障處理工具-JDK的命令行工具-jstack:Java堆棧跟蹤工具
一、理論知識
(一)jstack是什麼?
jstack(Stack Trace for Java),用於生成Java虛擬機當前時刻的線程快照(該線程快照一般也稱爲:threaddump或者javacore文件)。
jstack是Java虛擬機自帶的一種堆棧跟蹤工具,用於打印出給定的java進程ID或core file或遠程調試服務的Java堆棧信息。
(二)jstack有什麼用處?
線程快照是當前Java虛擬機內每一條線程正在執行的方法堆棧的集合,生成線程快照的主要目的是定位線程出現長時間停頓的原因,如線程間死鎖、死循環、請求外部資源導致的長時間等待
等都是導致線程長時間停頓的常見原因。
線程出現停頓的時候通過jstack來查看各個線程的調用堆棧,就可以知道沒有響應的線程到底在後臺做什麼事情,或者等待什麼資源。
如果Java程序崩潰生成core文件,jstack工具可以用來獲得core文件的java stack和native stack的信息,從而知道java程序是如何崩潰和在程序何處發生問題。
另外,jstack工具還可以附屬到正在運行的Java程序中,看到當時運行的java程序的java stack和native stack的信息,。如果現在運行的Java程序呈現hung的狀態,jstack是非常有用的。
jstack主要分爲兩個功能:(1)針對活着的進程做本地的或遠程的線程dump;(2) 針對core文件做線程dump。
一般情況下,通過jstack輸出的線程信息主要包括:jvm自身線程、用戶線程等。其中jvm線程會在jvm啓動時就會存在。對於用戶線程則是在用戶訪問時纔會生成。
(三)jstack怎麼用?
1、jstack命令格式
jstack [ option ] pid
# 重定向將輸出保存到文件。
jstack -l [Java進程PID] > jstack202005021345.txt
2、參數列表:
選項 | 作用 |
---|---|
[ option ] | 詳細見下面的option選項 |
pid | 需要被打印配置信息的java進程id,可以用jps查詢得到。 |
3、option選項列表:
選項 | 作用 |
---|---|
-F | 當’jstack [-l] pid’沒有相應的時候強制打印棧信息,如果直接jstack無響應時,用於強制jstack),一般情況不需要使用。 |
-l | 長列表。打印關於鎖的附加信息,例如屬於java.util.concurrent的ownable synchronizers列表,會使得JVM停頓得長久得多(可能會差很多倍,比如普通的jstack可能幾毫秒和一次GC沒區別,加了-l 就是近一秒的時間),-l 建議不要用。一般情況不需要使用。 |
-m | 打印java和native c/c++框架的所有棧信息.可以打印JVM的堆棧,顯示上Native的棧幀,一般應用排查不需要使用。 |
-h(或-help) | 打印幫助信息。 |
詳細見下面“實踐”章節和“案例實戰”章節。
(四)線程dump的分析工具:
- IBM Thread and Monitor Dump Analyze for Java 一個小巧的Jar包,能方便的按狀態,線程名稱,線程停留的函數排序,快速瀏覽。
- http://spotify.github.io/threaddump-analyzer Spotify提供的Web版在線分析工具,可以將鎖或條件相關聯的線程聚合到一起。
二、實踐
(一)死鎖問題
1、問題描述:
程序響應很慢,一直卡住。很可能是程序出現了死鎖。
2、原因分析:
造成死鎖的原因很可能是多線程的程序代碼對synchronize使用不當,同時鎖住了對方需要的資源。
3、排查步驟:
(1)使用jps
查看線程ID
(2)使用jstack pid
:查看進程情況。或者輸出到文件中,命令參考如下:
# 重定向將輸出保存到文件。
jstack -l [Java進程PID] > jstack202005021345.txt
(3)分析線程堆棧
詳見後面的第4步驟。
(二)CPU Load過高
1、問題描述:
CPU Load過高,如何排查原因?
2、原因分析:
造成CPU Load過高的原因,一般是以下幾種:Full GC次數增大、代碼存在Bug(例如死循環、正則的不巧當使用)等
3、排查步驟:
(1)top
查找出哪個進程消耗的cpu高。執行top命令,默認是進程視圖,其中PID是進程號
如果確定是java進程,執行命令jps -v
,查看Java進程PID
(2)top -Hp [Java進程PID]
查看當前進程下最消耗CPU的線程,獲得線程ID
(3)printf “%x\n” [步驟(2)中獲得的線程ID]
得到線程ID的16進製表示
(4)jstack [Java進程PID] | grep -A100 [步驟(3)中獲得的線程ID]
查看線程堆棧,定位代碼行。
在工作中,生產環境只有運維有權限操作,所以常用的做法是讓運維同事直接導入該進程的所有線程堆棧,命令爲:
jstack -l [Java進程PID] > jstack202005021345.txt
(5)分析線程堆棧
詳見後面的第4步驟。
4、分析線程堆棧
(1)準備知識:線程狀態
想要通過jstack命令來分析線程的情況的話,首先要知道線程都有哪些狀態,下面這些狀態是我們使用jstack命令查看線程堆棧信息時可能會看到的線程的幾種狀態:
線程狀態 | 說明 |
---|---|
NEW | 未啓動的。不會出現在Dump中。 |
RUNNABLE | 在虛擬機內執行的。運行中狀態,可能裏面還能看到locked字樣,表明它獲得了某把鎖。 |
BLOCKED | 受阻塞並等待監視器鎖。被某個鎖(synchronizers)給block住了。 |
WATING | 無限期等待另一個線程執行特定操作。等待某個condition或monitor發生,一般停留在park(), wait(), sleep(),join() 等語句裏。 |
TIMED_WATING | 有時限的等待另一個線程的特定操作。和WAITING的區別是wait() 等語句加上了時間限制 wait(timeout)。 |
TERMINATED | 已退出的。 |
(2)準備知識:Monitor
在多線程的 JAVA程序中,實現線程之間的同步,就要說說 Monitor。 Monitor是 Java中用以實現線程之間的互斥與協作的主要手段,它可以看成是對象或者 Class的鎖。每一個對象都有,也僅有一個 monitor。下 面這個圖,描述了線程和 Monitor之間關係,以及線程的狀態轉換圖:
進入區(Entrt Set) | 表示線程通過synchronized要求獲取對象的鎖。如果對象未被鎖住,則迚入擁有者;否則則在進入區等待。一旦對象鎖被其他線程釋放,立即參與競爭。 |
擁有者(The Owner) | 表示某一線程成功競爭到對象鎖。 |
等待區(Wait Set) | 表示線程通過對象的wait方法,釋放對象的鎖,並在等待區等待被喚醒。 |
從圖中可以看出,一個 Monitor在某個時刻,只能被一個線程擁有,該線程就是 “Active Thread”,而其它線程都是 “Waiting Thread”,分別在兩個隊列 “ Entry Set”和 “Wait Set”裏面等候。在 “Entry Set”中等待的線程狀態是 “Waiting for monitor entry”,而在“Wait Set”中等待的線程狀態是 “in Object.wait()”。 先看 “Entry Set”裏面的線程。我們稱被 synchronized保護起來的代碼段爲臨界區。當一個線程申請進入臨界區時,它就進入了 “Entry Set”隊列。對應的 code就像:
synchronized(obj) {
.........
}
(3)開始分析:調用修飾
表示線程在方法調用時,額外的重要的操作。線程Dump分析的重要信息。修飾上方的方法調用。
locked <地址> 目標 | 使用synchronized申請對象鎖成功,監視器的擁有者。 |
waiting to lock <地址> 目標 | 使用synchronized申請對象鎖未成功,在迚入區等待。 |
waiting on <地址> 目標 | 使用synchronized申請對象鎖成功後,釋放鎖幵在等待區等待。 |
parking to wait for <地址> 目標 | 需與堆棧中的”parking to wait for (atjava.util.concurrent.SynchronousQueue$TransferStack)”結合來看。first–>此線程是在等待某個條件的發生,來把自己喚醒,second–>SynchronousQueue不是一個隊列,其是線程之間移交信息的機制,當我們把一個元素放入到 SynchronousQueue 中時必須有另一個線程正在等待接受移交的任務,因此這就是本線程在等待的條件。 |
locked
at oracle.jdbc.driver.PhysicalConnection.prepareStatement
- locked <0x00002aab63bf7f58> (a oracle.jdbc.driver.T4CConnection)
at oracle.jdbc.driver.PhysicalConnection.prepareStatement
- locked <0x00002aab63bf7f58> (a oracle.jdbc.driver.T4CConnection)
at com.jiuqi.dna.core.internal.db.datasource.PooledConnection.prepareStatement
通過synchronized關鍵字,成功獲取到了對象的鎖,成爲監視器的擁有者,在臨界區內操作。對象鎖是可以線程重入的。
waiting to lock
at com.jiuqi.dna.core.impl.CacheHolder.isVisibleIn(CacheHolder.java:165)
- waiting to lock <0x0000000097ba9aa8> (a CacheHolder)
at com.jiuqi.dna.core.impl.CacheGroup$Index.findHolder
at com.jiuqi.dna.core.impl.ContextImpl.find
at com.jiuqi.dna.bap.basedata.common.util.BaseDataCenter.findInfo
通過synchronized關鍵字,沒有獲取到了對象的鎖,線程在監視器的進入區等待。在調用棧頂出現,線程狀態爲Blocked。
waiting on
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000da2defb0> (a WorkingThread)
at com.jiuqi.dna.core.impl.WorkingManager.getWorkToDo
- locked <0x00000000da2defb0> (a WorkingThread)
at com.jiuqi.dna.core.impl.WorkingThread.run
通過synchronized關鍵字,成功獲取到了對象的鎖後,調用了wait方法,進入對象的等待區等待。在調用棧頂出現,線程狀態爲WAITING或TIMED_WATING。
parking to wait for
park是基本的線程阻塞原語,不通過監視器在對象上阻塞。隨concurrent包會出現的新的機制,不synchronized體系不同。
(4)開始分析:線程狀態產生的原因
線程狀態 | 產生的原因 |
---|---|
runnable | 狀態一般爲RUNNABLE。 |
in Object.wait() | 等待區等待,狀態爲WAITING或TIMED_WAITING。 |
waiting for monitor entry | 進入區等待,狀態爲BLOCKED。 |
waiting on condition | 等待區等待、被park。 |
sleeping | sleeping |
Wait on condition 該狀態出現在線程等待某個條件的發生。具體是什麼原因,可以結合 stacktrace來分析。 最常見的情況就是線程處於sleep狀態,等待被喚醒。 常見的情況還有等待網絡IO:在java引入nio之前,對於每個網絡連接,都有一個對應的線程來處理網絡的讀寫操作,即使沒有可讀寫的數據,線程仍然阻塞在讀寫操作上,這樣有可能造成資源浪費,而且給操作系統的線程調度也帶來壓力。在 NewIO裏採用了新的機制,編寫的服務器程序的性能和可擴展性都得到提高。 正等待網絡讀寫,這可能是一個網絡瓶頸的徵兆。因爲網絡阻塞導致線程無法執行。一種情況是網絡非常忙,幾 乎消耗了所有的帶寬,仍然有大量數據等待網絡讀 寫;另一種情況也可能是網絡空閒,但由於路由等問題,導致包無法正常的到達。所以要結合系統的一些性能觀察工具來綜合分析,比如 netstat統計單位時間的發送包的數目,如果很明顯超過了所在網絡帶寬的限制 ; 觀察 cpu的利用率,如果系統態的 CPU時間,相對於用戶態的 CPU時間比例較高;如果程序運行在 Solaris 10平臺上,可以用 dtrace工具看系統調用的情況,如果觀察到 read/write的系統調用的次數或者運行時間遙遙領先;這些都指向由於網絡帶寬所限導致的網絡瓶頸。
runnable
in Object.wait()
waiting for monitor entry
待補充。
waiting on condition
sleeping
待補充。
三、案例實戰
(一)案例1:空的無限循環
1、準備:JStackDemo1.java
package jstack;
public class JStackDemo1 {
public static void main(String[] args) {
while (true) {
//Do Nothing
}
}
}
2、第一步:jps查看進程號
D:\1未備份\davecode\study\架構師領域\java\jvm>jps
13584
8980 JStackDemo1
11336 RemoteMavenServer
17880 Jps
可以看到,JStackDemo1程序的進程號爲8980
3、第二步:jstack 查看堆棧信息
D:\1未備份\davecode\study\架構師領域\java\jvm>jstack 8980
2020-05-05 16:32:12
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.191-b12 mixed mode):
"Service Thread" #11 daemon prio=9 os_prio=0 tid=0x0000000019986800 nid=0xa48 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C1 CompilerThread3" #10 daemon prio=9 os_prio=2 tid=0x00000000198e8000 nid=0x1d8 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread2" #9 daemon prio=9 os_prio=2 tid=0x00000000198e5000 nid=0x1f44 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread1" #8 daemon prio=9 os_prio=2 tid=0x00000000198e3800 nid=0x368c waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x00000000198df000 nid=0x27ec waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x00000000198a9800 nid=0x1c40 runnable [0x000000001ae7e000]
java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
at java.net.SocketInputStream.read(SocketInputStream.java:171)
at java.net.SocketInputStream.read(SocketInputStream.java:141)
at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
- locked <0x00000000d608d230> (a java.io.InputStreamReader)
at java.io.InputStreamReader.read(InputStreamReader.java:184)
at java.io.BufferedReader.fill(BufferedReader.java:161)
at java.io.BufferedReader.readLine(BufferedReader.java:324)
- locked <0x00000000d608d230> (a java.io.InputStreamReader)
at java.io.BufferedReader.readLine(BufferedReader.java:389)
at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:64)
"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x000000001983e000 nid=0xbbc waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x000000001815e800 nid=0x2ed0 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x0000000018153000 nid=0x4604 in Object.wait() [0x000000001ab7f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000d5f08ed0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
- locked <0x00000000d5f08ed0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)
"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x0000000019823000 nid=0x2b08 in Object.wait() [0x000000001aa7e000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000d5f06bf8> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x00000000d5f06bf8> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
"main" #1 prio=5 os_prio=0 tid=0x0000000002ed3800 nid=0x11ec runnable [0x0000000002d0f000]
java.lang.Thread.State: RUNNABLE
at deadlock.JStackDemo1.main(JStackDemo1.java:6)
"VM Thread" os_prio=2 tid=0x0000000018146800 nid=0x38e8 runnable
"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x0000000002ee9000 nid=0x2378 runnable
"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x0000000002eea800 nid=0x1b00 runnable
"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x0000000002eec000 nid=0x2bd0 runnable
"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x0000000002eee800 nid=0x3570 runnable
"GC task thread#4 (ParallelGC)" os_prio=0 tid=0x0000000002ef1000 nid=0x1100 runnable
"GC task thread#5 (ParallelGC)" os_prio=0 tid=0x0000000002ef2000 nid=0x17dc runnable
"GC task thread#6 (ParallelGC)" os_prio=0 tid=0x0000000002ef5000 nid=0x4ac runnable
"GC task thread#7 (ParallelGC)" os_prio=0 tid=0x0000000002ef6800 nid=0x3f8 runnable
"GC task thread#8 (ParallelGC)" os_prio=0 tid=0x0000000002ef7800 nid=0x3988 runnable
"GC task thread#9 (ParallelGC)" os_prio=0 tid=0x0000000002ef8800 nid=0x120 runnable
"VM Periodic Task Thread" os_prio=2 tid=0x00000000199d1800 nid=0x3a94 waiting on condition
JNI global references: 12
從這段堆棧信息中可以看到,當前一共有一條用戶級別線程,線程處於runnable狀態,執行到JStackDemo1.java的第6行。
"main" #1 prio=5 os_prio=0 tid=0x0000000002ed3800 nid=0x11ec runnable [0x0000000002d0f000]
java.lang.Thread.State: RUNNABLE
at deadlock.JStackDemo1.main(JStackDemo1.java:6)
(二)案例2:線程無限循環打印
1、準備:Thread1.java和JStackDemo2.java
Thread1.java
package jstack;
public class Thread1 implements Runnable{
public void run() {
while(true){
System.out.println(1);
}
}
}
JStackDemo2.java
package jstack;
public class JStackDemo2 {
public static void main(String[] args) {
Thread thread = new Thread(new Thread1());
thread.start();
}
}
2、第一步:jps查看進程號
D:\1未備份\davecode\study\架構師領域\java\jvm>jps
12544 JStackDemo1
13584
11336 RemoteMavenServer
14760 Launcher
14012 Jps
9084 JStackDemo2
可以看到,JStackDemo2程序的進程號爲9084。
3、第二步:jstack 查看堆棧信息
D:\1未備份\davecode\study\架構師領域\java\jvm>jstack 9084
2020-05-05 16:50:16
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.191-b12 mixed mode):
"DestroyJavaVM" #13 prio=5 os_prio=0 tid=0x000000001985d800 nid=0x3acc waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Thread-0" #12 prio=5 os_prio=0 tid=0x000000001985c800 nid=0x32b4 runnable [0x000000001b41f000]
java.lang.Thread.State: RUNNABLE
at java.io.FileOutputStream.writeBytes(Native Method)
at java.io.FileOutputStream.write(FileOutputStream.java:326)
at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82)
at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:140)
- locked <0x0000000081c4bbc0> (a java.io.BufferedOutputStream)
at java.io.PrintStream.write(PrintStream.java:482)
- locked <0x0000000081c066a8> (a java.io.PrintStream)
at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:221)
at sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:291)
at sun.nio.cs.StreamEncoder.flushBuffer(StreamEncoder.java:104)
- locked <0x0000000081c06668> (a java.io.OutputStreamWriter)
at java.io.OutputStreamWriter.flushBuffer(OutputStreamWriter.java:185)
at java.io.PrintStream.write(PrintStream.java:527)
- eliminated <0x0000000081c066a8> (a java.io.PrintStream)
at java.io.PrintStream.print(PrintStream.java:597)
at java.io.PrintStream.println(PrintStream.java:736)
- locked <0x0000000081c066a8> (a java.io.PrintStream)
at deadlock.Thread1.run(Thread1.java:6)
at java.lang.Thread.run(Thread.java:748)
"Service Thread" #11 daemon prio=9 os_prio=0 tid=0x0000000019807800 nid=0x4240 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C1 CompilerThread3" #10 daemon prio=9 os_prio=2 tid=0x0000000019767800 nid=0x2428 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread2" #9 daemon prio=9 os_prio=2 tid=0x0000000019764800 nid=0x4530 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread1" #8 daemon prio=9 os_prio=2 tid=0x0000000019762800 nid=0x3214 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x0000000019729000 nid=0x3954 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x0000000019761000 nid=0x29d8 runnable [0x000000001ad1e000]
java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
at java.net.SocketInputStream.read(SocketInputStream.java:171)
at java.net.SocketInputStream.read(SocketInputStream.java:141)
at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
- locked <0x0000000081c04c48> (a java.io.InputStreamReader)
at java.io.InputStreamReader.read(InputStreamReader.java:184)
at java.io.BufferedReader.fill(BufferedReader.java:161)
at java.io.BufferedReader.readLine(BufferedReader.java:324)
- locked <0x0000000081c04c48> (a java.io.InputStreamReader)
at java.io.BufferedReader.readLine(BufferedReader.java:389)
at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:64)
"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x00000000196bf000 nid=0x34a8 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x0000000019712000 nid=0x2b8c runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x0000000002d4c800 nid=0x43b0 in Object.wait() [0x000000001aa1f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x0000000081c08760> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
- locked <0x0000000081c08760> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)
"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x00000000196a3000 nid=0x864 in Object.wait() [0x000000001a91e000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x0000000081c0e300> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x0000000081c0e300> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
"VM Thread" os_prio=2 tid=0x0000000019682800 nid=0x3894 runnable
"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x0000000002c69000 nid=0x23d8 runnable
"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x0000000002c6a800 nid=0x3370 runnable
"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x0000000002c6c000 nid=0x3be4 runnable
"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x0000000002c6e800 nid=0x272c runnable
"GC task thread#4 (ParallelGC)" os_prio=0 tid=0x0000000002c71000 nid=0x47a0 runnable
"GC task thread#5 (ParallelGC)" os_prio=0 tid=0x0000000002c72000 nid=0x3548 runnable
"GC task thread#6 (ParallelGC)" os_prio=0 tid=0x0000000002c75000 nid=0x21b4 runnable
"GC task thread#7 (ParallelGC)" os_prio=0 tid=0x0000000002c76800 nid=0x85c runnable
"GC task thread#8 (ParallelGC)" os_prio=0 tid=0x0000000002c77800 nid=0x2e38 runnable
"GC task thread#9 (ParallelGC)" os_prio=0 tid=0x0000000002c78800 nid=0x43d0 runnable
"VM Periodic Task Thread" os_prio=2 tid=0x000000001978b000 nid=0x3804 waiting on condition
JNI global references: 12
從這段堆棧信息中可以看到,
線程的狀態: WAITING 線程的調用棧
線程的當前鎖住的資源: <0x0000000081c0e300>
線程當前等待的資源:<0x0000000081c0e300>
"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x00000000196a3000 nid=0x864 in Object.wait() [0x000000001a91e000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x0000000081c0e300> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x0000000081c0e300> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
爲什麼同時鎖住和等待同一個資源:
因爲線程的執行中,先獲得了這個對象的 Monitor(對應於 locked <0x0000000081c0e300>)。
當執行到 obj.wait(),線程即放棄了 Monitor的所有權,進入 “wait set”隊列(對應於 waiting on <0x0000000081c0e300> )。
(三)案例3:線程Dump的分析
1、進入區等待
"d&a-3588" daemon waiting for monitor entry [0x000000006e5d5000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.jiuqi.dna.bap.authority.service.UserService$LoginHandler.handle()
- waiting to lock <0x0000000602f38e90> (a java.lang.Object)
at com.jiuqi.dna.bap.authority.service.UserService$LoginHandler.handle()
關鍵字:waiting for monitor entry
、waiting to lock
線程狀態BLOCKED,線程動作wait on monitor entry,調用修飾waiting to lock總是一起出現。表示在代碼級別已經存在衝突的調用。必然有問題的代碼,需要儘可能減少其發生。
2、同步塊阻塞
"blocker" runnable
java.lang.Thread.State: RUNNABLE
at com.jiuqi.hcl.javadump.Blocker$1.run(Blocker.java:23)
- locked <0x00000000eb8eff68> (a java.lang.Object)
"blockee-11" waiting for monitor entry
java.lang.Thread.State: BLOCKED (on object monitor)
at com.jiuqi.hcl.javadump.Blocker$2.run(Blocker.java:41)
- waiting to lock <0x00000000eb8eff68> (a java.lang.Object)
"blockee-86" waiting for monitor entry
java.lang.Thread.State: BLOCKED (on object monitor)
at com.jiuqi.hcl.javadump.Blocker$2.run(Blocker.java:41)
- waiting to lock <0x00000000eb8eff68> (a java.lang.Object)
關鍵字:waiting for monitor entry
、waiting to lock
、BLOCKED
、waiting to lock
一個線程鎖住某對象,大量其他線程在該對象上等待。
3、持續運行的IO
"d&a-614" daemon prio=6 tid=0x0000000022f1f000 nid=0x37c8 runnable
[0x0000000027cbd000]
java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.read(Unknown Source)
at oracle.net.ns.Packet.receive(Packet.java:240)
at oracle.net.ns.DataPacket.receive(DataPacket.java:92)
at oracle.net.ns.NetInputStream.getNextPacket(NetInputStream.java:172)
at oracle.net.ns.NetInputStream.read(NetInputStream.java:117)
at oracle.jdbc.driver.T4CMAREngine.unmarshalUB1(T4CMAREngine.java:1034)
at oracle.jdbc.driver.T4C8Oall.receive(T4C8Oall.java:588)
關鍵字:RUNNABLE
、SocketInputStream.socketRead0
、jdbc
IO操作是可以以RUNNABLE狀態達成阻塞。例如:數據庫死鎖、網絡讀寫。 格外注意對IO線程的真實狀態的分析。 一般來說,被捕捉到RUNNABLE的IO調用,都是有問題的。
以上堆棧顯示: 線程狀態爲RUNNABLE。 調用棧在SocketInputStream或SocketImpl上,socketRead0等方法。 調用棧包含了jdbc相關的包。很可能發生了數據庫死鎖。
4、分線程調度的休眠
正常的線程池等待:關鍵字:in Object.wait()
、TIMED_WAITING
"d&a-131" in Object.wait()
java.lang.Thread.State: TIMED_WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
at com.jiuqi.dna.core.impl.WorkingManager.getWorkToDo(WorkingManager.java:322)
- locked <0x0000000313f656f8> (a com.jiuqi.dna.core.impl.WorkingThread)
at com.jiuqi.dna.core.impl.WorkingThread.run(WorkingThread.java:40)
可疑的線程等待,關鍵字:in Object.wait()
、WAITING
、at com.jiuqi.dna.core.impl.Transaction.lock()
"d&a-121" in Object.wait()
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:485)
at com.jiuqi.dna.core.impl.AcquirableAccessor.exclusive()
- locked <0x00000003011678d8> (a com.jiuqi.dna.core.impl.CacheGroup)
at com.jiuqi.dna.core.impl.Transaction.lock()
5、小結
wait on monitor entry: 被阻塞的,肯定有問題
runnable : 注意IO線程
in Object.wait(): 注意非線程池等待
(四)案例4:死鎖分析
所謂死鎖: 是指兩個或兩個以上的進程在執行過程中,由於競爭資源或者由於彼此通信而造成的一種阻塞的現象,若無外力作用,它們都將無法推進下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的進程稱爲死鎖進程。
1、準備
(1)JStackDemo3.java
package jstack;
public class JStackDemo3 {
public static void main(String[] args) {
Thread t1 = new Thread(new DeadLockclass(true));//建立一個線程
Thread t2 = new Thread(new DeadLockclass(false));//建立另一個線程
t1.start();//啓動一個線程
t2.start();//啓動另一個線程
}
}
(2)DeadLockclass.java
package jstack;
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());
}
}
}
}
}
}
(2)Suo.java
package jstack;
class Suo {
static Object o1 = new Object();
static Object o2 = new Object();
}
2、啓動程序
當啓動該程序時,看一下控制檯:
"C:\Program Files\java\jdk1.8.0_191\bin\java.exe" -javaagent:D:\davesoftware\ideaIU-2018.3.win\lib\idea_rt.jar=62726:D:\davesoftware\ideaIU-2018.3.win\bin -Dfile.encoding=UTF-8 -classpath "C:\Program Files\java\jdk1.8.0_191\jre\lib\charsets.jar;C:\Program Files\java\jdk1.8.0_191\jre\lib\deploy.jar;C:\Program Files\java\jdk1.8.0_191\jre\lib\ext\access-bridge-64.jar;C:\Program Files\java\jdk1.8.0_191\jre\lib\ext\cldrdata.jar;C:\Program Files\java\jdk1.8.0_191\jre\lib\ext\dnsns.jar;C:\Program Files\java\jdk1.8.0_191\jre\lib\ext\jaccess.jar;C:\Program Files\java\jdk1.8.0_191\jre\lib\ext\jfxrt.jar;C:\Program Files\java\jdk1.8.0_191\jre\lib\ext\localedata.jar;C:\Program Files\java\jdk1.8.0_191\jre\lib\ext\nashorn.jar;C:\Program Files\java\jdk1.8.0_191\jre\lib\ext\sunec.jar;C:\Program Files\java\jdk1.8.0_191\jre\lib\ext\sunjce_provider.jar;C:\Program Files\java\jdk1.8.0_191\jre\lib\ext\sunmscapi.jar;C:\Program Files\java\jdk1.8.0_191\jre\lib\ext\sunpkcs11.jar;C:\Program Files\java\jdk1.8.0_191\jre\lib\ext\zipfs.jar;C:\Program Files\java\jdk1.8.0_191\jre\lib\javaws.jar;C:\Program Files\java\jdk1.8.0_191\jre\lib\jce.jar;C:\Program Files\java\jdk1.8.0_191\jre\lib\jfr.jar;C:\Program Files\java\jdk1.8.0_191\jre\lib\jfxswt.jar;C:\Program Files\java\jdk1.8.0_191\jre\lib\jsse.jar;C:\Program Files\java\jdk1.8.0_191\jre\lib\management-agent.jar;C:\Program Files\java\jdk1.8.0_191\jre\lib\plugin.jar;C:\Program Files\java\jdk1.8.0_191\jre\lib\resources.jar;C:\Program Files\java\jdk1.8.0_191\jre\lib\rt.jar;D:\1未備份\davecode\study\架構師領域\java\jvm\target\classes" jstack.JStackDemo3
o1 Thread-0
o2 Thread-1
可以發現,程序只輸出了兩行內容,然後程序就不再打印其它的東西了,但是程序並沒有停止。這樣就產生了死鎖。 當線程1使用synchronized
鎖住了o1的同時,線程2也是用synchronized
鎖住了o2。當兩個線程都執行完第一個打印任務的時候,線程1想鎖住o2,線程2想鎖住o1。但是,線程1當前鎖着o1,線程2鎖着o2。所以兩個想成都無法繼續執行下去,就造成了死鎖。
3、第一步:jps查看進程號
D:\1未備份\davecode\study\架構師領域\java\jvm>jps
13216 KotlinCompileDaemon
13584
15520 JStackDemo3
5456 Launcher
16116 Jps
11336 RemoteMavenServer
可以看到,JStackDemo3程序的進程號爲15520。
4、第三步:jstack 查看堆棧信息
D:\1未備份\davecode\study\架構師領域\java\jvm>jstack 15520
2020-05-05 17:27:14
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.191-b12 mixed mode):
"DestroyJavaVM" #14 prio=5 os_prio=0 tid=0x0000000002ac3800 nid=0x718 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Thread-1" #13 prio=5 os_prio=0 tid=0x00000000195b4800 nid=0x2624 waiting for monitor entry [0x000000001b27f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at jstack.DeadLockclass.run(DeadLockclass.java:30)
- waiting to lock <0x00000000d62e8978> (a java.lang.Object)
- locked <0x00000000d62e8988> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
"Thread-0" #12 prio=5 os_prio=0 tid=0x00000000195b3800 nid=0x578 waiting for monitor entry [0x000000001b17f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at jstack.DeadLockclass.run(DeadLockclass.java:17)
- waiting to lock <0x00000000d62e8988> (a java.lang.Object)
- locked <0x00000000d62e8978> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
"Service Thread" #11 daemon prio=9 os_prio=0 tid=0x0000000019500800 nid=0x2134 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C1 CompilerThread3" #10 daemon prio=9 os_prio=2 tid=0x00000000194d5000 nid=0x402c waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread2" #9 daemon prio=9 os_prio=2 tid=0x00000000194d2000 nid=0x47c4 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread1" #8 daemon prio=9 os_prio=2 tid=0x00000000194d0800 nid=0x3d50 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x00000000194c6000 nid=0x1970 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x00000000194b5800 nid=0x3978 runnable [0x000000001aa7e000]
java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
at java.net.SocketInputStream.read(SocketInputStream.java:171)
at java.net.SocketInputStream.read(SocketInputStream.java:141)
at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
- locked <0x00000000d608d230> (a java.io.InputStreamReader)
at java.io.InputStreamReader.read(InputStreamReader.java:184)
at java.io.BufferedReader.fill(BufferedReader.java:161)
at java.io.BufferedReader.readLine(BufferedReader.java:324)
- locked <0x00000000d608d230> (a java.io.InputStreamReader)
at java.io.BufferedReader.readLine(BufferedReader.java:389)
at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:64)
"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x000000001942e000 nid=0x8d0 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x0000000017d4e800 nid=0x43f0 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x0000000017d43000 nid=0x214c in Object.wait() [0x000000001a77f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000d5f08ed0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
- locked <0x00000000d5f08ed0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)
"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x0000000019413000 nid=0x37f8 in Object.wait() [0x000000001a67f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000d5f06bf8> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x00000000d5f06bf8> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
"VM Thread" os_prio=2 tid=0x0000000017d36800 nid=0x2d18 runnable
"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x0000000002ad9000 nid=0xb94 runnable
"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x0000000002ada800 nid=0x26ec runnable
"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x0000000002adc000 nid=0x3ca8 runnable
"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x0000000002ade800 nid=0x3ab4 runnable
"GC task thread#4 (ParallelGC)" os_prio=0 tid=0x0000000002ae1000 nid=0x22bc runnable
"GC task thread#5 (ParallelGC)" os_prio=0 tid=0x0000000002ae2000 nid=0x2844 runnable
"GC task thread#6 (ParallelGC)" os_prio=0 tid=0x0000000002ae5000 nid=0x2450 runnable
"GC task thread#7 (ParallelGC)" os_prio=0 tid=0x0000000002ae6800 nid=0x3d84 runnable
"GC task thread#8 (ParallelGC)" os_prio=0 tid=0x0000000002ae7800 nid=0x3e60 runnable
"GC task thread#9 (ParallelGC)" os_prio=0 tid=0x0000000002ae8800 nid=0x19ac runnable
"VM Periodic Task Thread" os_prio=2 tid=0x0000000019501000 nid=0x2be8 waiting on condition
JNI global references: 12
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x0000000017d3fe28 (object 0x00000000d62e8978, a java.lang.Object),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x0000000017d42608 (object 0x00000000d62e8988, a java.lang.Object),
which is held by "Thread-1"
Java stack information for the threads listed above:
===================================================
"Thread-1":
at jstack.DeadLockclass.run(DeadLockclass.java:30)
- waiting to lock <0x00000000d62e8978> (a java.lang.Object)
- locked <0x00000000d62e8988> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
"Thread-0":
at jstack.DeadLockclass.run(DeadLockclass.java:17)
- waiting to lock <0x00000000d62e8988> (a java.lang.Object)
- locked <0x00000000d62e8978> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
Found 1 deadlock.
堆棧寫的很明顯,它告訴我們 Found one Java-level deadlock
,然後指出造成死鎖的兩個線程的內容。然後,又通過 Java stack information for the threads listed above
來顯示更詳細的死鎖的信息。 他說
Thread-1在想要執行第40行的時候,當前鎖住了資源<0x00000007d6aa2ca8>
,但是他在等待資源<0x00000007d6aa2c98>
Thread-0在想要執行第27行的時候,當前鎖住了資源<0x00000007d6aa2c98>
,但是他在等待資源<0x00000007d6aa2ca8>
由於這兩個線程都持有資源,並且都需要對方的資源,所以造成了死鎖。 原因我們找到了,就可以具體問題具體分析,解決這個死鎖了。
(五)小結
虛擬機執行Full GC時,會阻塞所有的用戶線程。因此,即時獲取到同步鎖的線程也有可能被阻塞。 在查看線程Dump時,首先查看內存使用情況。
對於jstack做的ThreadDump的棧,可以反映如下信息(一個load飆高的過程分析,非常有價值)
1. 如果某個相同的call stack經常出現, 我們有80%的以上的理由確定這個代碼存在性能問題(讀網絡的部分除外);
2. 如果相同的call stack出現在同一個線程上(tid)上, 我們很很大理由相信, 這段代碼可能存在較多的循環或者死循環;
3. 如果某call stack經常出現, 並且裏面帶有lock,請檢查一下這個lock的產生的原因, 可能是全局lock造成了性能問題;
4. 在一個不大壓力的羣集裏(w<2), 我們是很少拿到帶有業務代碼的stack的, 並且一般在一個完整stack中, 最多隻有1-2業務代碼的stack,
5. 如果經常出現, 一定要檢查代碼, 是否出現性能問題。
6. 如果你懷疑有dead lock問題, 那麼請把所有的lock id找出來,看看是不是出現重複的lock id。
jstack -m 會打印出JVM堆棧信息,涉及C、C++部分代碼,可能需要配合gdb命令來分析。