在性能調優的時候,apm監控可以看到一直卡在mysql的DruidDataSource.getConnection()
這個時候數據庫的資源使用情況cpu、內存、網絡均正常,dubbo容器的cpu等資源也正常
抓取線程快照查看線程爲何一直處於數據庫等getConnection
獲取線程快照
在服務端連續三次將線程快照down下來,命令如下
jstack pid > 文件名.txt
- pid爲需要抓取的進程數
- 抓線程快照,必須是在加壓,且壓到出現問題時抓取,連續抓取3~5次
有兩種方式可以分析線程快照
- 直接使用文本的方式
- 使用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
- 線程狀態
waiting for monitor entry
:
意味着在等待進入一個臨界區 ,所以它在"Entry Set"隊列中等待
此時線程狀態一般都是 Blocked:java.lang.Thread.State: BLOCKED (on object monitor)
如果大量線程在這個狀態,可能是一個全局鎖阻塞住了大量線程
如果短時間內打印的 thread dump 文件反映,隨着時間流逝,waiting for monitor entry 的線程越來越多,沒有減少的趨勢,可能意味着某些線程在臨界區裏呆的時間太長了,以至於越來越多新線程遲遲無法進入臨界區
- 線程狀態
waiting on condition
:
說明它在等待另一個條件的發生,來把自己喚醒,或者乾脆它是調用了 sleep(N)
此時線程狀態大致爲以下幾種:
java.lang.Thread.State: WAITING (parking):一直等那個條件發生
java.lang.Thread.State: TIMED_WAITING (parking或sleeping):定時的,那個條件不到來,也將定時喚醒自己
如果大量線程在在這個狀態,可能是它們又跑去獲取第三方資源,尤其是第三方網絡資源,遲遲獲取不到Response,導致大量線程進入等待狀態
所以如果發現有大量的線程都處在 Wait on condition,從線程堆棧看,正等待網絡讀寫,這可能是一個網絡瓶頸的徵兆,因爲網絡阻塞導致線程無法執行
- 線程狀態
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線程,垃圾回收線程等