jstack分析線程死鎖


一、介紹

jstack是java虛擬機自帶的一種堆棧跟蹤工具。jstack用於打印出給定的java進程ID或core file或遠程調試服務的Java堆棧信息,如果是在64位機器上,需要指定選項"-J-d64",Windows的jstack使用方式只支持以下的這種方式:

jstack [-l] pid

主要分為兩個功能: 

a.  針對活着的進程做本地的或遠程的線程dump; 

b.  針對core文件做線程dump。

jstack用於生成java虛擬機當前時刻的線程快照。線程快照是當前java虛擬機內每一條線程正在執行的方法堆棧的集合,生成線程快照的主要目的是定位線程出現長時間停頓的原因,如線程間死鎖、死循環、請求外部資源導致的長時間等待等。 線程出現停頓的時候通過jstack來查看各個線程的調用堆棧,就可以知道沒有響應的線程到底在后台做什么事情,或者等待什么資源。 如果java程序崩潰生成core文件,jstack工具可以用來獲得core文件的java stack和native stack的信息,從而可以輕松地知道java程序是如何崩潰和在程序何處發生問題。另外,jstack工具還可以附屬到正在運行的java程序中,看到當時運行的java程序的java stack和native stack的信息, 如果現在運行的java程序呈現hung的狀態,jstack是非常有用的。

 

jstack命令主要用來查看Java線程的調用堆棧的,可以用來分析線程問題(如死鎖)。

線程狀態

想要通過jstack命令來分析線程的情況的話,首先要知道線程都有哪些狀態,下面這些狀態是我們使用jstack命令查看線程堆棧信息時可能會看到的線程的幾種狀態:

NEW,未啟動的。不會出現在Dump中。

RUNNABLE,在虛擬機內執行的。運行中狀態,可能里面還能看到locked字樣,表明它獲得了某把鎖。

BLOCKED,受阻塞並等待監視器鎖。被某個鎖(synchronizers)給block住了。

WATING,無限期等待另一個線程執行特定操作。等待某個condition或monitor發生,一般停留在park(), wait(), sleep(),join() 等語句里。

TIMED_WATING,有時限的等待另一個線程的特定操作。和WAITING的區別是wait() 等語句加上了時間限制 wait(timeout)。

TERMINATED,已退出的。

Monitor

在多線程的 JAVA程序中,實現線程之間的同步,就要說說 Monitor。 Monitor是 Java中用以實現線程之間的互斥與協作的主要手段,它可以看成是對象或者 Class的鎖。每一個對象都有,也僅有一個 monitor。下面這個圖,描述了線程和 Monitor之間關系,以 及線程的狀態轉換圖:

進入區(Entry 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”隊列。

調用修飾

表示線程在方法調用時,額外的重要的操作。線程Dump分析的重要信息。修飾上方的方法調用。

locked <地址> 目標:使用synchronized申請對象鎖成功,監視器的擁有者。

waiting to lock <地址> 目標:使用synchronized申請對象鎖未成功,在迚入區等待。

waiting on <地址> 目標:使用synchronized申請對象鎖成功后,釋放鎖后在等待區等待。

parking to wait for <地址> 目標

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體系不同。

線程動作

線程狀態產生的原因

runnable:狀態一般為RUNNABLE。

in Object.wait():等待區等待,狀態為WAITING或TIMED_WAITING。

waiting for monitor entry:進入區等待,狀態為BLOCKED。

waiting on condition:等待區等待、被park。

sleeping:休眠的線程,調用了Thread.sleep()。

Wait on condition 該狀態出現在線程等待某個條件的發生。具體是什么原因,可以結合 stacktrace來分析。 最常見的情況就是線程處於sleep狀態,等待被喚醒。 常見的情況還有等待網絡IO:在java引入nio之前,對於每個網絡連接,都有一個對應的線程來處理網絡的讀寫操作,即使沒有可讀寫的數據,線程仍然阻塞在讀寫操作上,這樣有可能造成資源浪費,而且給操作系統的線程調度也帶來壓力。在 NewIO里采用了新的機制,編寫的服務器程序的性能和可擴展性都得到提高。 正等待網絡讀寫,這可能是一個網絡瓶頸的征兆。因為網絡阻塞導致線程無法執行。一種情況是網絡非常忙,幾 乎消耗了所有的帶寬,仍然有大量數據等待網絡讀 寫;另一種情況也可能是網絡空閑,但由於路由等問題,導致包無法正常的到達。所以要結合系統的一些性能觀察工具來綜合分析,比如 netstat統計單位時間的發送包的數目,如果很明顯超過了所在網絡帶寬的限制 ; 觀察 cpu的利用率,如果系統態的 CPU時間,相對於用戶態的 CPU時間比例較高;如果程序運行在 Solaris 10平台上,可以用 dtrace工具看系統調用的情況,如果觀察到 read/write的系統調用的次數或者運行時間遙遙領先;這些都指向由於網絡帶寬所限導致的網絡瓶頸。

 

二、命令格式

jstack [ option ] pid
jstack [ option ] executable core
jstack [ option ] [server-id@]remote-hostname-or-IP

 

常用參數說明

1)options: 

executable Java executable from which the core dump was produced.(可能是產生core dump的java可執行程序)

core 將被打印信息的core dump文件

remote-hostname-or-IP 遠程debug服務的主機名或ip

server-id 唯一id,假如一台主機上多個遠程debug服務 

2)基本參數:

-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打印幫助信息

pid 需要被打印配置信息的java進程id,可以用jps查詢.

 

線程dump的分析工具:

 

其他

虛擬機執行Full GC時,會阻塞所有的用戶線程。因此,即時獲取到同步鎖的線程也有可能被阻塞。 在查看線程Dump時,首先查看內存使用情況。

 

頻繁GC問題或內存溢出問題

一、使用jps查看線程ID

二、使用jstat -gc 3331 250 20 查看gc情況,一般比較關注PERM區的情況,查看GC的增長情況。

三、使用jstat -gccause:額外輸出上次GC原因

四、使用jmap -dump:format=b,file=heapDump 3331生成堆轉儲文件

五、使用jhat或者可視化工具(Eclipse Memory Analyzer 、IBM HeapAnalyzer)分析堆情況。

六、結合代碼解決內存溢出或泄露問題。

 

死鎖問題

一、使用jps查看線程ID

二、使用jstack 3331:查看線程情況

寫個簡單的死鎖demo:

 

對於jstack做的ThreadDump的棧,可以反映如下信息:

  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。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM