背景
最近壓測Cassandra的時候,發現一個Cassandra程序一直沒有完成初始化。經過排查後發現是死鎖問題,這篇文章将會帶領大家回顧整個排查過程,學習如何排查Java死鎖問題,是一個非常值得學習的經驗。
調查過程
1.問題發現
首先是啟動後,通過Cassandra指令
nodetool netstats
觀察何時進入NORMAL狀态。如下圖所示:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnLmRGOiNTMzEmZ3IGOlVjY2ITZjdzMxczY2IGM4ETZxYmYxcTM2IjY48CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.png)
但是過了很久都沒有進入NORMAL,一直處于STARTING狀态。
2.jstack排查
想要知道為啥一直處于STARTING狀态,當然是用Jstack去觀察程序到底在做什麼。
jstack顯示主線程處于WAITING狀态。這符合我們觀察到的現象,因為确實一直卡在STARTING狀态沒有走下去。使主線程卡住的地方是
AbstractCommitLogSegmentManager.awaitAvailableSegment()
,這時候需要去看具體的源碼。看看這個wait調用會由誰通知解開。
3.分析源碼
源碼分析後得知:
awaitAvailableSegment()
在等待另一個線程建立Segment,也就是等待生産者建立好對應資源。而這個生産者的線程叫
COMMIT-LOG-ALLOCATOR
。是以我們再去看一眼剛才的jstack,看看這個線程在做什麼。預想中的情況應該是這個生産者線程也卡在某個地方,或者因為未知異常退出了(退出時候沒能通知等在
awaitAvailableSegment()
上的線程)。
4.排查問題線程
在jstack中找到
COMMIT-LOG-ALLOCATOR
:
乍一看,蒙圈了。這個線程處于RUNNABLE狀态,完全不符合我們預想中的情況。那這個線程明明在“運作”,為何沒有把對應的Segment資源生産出來呢?一種可能是死循環了,邏輯沒有往下走。通過多次jstack發現,stack總是在133這行。并且通過
cat /proc/<pid>/task/<tid>/stat
确認,這個線程确實沒有在運作,卡在133這行。
這裡擷取tid的方法是通過将jstack中16進制的nid轉換成10進制,
printf %d 0x4b7f
。輸出:19327
然後我們繼續,看133這行代碼:
133這行代碼,是非常簡單的一個靜态函數調用
CommitLog.handleCommitError()
,沒有涉及任何鎖。為何會卡死在這行上?為何jstack看到的狀态卻是RUNNABLE?
Java層面的工具已經不能解決問題了。這時候通過pstack指令,檢視堆棧:
我們發現線程實際卡在JVM的
InstanceKlass::initialize
調用上,也就是卡在類初始化上了。第一反應就是類初始化死鎖問題,回顧下133這行代碼,調用了
CommitLog.handleCommitError()
,也就是通路到了
CommitLog
這個類。然後我們再去jstack裡看一下,哪個線程在負責
CommitLog
的初始化,并且一直沒初始化好。沒錯就是最開始我們看到卡住的主線程!
jstack中的
clinit
是表示進入類初始化。
5.水落石出
主線程 -> 進入
CommitLog
初始化流程 -> 調用
awaitAvailableSegment()
-> 等待
COMMIT-LOG-ALLOCATOR
完成資源生産
此時CommitLog類還沒有初始化完成,主線程持有CommitLog類的鎖。
COMMIT-LOG-ALLOCATOR
-> 生産資源 -> 遇到異常,走到catch邏輯(133行)-> 133行通路
CommitLog
類 ->
CommitLog
仍然在初始化 -> 死鎖産生
總結
Java類初始化死鎖,往往是由于多個靜态類互相之間的關系設計不好,互相通路,導緻可能出現初始化時候死鎖。排查這類問題,一般都是jstack,然後再結合代碼分析。順便提一下,前面提到的RUNNABLE狀态,并不能表示線程就在運作,雖然大多數時候是。
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.
摘自:
https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.State.html
後續
最後這個問題,已經貢獻到社群 CASSANDRA-15295 。如果你想使用穩定的Cassandra服務,歡迎試用
阿裡雲Cassandra服務。如果你對Cassandra感興趣,也歡迎加入社群讨論,掃描下方二維碼進入。