【性能調優】線程快照分析

在性能調優的時候,apm監控可以看到一直卡在mysql的DruidDataSource.getConnection()
在這裏插入圖片描述
這個時候數據庫的資源使用情況cpu、內存、網絡均正常,dubbo容器的cpu等資源也正常
抓取線程快照查看線程爲何一直處於數據庫等getConnection

獲取線程快照

在服務端連續三次將線程快照down下來,命令如下

jstack pid > 文件名.txt
  1. pid爲需要抓取的進程數
  2. 抓線程快照,必須是在加壓,且壓到出現問題時抓取,連續抓取3~5次

有兩種方式可以分析線程快照

  1. 直接使用文本的方式
  2. 使用jdc工具分析 下載鏈接

下面針對兩種方式說明具體的分析方式

文本查看方式

一、查看全局鎖
需要關注的就是看有沒有全局鎖鎖住其他線程
直接用文本編輯器打開,搜索關鍵字“- waiting to lock”,統計一下數量
在這裏插入圖片描述
如果很多線程在這個狀態,說明有全局鎖堵住線程了,比如以上線程有11個在等待鎖

二、查看持有幾把鎖
看有幾把鎖,就是看- waiting to lock後面的鎖地址有幾個不同的

- waiting to lock <0x00000007004d9ee0> (a java.lang.Class for javax.crypto.JceSecurity)
- waiting to lock <0x00000007004d9ee0> (a java.lang.Class for javax.crypto.JceSecurity)
- waiting to lock <0x00000007004d9ee0> (a java.lang.Class for javax.crypto.JceSecurity)
- waiting to lock <0x00000007004d9ee0> (a java.lang.Class for javax.crypto.JceSecurity)
- waiting to lock <0x00000007004d9ee0> (a java.lang.Class for javax.crypto.JceSecurity)
- waiting to lock <0x00000007004d9ee0> (a java.lang.Class for javax.crypto.JceSecurity)
- waiting to lock <0x00000007004d9ee0> (a java.lang.Class for javax.crypto.JceSecurity)
- waiting to lock <0x00000007004d9ee0> (a java.lang.Class for javax.crypto.JceSecurity)
- waiting to lock <0x00000007004d9ee0> (a java.lang.Class for javax.crypto.JceSecurity)
- waiting to lock <0x00000007004d9ee0> (a java.lang.Class for javax.crypto.JceSecurity)
- waiting to lock <0x00000007004d9ee0> (a java.lang.Class for javax.crypto.JceSecurity)

可以看到這11個線程只有1把鎖

三、查看鎖是哪個線程持有
直接複製鎖地址在線程快照文件中全局搜索,找到 - locked + 鎖地址(比如locked <0x00000007004d9ee0>),即可找到鎖的持有線程

四、查看持有鎖線程具體執行的事務
在這裏插入圖片描述
找到線程鎖持有的線程,查看這個線程的堆棧看到它在幹嘛,從堆棧上找程序實現代碼,這樣就可以知道這把鎖是什麼場景加的,上面的堆棧中可以看到線程在查db後加密

jac工具查看

一、查看統計視圖
在這裏插入圖片描述
二、waiting on monitor的線程
如果當前有全局鎖,這裏一定很多在是等待。如果是在等鎖,對應的狀態是BLOCKED

  1. 線程狀態waiting for monitor entry
    意味着在等待進入一個臨界區 ,所以它在"Entry Set"隊列中等待
    此時線程狀態一般都是 Blocked:java.lang.Thread.State: BLOCKED (on object monitor)

如果大量線程在這個狀態,可能是一個全局鎖阻塞住了大量線程
如果短時間內打印的 thread dump 文件反映,隨着時間流逝,waiting for monitor entry 的線程越來越多,沒有減少的趨勢,可能意味着某些線程在臨界區裏呆的時間太長了,以至於越來越多新線程遲遲無法進入臨界區

  1. 線程狀態waiting on condition
    說明它在等待另一個條件的發生,來把自己喚醒,或者乾脆它是調用了 sleep(N)
    此時線程狀態大致爲以下幾種:
    java.lang.Thread.State: WAITING (parking):一直等那個條件發生
    java.lang.Thread.State: TIMED_WAITING (parking或sleeping):定時的,那個條件不到來,也將定時喚醒自己

如果大量線程在在這個狀態,可能是它們又跑去獲取第三方資源,尤其是第三方網絡資源,遲遲獲取不到Response,導致大量線程進入等待狀態
所以如果發現有大量的線程都處在 Wait on condition,從線程堆棧看,正等待網絡讀寫,這可能是一個網絡瓶頸的徵兆,因爲網絡阻塞導致線程無法執行

  1. 線程狀態in Object.wait()
    說明它獲得了監視器之後,又調用了 java.lang.Object.wait() 方法
    每個 Monitor在某個時刻,只能被一個線程擁有,該線程就是 “Active Thread”,而其它線程都是 “Waiting Thread”,分別在兩個隊列 “ Entry Set”和 “Wait Set”裏面等候。在 “Entry Set”中等待的線程狀態是 “Waiting for monitor entry”,而在 “Wait Set”中等待的線程狀態是 “in Object.wait()”
    當線程獲得了 Monitor,如果發現線程繼續運行的條件沒有滿足,它則調用對象(一般就是被 synchronized的對象)的 wait() 方法,放棄了 Monitor,進入 “Wait Set”隊列
    此時線程狀態大致爲以下幾種:
    java.lang.Thread.State: TIMED_WAITING (on object monitor);
    java.lang.Thread.State: WAITING (on object monitor);
    一般都是RMI相關線程(RMI RenewClean、 GC Daemon、RMI Reaper),GC線程(Finalizer),引用對象垃圾回收線程(Reference Handler)等系統線程處於這種狀態

三、如果很多卡在waiting on monitor狀態,查看鎖的持有者在幹嘛,使用Analysis -> Monitor Detail
在這裏插入圖片描述
如果有鎖就會出現如下鎖狀態列表:
在這裏插入圖片描述
可以看到線程thread - 507 持有鎖,其他11個線程在等待鎖

四、沒鎖的情況時,查看waitting on condition狀態,有很多種場景會使得代碼處於這種狀態,需要一個個線程去看他們在幹嘛,這個沒啥規律

五、查看Object.wait狀態
對於應用,很多時候處於這種狀態的是在等外部響應,比如等外部接口返回,如果很多線程處於這種狀態,要重點看堆棧在幹嘛,就可以大概判斷你的程序堵在哪了
反而,你不用太過於關注Runnabe狀態的線程,也有可能有處於死鎖的:Deadlock,但這種極少

最後說明,通過線程快照不單是看程序有啥問題,還可以看出程序當前是不是到達極限,但這個更多依賴經驗,不同應用的線程類型不一樣,不同類型的線程處於不同狀態的意義得搞清楚,比如數據連接線程,jedis線程,垃圾回收線程等

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章