天天看點

【Java類初始化死鎖】記一次Cassandra死鎖問題排查背景調查過程總結後續

背景

最近壓測Cassandra的時候,發現一個Cassandra程序一直沒有完成初始化。經過排查後發現是死鎖問題,這篇文章将會帶領大家回顧整個排查過程,學習如何排查Java死鎖問題,是一個非常值得學習的經驗。

調查過程

1.問題發現

首先是啟動後,通過Cassandra指令

nodetool netstats

觀察何時進入NORMAL狀态。如下圖所示:

【Java類初始化死鎖】記一次Cassandra死鎖問題排查背景調查過程總結後續

但是過了很久都沒有進入NORMAL,一直處于STARTING狀态。

2.jstack排查

想要知道為啥一直處于STARTING狀态,當然是用Jstack去觀察程序到底在做什麼。

【Java類初始化死鎖】記一次Cassandra死鎖問題排查背景調查過程總結後續

jstack顯示主線程處于WAITING狀态。這符合我們觀察到的現象,因為确實一直卡在STARTING狀态沒有走下去。使主線程卡住的地方是

AbstractCommitLogSegmentManager.awaitAvailableSegment()

,這時候需要去看具體的源碼。看看這個wait調用會由誰通知解開。

3.分析源碼

源碼分析後得知:

awaitAvailableSegment()

在等待另一個線程建立Segment,也就是等待生産者建立好對應資源。而這個生産者的線程叫

COMMIT-LOG-ALLOCATOR

。是以我們再去看一眼剛才的jstack,看看這個線程在做什麼。預想中的情況應該是這個生産者線程也卡在某個地方,或者因為未知異常退出了(退出時候沒能通知等在

awaitAvailableSegment()

上的線程)。

4.排查問題線程

在jstack中找到

COMMIT-LOG-ALLOCATOR

【Java類初始化死鎖】記一次Cassandra死鎖問題排查背景調查過程總結後續

乍一看,蒙圈了。這個線程處于RUNNABLE狀态,完全不符合我們預想中的情況。那這個線程明明在“運作”,為何沒有把對應的Segment資源生産出來呢?一種可能是死循環了,邏輯沒有往下走。通過多次jstack發現,stack總是在133這行。并且通過

cat /proc/<pid>/task/<tid>/stat

确認,這個線程确實沒有在運作,卡在133這行。

這裡擷取tid的方法是通過将jstack中16進制的nid轉換成10進制,

printf %d 0x4b7f

。輸出:19327

然後我們繼續,看133這行代碼:

【Java類初始化死鎖】記一次Cassandra死鎖問題排查背景調查過程總結後續

133這行代碼,是非常簡單的一個靜态函數調用

CommitLog.handleCommitError()

,沒有涉及任何鎖。為何會卡死在這行上?為何jstack看到的狀态卻是RUNNABLE?

Java層面的工具已經不能解決問題了。這時候通過pstack指令,檢視堆棧:

【Java類初始化死鎖】記一次Cassandra死鎖問題排查背景調查過程總結後續

我們發現線程實際卡在JVM的

InstanceKlass::initialize

調用上,也就是卡在類初始化上了。第一反應就是類初始化死鎖問題,回顧下133這行代碼,調用了

CommitLog.handleCommitError()

,也就是通路到了

CommitLog

這個類。然後我們再去jstack裡看一下,哪個線程在負責

CommitLog

的初始化,并且一直沒初始化好。沒錯就是最開始我們看到卡住的主線程!

【Java類初始化死鎖】記一次Cassandra死鎖問題排查背景調查過程總結後續

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感興趣,也歡迎加入社群讨論,掃描下方二維碼進入。

【Java類初始化死鎖】記一次Cassandra死鎖問題排查背景調查過程總結後續