1. 程式人生 > >Java問題定位之如何藉助執行緒堆疊進行問題分析

Java問題定位之如何藉助執行緒堆疊進行問題分析

在大型的應用中,執行緒堆疊打印出來特別多,如何從眾多的資訊中找到真正有用,有價值的資訊,我們需要一定的技巧。本文對此詳細介紹。

我們可以從三個方面分析:堆疊的區域性資訊,一次堆疊的統計資訊,多個堆疊的對比資訊。

  • 從一次的堆疊資訊中,我們可以直接獲取以下資訊:
    • 每一個執行緒的呼叫關係,當前執行緒在呼叫哪些函式
    • 每個執行緒的當前狀態,持有哪些鎖,在等待哪些鎖?
  • 從一次堆疊資訊中,我們還可以統計以下資訊:
    • 是否有很多執行緒都在等待同一個鎖,說明這個系統存在效能瓶頸,導致了鎖競爭
    • 當前執行緒的總數量
    • 大多數執行緒在幹什麼,在執行什麼程式碼?
  • 從多次的堆疊資訊中,可以得到以下資訊:
    • 一個執行緒是否長期執行,如果每次列印堆疊某個執行緒一直處於同樣的呼叫上下文中,那麼說明這個執行緒一直執行這段程式碼,此時要根據程式碼邏輯檢查,是否合理。
    • 某個執行緒是否長期存在獲取不到鎖的情況,執行緒是否永遠得不到喚醒,如果某一個執行緒一直等在一個鎖,就要檢查佔用這個鎖的執行緒為什麼不釋放。
如果說列印一次執行緒堆疊是平面,那麼列印多次就是立體了,我們可以看到一段執行的情況。

接下來從以下幾個方面分析:

  • 執行緒死鎖分析
  • java程式碼導致的cpu過高分析
  • 死迴圈分析
  • 資源不足分析效能瓶頸分析

執行緒死鎖分析

執行緒死鎖的原因:當兩個或者多個執行緒正在等待被對方佔用的鎖,死鎖就會發生。在時間0的時候,執行緒0佔用lock0鎖,執行緒1佔用lock1鎖。在時間1,進行了其它操作,在時間2,執行緒0,1企圖持有對方的鎖,但是由於2個鎖已經在時間0被鎖住,所以只能等待釋放。由於這兩個執行緒互相要等待被對方佔有的鎖,自己才能繼續,因此就造成了死鎖。

我們看以下示例:
從列印的堆疊資訊可以看到第一行,found one java-level deadlock ,如果執行緒存在死鎖情況,堆疊會直接給出死鎖的分析結果。從堆疊資訊中可以看到,TestThread2中持有鎖<0x22bffb10>但在等待鎖<0x22bffb08>,相反,TestThread2中持有鎖<0x22bffb08>,但在等待鎖<0x22bffb10>。這裡說的死鎖是真正意義上的死鎖,由於程式碼的錯誤導致的,嚴重程度取決於執行緒執行什麼性質的功能程式碼,如果是關鍵功能,可能造成整個系統的癱瘓。死鎖的2個或者多個執行緒是不消耗cpu的,所以認為死鎖會導致cpu100%是錯誤的。

Java程式碼死迴圈等導致的cpu過高的分析

當系統負載大的時候會導致cpu過高,但不正確的程式碼也會導致cpu過高,比如死迴圈。我們如何從執行緒堆疊中找到死迴圈的執行緒呢?方法就是多次列印堆疊資訊,通過對比前後的執行緒,找到一致執行的執行緒。具體步驟如下:
  1. 通過上一篇文章介紹過的方法列印第一次堆疊資訊
  2. 等待一定時間,再次列印第二次堆疊資訊
  3. 預處理2次堆疊資訊,首先排除等待狀態的執行緒,這種狀態的執行緒不消耗cpu,前面已經講過。我們只關注runnable狀態的執行緒。
  4. 比較前後2次預處理後的執行緒,找出一段時間一直活躍的執行緒,如果2次堆疊資訊在同一個執行緒處於同樣的呼叫上下文,就列為重點懷疑物件。接下來結合程式碼邏輯檢查該執行緒執行的上下文所對應的程式碼是不是屬於應該長期執行的程式碼。
如果通過堆疊定位沒有發現可疑程式碼,那麼cpu高可能是不恰當的記憶體設定導致的頻繁gc,從而導致的cpu過高。

資源不足等導致的效能下降分析

這裡說的資源包括資料庫連線。大多數時候資源不足和效能瓶頸是同一類問題。當資源不足,就會導致資源的競爭,請求該資源的執行緒會被阻塞或者掛起,自然就導致效能下降。系統對於資源,一般的設計模式是:當需要資源的時候,獲取資源,當不需要的時候就把資源釋放掉。如果暫時沒有可用的資源,就等待在哪裡。如果有別的執行緒釋放資源,那麼等待的執行緒被notify,等待的執行緒獲得資源繼續執行。一般的資源設計都遵循wait/notify模式。如果資源不足,那麼有大量的執行緒等待資源,列印的執行緒堆疊如果具有這個特徵,說明該資源存在瓶頸。
  1. 大量的執行緒停在同樣的呼叫上下文上。
  2. 資源數量配置太小,如資料庫連線數,如果系統壓力過大,資源不足導致執行緒不能及時獲得資源而等待在那裡。
  3. 獲得資源的執行緒把持資源時間太久,導致資源不足。比如把一段和操作資料庫無關的程式碼放在獲取資料庫連線和釋放資料庫連線之間。
  4. 設計不合理導致的資源佔用太久,比如sql沒有加索引導致執行sql太慢。
  5. 資源用完後,沒有關閉導致資源洩漏或者減少。
資源不足往往表現出一個現象是系統越來越慢,並最終停止響應,超時。

多個鎖導致的鎖鏈分析

很多執行緒在等待不同的鎖,有的鎖競爭可能是由於另一鎖物件競爭導致,這時候需要找到根源。
  1. 看到有40多個執行緒在等待鎖0xbef17078,首先找到已經佔有這把鎖的執行緒thread-196
  2. 看到thread-196佔有鎖,0xbef17078,但又在等待鎖<0xbc7b4110>,那麼此時需要再找佔有<0xbc7b4110>這個鎖的執行緒,即thread-609。
  3. 那麼佔有鎖<0xbc7b4110>的執行緒是問題的根源,下一步就是查到底為什麼這個執行緒長時間佔有這個鎖。可能的原因是持有這把鎖的執行緒正在執行的程式碼效能比較低,導致佔有時間過長。