摘要:Thread Dump是非常有用的診斷Java應用問題的工具。
本文分享自華為雲社群《偵錯排錯 - Java 執行緒分析之執行緒Dump分析》,作者:龍哥手記。
Thread Dump是非常有用的診斷Java應用問題的工具。每一個Java虛擬機器器都有及時生成所有執行緒在某一點狀態的thread-dump的能力,雖然各個 Java虛擬機器器列印的thread dump略有不同,但是 大多都提供了當前活動執行緒的快照,及JVM中所有Java執行緒的堆疊跟蹤資訊,堆疊資訊一般包含完整的類名及所執行的方法,如果可能的話還有原始碼的行數。
一般當伺服器掛起,崩潰或者效能低下時,就需要抓取伺服器的執行緒堆疊(Thread Dump)用於後續的分析。在實際執行中,往往一次 dump的資訊,還不足以確認問題。為了反映執行緒狀態的動態變化,需要接連多次做thread dump,每次間隔10-20s,建議至少產生三次 dump資訊,如果每次 dump都指向同一個問題,我們才確定問題的典型性。
ps –ef | grep java kill -3 <pid>
注意:
一定要謹慎, 一步不慎就可能讓伺服器程序被殺死。kill -9 命令會殺死程序。
jps 或 ps –ef | grep java (獲取PID)
jstack [-l ] <pid> | tee -a jstack.log(獲取ThreadDump)
2011-11-02 19:05:06 Full thread dump Java HotSpot(TM) Server VM (16.3-b01 mixed mode):
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提供了大部分資訊來精確定位問題根源。
堆疊資訊應該逆向解讀:程式先執行的是第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 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; }
每一個執行緒,在堆記憶體中都有一個對應的Thread物件。Thread t = new Thread();當剛剛在堆記憶體中建立Thread物件,還沒有呼叫t.start()方法之前,執行緒就處在NEW狀態。在這個狀態上,執行緒與普通的java物件沒有什麼區別,就僅僅是一個堆記憶體中的物件。
該狀態表示執行緒具備所有執行條件,在執行佇列中準備作業系統的排程,或者正在執行。 這個狀態的執行緒比較正常,但如果執行緒長時間停留在在這個狀態就不正常了,這說明執行緒執行的時間很長(存在效能問題),或者是執行緒一直得不得執行的機會(存線上程飢餓的問題)。
執行緒正在等待獲取java物件的監視器(也叫內建鎖),即執行緒正在等待進入由synchronized保護的方法或者程式碼塊。synchronized用來保證原子性,任意時刻最多隻能由一個執行緒進入該臨界區域,其他執行緒只能排隊等待。
處在該執行緒的狀態,正在等待某個事件的發生,只有特定的條件滿足,才能獲得執行機會。而產生這個特定的事件,通常都是另一個執行緒。也就是說,如果不發生特定的事件,那麼處在該狀態的執行緒一直等待,不能獲取執行的機會。比如:
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的執行緒,如果特定的事件發生或者是時間流逝完畢,都會恢復執行。
執行緒執行完畢,執行完run方法正常返回,或者丟擲了執行時異常而結束,執行緒都會停留在這個狀態。這個時候執行緒只剩下Thread物件了,沒有什麼用了。
該狀態說明它在等待另一個條件的發生,來把自己喚醒,或者乾脆它是呼叫了 sleep(n)。
此時執行緒狀態大致為以下幾種:
java.lang.Thread.State: WAITING (parking):一直等那個條件發生;
java.lang.Thread.State: TIMED_WAITING (parking或sleeping):定時的,那個條件不到來,也將定時喚醒自己。
在多執行緒的JAVA程式中,實現執行緒之間的同步,就要說說 Monitor。Monitor是Java中用以實現執行緒之間的互斥與共同作業的主要手段,它可以看成是物件或者Class的鎖。每一個物件都有,也僅有一個 Monitor 。下面這個圖,描述了執行緒和 Monitor之間關係,以及執行緒的狀態轉換圖: