虛擬機性能監控與故障處理工具-JDK的命令行工具-jstack:Java堆棧跟蹤工具

虛擬機性能監控與故障處理工具-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的分析工具:

二、實踐

(一)死鎖問題

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之間關係,以及線程的狀態轉換圖:

img

進入區(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 entrywaiting 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 entrywaiting to lockBLOCKEDwaiting 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)

關鍵字:RUNNABLESocketInputStream.socketRead0jdbc

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()WAITINGat 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命令來分析。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章