詳解JAVA執行緒問題診斷工具Thread Dump

2022-12-27 18:03:18
摘要:Thread Dump是非常有用的診斷Java應用問題的工具。

本文分享自華為雲社群《偵錯排錯 - Java 執行緒分析之執行緒Dump分析》,作者:龍哥手記。

Thread Dump是非常有用的診斷Java應用問題的工具。每一個Java虛擬機器器都有及時生成所有執行緒在某一點狀態的thread-dump的能力,雖然各個 Java虛擬機器器列印的thread dump略有不同,但是 大多都提供了當前活動執行緒的快照,及JVM中所有Java執行緒的堆疊跟蹤資訊,堆疊資訊一般包含完整的類名及所執行的方法,如果可能的話還有原始碼的行數。

Thread Dump特點

  • 能在各種作業系統下使用;
  • 能在各種Java應用伺服器下使用;
  • 能在生產環境下使用而不影響系統的效能;
  • 能將問題直接定位到應用程式的程式碼行上;

Thread Dump抓取

一般當伺服器掛起,崩潰或者效能低下時,就需要抓取伺服器的執行緒堆疊(Thread Dump)用於後續的分析。在實際執行中,往往一次 dump的資訊,還不足以確認問題。為了反映執行緒狀態的動態變化,需要接連多次做thread dump,每次間隔10-20s,建議至少產生三次 dump資訊,如果每次 dump都指向同一個問題,我們才確定問題的典型性。

  • 作業系統命令獲取ThreadDump
ps –ef | grep java
kill -3 <pid>

注意:

一定要謹慎, 一步不慎就可能讓伺服器程序被殺死。kill -9 命令會殺死程序。

  • JVM 自帶的工具獲取執行緒堆疊
jps 或 ps –ef | grep java (獲取PID)
jstack [-l ] <pid> | tee -a jstack.log(獲取ThreadDump)

Thread Dump分析

Thread Dump資訊

  • 頭部資訊:時間,JVM資訊
2011-11-02 19:05:06  
Full thread dump Java HotSpot(TM) Server VM (16.3-b01 mixed mode): 
  • 執行緒INFO資訊塊:
1. "Timer-0" daemon prio=10 tid=0xac190c00 nid=0xaef in Object.wait() [0xae77d000] 
# 執行緒名稱:Timer-0;執行緒型別:daemon;優先順序: 10,預設是5;
# JVM執行緒id:tid=0xac190c00,JVM內部執行緒的唯一標識(通過java.lang.Thread.getId()獲取,通常用自增方式實現)。
# 對應系統執行緒id(NativeThread ID):nid=0xaef,和top命令檢視的執行緒pid對應,不過一個是10進位制,一個是16進位制。(通過命令:top -H -p pid,可以檢視該程序的所有執行緒資訊)
# 執行緒狀態:in Object.wait();
# 起始棧地址:[0xae77d000],物件的記憶體地址,通過JVM記憶體檢視工具,能夠看出執行緒是在哪兒個物件上等待;
2.  java.lang.Thread.State: TIMED_WAITING (on object monitor)
3.  at java.lang.Object.wait(Native Method)
4.  -waiting on <0xb3885f60> (a java.util.TaskQueue)     # 繼續wait 
5.  at java.util.TimerThread.mainLoop(Timer.java:509)
6.  -locked <0xb3885f60> (a java.util.TaskQueue)         # 已經locked
7.  at java.util.TimerThread.run(Timer.java:462)
Java thread statck trace:是上面2-7行的資訊。到目前為止這是最重要的資料,Java stack trace提供了大部分資訊來精確定位問題根源。
  • Java thread statck trace詳解:

堆疊資訊應該逆向解讀:程式先執行的是第7行,然後是第6行,依次類推。

- locked <0xb3885f60> (a java.util.ArrayList)
- waiting on <0xb3885f60> (a java.util.ArrayList) 

也就是說物件先上鎖,鎖住物件0xb3885f60,然後釋放該物件鎖,進入waiting狀態。為啥會出現這樣的情況呢?看看下面的java程式碼範例,就會明白:

synchronized(obj) {  
   .........  
   obj.wait();  
   .........  
}
如上,執行緒的執行過程,先用 synchronized 獲得了這個物件的 Monitor(對應於 locked <0xb3885f60> )。當執行到 obj.wait(),執行緒即放棄了 Monitor的所有權,進入 「wait set」佇列(對應於 waiting on <0xb3885f60> )。

在堆疊的第一行資訊中,進一步標明瞭執行緒在程式碼級的狀態,例如:

java.lang.Thread.State: TIMED_WAITING (parking)
解釋如下:
|blocked|

> This thread tried to enter asynchronized block, but the lock was taken by another thread. This thread isblocked until the lock gets released.

|blocked (on thin lock)|

> This is the same state asblocked, but the lock in question is a thin lock.

|waiting|

> This thread calledObject.wait() on an object. The thread will remain there until some otherthread sends a notification to that object.

|sleeping|

> This thread calledjava.lang.Thread.sleep().

|parked|

> This thread calledjava.util.concurrent.locks.LockSupport.park().

|suspended|

> The thread's execution wassuspended by java.lang.Thread.suspend() or a JVMTI agent call.
 

Thread狀態分析

執行緒的狀態是一個很重要的東西,因此thread dump中會顯示這些狀態,通過對這些狀態的分析,能夠得出執行緒的執行狀況,進而發現可能存在的問題。執行緒的狀態在Thread.State這個列舉型別中定義

public enum State   
{  
       /** 
        * Thread state for a thread which has not yet started. 
        */  
       NEW,  
         
       /** 
        * Thread state for a runnable thread.  A thread in the runnable 
        * state is executing in the Java virtual machine but it may 
        * be waiting for other resources from the operating system 
        * such as processor. 
        */  
       RUNNABLE,  
         
       /** 
        * Thread state for a thread blocked waiting for a monitor lock. 
        * A thread in the blocked state is waiting for a monitor lock 
        * to enter a synchronized block/method or  
        * reenter a synchronized block/method after calling 
        * {@link Object#wait() Object.wait}. 
        */  
       BLOCKED,  
     
       /** 
        * Thread state for a waiting thread. 
        * A thread is in the waiting state due to calling one of the  
        * following methods: 
        * <ul> 
        *   <li>{@link Object#wait() Object.wait} with no timeout</li> 
        *   <li>{@link #join() Thread.join} with no timeout</li> 
        *   <li>{@link LockSupport#park() LockSupport.park}</li> 
        * </ul> 
        *  
        * <p>A thread in the waiting state is waiting for another thread to 
        * perform a particular action.   
        * 
        * For example, a thread that has called <tt>Object.wait()</tt> 
        * on an object is waiting for another thread to call  
        * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on  
        * that object. A thread that has called <tt>Thread.join()</tt>  
        * is waiting for a specified thread to terminate. 
        */  
       WAITING,  
         
       /** 
        * Thread state for a waiting thread with a specified waiting time. 
        * A thread is in the timed waiting state due to calling one of  
        * the following methods with a specified positive waiting time: 
        * <ul> 
        *   <li>{@link #sleep Thread.sleep}</li> 
        *   <li>{@link Object#wait(long) Object.wait} with timeout</li> 
        *   <li>{@link #join(long) Thread.join} with timeout</li> 
        *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>  
        *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li> 
        * </ul> 
        */  
       TIMED_WAITING,  
  
       /** 
        * Thread state for a terminated thread. 
        * The thread has completed execution. 
        */  
       TERMINATED;  
}
  • NEW:

每一個執行緒,在堆記憶體中都有一個對應的Thread物件。Thread t = new Thread();當剛剛在堆記憶體中建立Thread物件,還沒有呼叫t.start()方法之前,執行緒就處在NEW狀態。在這個狀態上,執行緒與普通的java物件沒有什麼區別,就僅僅是一個堆記憶體中的物件。

  • RUNNABLE:

該狀態表示執行緒具備所有執行條件,在執行佇列中準備作業系統的排程,或者正在執行。 這個狀態的執行緒比較正常,但如果執行緒長時間停留在在這個狀態就不正常了,這說明執行緒執行的時間很長(存在效能問題),或者是執行緒一直得不得執行的機會(存線上程飢餓的問題)。

  • BLOCKED:

執行緒正在等待獲取java物件的監視器(也叫內建鎖),即執行緒正在等待進入由synchronized保護的方法或者程式碼塊。synchronized用來保證原子性,任意時刻最多隻能由一個執行緒進入該臨界區域,其他執行緒只能排隊等待。

  • WAITING:

處在該執行緒的狀態,正在等待某個事件的發生,只有特定的條件滿足,才能獲得執行機會。而產生這個特定的事件,通常都是另一個執行緒。也就是說,如果不發生特定的事件,那麼處在該狀態的執行緒一直等待,不能獲取執行的機會。比如:

A執行緒呼叫了obj物件的obj.wait()方法,如果沒有執行緒呼叫obj.notify或obj.notifyAll,那麼A執行緒就沒有辦法恢復執行; 如果A執行緒呼叫了LockSupport.park(),沒有別的執行緒呼叫LockSupport.unpark(A),那麼A沒有辦法恢復執行。 TIMED_WAITING:

J.U.C中很多與執行緒相關類,都提供了限時版本和不限時版本的API。TIMED_WAITING意味著執行緒呼叫了限時版本的API,正在等待時間流逝。當等待時間過去後,執行緒一樣可以恢復執行。如果執行緒進入了WAITING狀態,一定要特定的事件發生才能恢復執行;而處在TIMED_WAITING的執行緒,如果特定的事件發生或者是時間流逝完畢,都會恢復執行。

  • TERMINATED:

執行緒執行完畢,執行完run方法正常返回,或者丟擲了執行時異常而結束,執行緒都會停留在這個狀態。這個時候執行緒只剩下Thread物件了,沒有什麼用了。

關鍵狀態分析

  • Wait on condition:The thread is either sleeping or waiting to be notified by another thread.

該狀態說明它在等待另一個條件的發生,來把自己喚醒,或者乾脆它是呼叫了 sleep(n)。

此時執行緒狀態大致為以下幾種:

java.lang.Thread.State: WAITING (parking):一直等那個條件發生;
java.lang.Thread.State: TIMED_WAITING (parking或sleeping):定時的,那個條件不到來,也將定時喚醒自己。
  • Waiting for Monitor Entry 和 in Object.wait():The thread is waiting to get the lock for an object (some other thread may be holding the lock). This happens if two or more threads try to execute synchronized code. Note that the lock is always for an object and not for individual methods.

在多執行緒的JAVA程式中,實現執行緒之間的同步,就要說說 Monitor。Monitor是Java中用以實現執行緒之間的互斥與共同作業的主要手段,它可以看成是物件或者Class的鎖。每一個物件都有,也僅有一個 Monitor 。下面這個圖,描述了執行緒和 Monitor之間關係,以及執行緒的狀態轉換圖:

 

 

點選關注,第一時間瞭解華為雲新鮮技術~