ThreadDump分析筆記(二) 分析堆棧

最近在進行一些系統問題追蹤分析,順便翻了翻以前的筆記和書籍,突然發現了以前寫的 ThreadDump分析筆記(一) 解讀堆棧
,阿哈哈哈,好吧,這次順便補個二。線程堆棧是我們排查問題常用的一種數據,具有很高的價值。但是線程堆棧打印出來是賊拉多的,上一次已經把基礎概念說了,今兒就來叨叨下應該怎麼看這玩意。

##0x01 線程堆棧可以幹嘛

線程堆棧主要是反映了當前系統線程正在幹什麼,堆棧可以從幾個角度來分析

局部堆棧信息(就是每個線程的詳細棧信息)

記錄了每一個線程調用的層次關係,當前線程正在執行哪個函數,當前的線程狀態是什麼, 當前線程持有哪些鎖,在等待哪些鎖。

堆棧的統計信息(每個線程棧都會有一個統計數據)

可以看出鎖的競爭狀態,是不是有很多鎖競爭,是不是有死鎖,大部分線程處於什麼狀態,線程總數等。

多次堆棧對比信息

多次堆棧的好處就是可以形成對比,通過對比統計,你就纔會發現問題所在。

例如每次打印堆棧這個線程一直處於同樣的上下文中(比如一直在執行某一段代碼),那就要考慮是否合理或者有瓶頸了。

例如某一個線程在多次堆棧中都在等待一個鎖,那就要看看這個鎖爲什麼不釋放。

線程棧在問題分析上原理都是類似的,我們搞幾個常見的問題,這幾個問題基本就涵蓋了生產環境經常發生的現象

##0x02 死循環分析(多次堆棧對比信息)

代碼出了BUG導致死循環,會明顯拉高CPU,這時候就可以通過上面所講的多次對比的方法進行可疑線程尋找,具體步驟如下:

  • 使用jstack pid > pid.dump 導出第一次線程棧文件
  • 等幾十秒在重讀導出第二個dump 文件
  • 對比兩個文件,排除掉WAITING和SLEEPING 狀態的數據,因爲這兩種狀態是不消耗CPU的
  • 找出這一段時間內一直活躍的線程,如果相同的線程處於相同的上下文,那這個就要作爲重點排查對象。結合代碼來查看線程所執行的上下文是否屬於應該長期運行的代碼。

如果對比完後沒有發現可以線程,那就需要考慮下CPU過高是否是因爲內存設置不合理,比如是否沒有設置元空間大小而導致頻繁full gc,是否新生代太小導致大批量對象晉升導致的頻繁GC,從而導致CPU過高。

0x03 CPU消耗分析

死循環會導致CPU 過高,這是一種BUG。但在大多數情況下,有很多CPU密集型的邏輯計算也會拉高CPU。這些問題分析起來可能會微微不太方便,因爲單純通過線程棧這一種信息是不夠了,我們需要藉助linux的一些小命令。

都知道我們的應用是以進程的方式運行在linux中,每個進程內又包含了很多的線程,在java 中我們使用的是。在linux中,我們可以使用top 命令查看cpu 佔比。而需要知道的是linux中的本地線程(LWPID)是和java 線程一一對應的,在top顯示的是10進制的線程ID,而在線程棧中的nid用16禁制來表示。 (其中涉及的知識點 內核線程KLT、輕量級進程LWP、用戶線程UT可以看一下)

具體分析步驟如下:

  • top -Hp pid 來獲取當前該進程內所有線程的統計數據,PID就是線程ID(LWPID),這個ID對應這java thread dump 內的nid(native thread id),nid 是16進制的,所有如圖PID=221 就等於 nid=0xdd,可以使用printf “%x\n” 221 來轉換ID

top -Hp PID

  • 在java 線程棧中搜索nid=0xdd

如果找到的對應的線程PID正在執行java代碼,那說明該java代碼導致cpu過高,如:

    "Thread-444" prio=1 tid=0xa4853568 nid=0xdd runnable [0xafcf7000..0xafcf8680] 
        //當前正在執行的代碼是純Java代碼
         at org.apache.commons.collections.ReferenceMap.getEntry(Unknown Source) 
        at org.apache.commons.collections.ReferenceMap.get(Unknown Source)

如果找到找到的PID 正在執行行Native code ,那說明導致CPU過高問題代碼在JNI調用中,如:

    "Thread-609" prio=5 tid=0x01583d88 nid=0xdd runnable [7a680000..7a6819c0] 
        //CheckLicense是Native方法,說明導致CPU過高的問題代碼在本地代碼中。
         at meetingmgr.conferencemgr.Operation.CheckLicense(Native method) 
        at meetingmgr.MeetingAdapter.prolongMeeting(MeetingAdapter.java:171)    

這種定位方式基本可以一次命中問題所在,不管什麼原因導致的CPU 過高都可以排查出來,這種方式消耗小,生產環境非常是適合使用。

##0x04 資源不足分析

系統資源不足猶如一條高速公路中間一截變成了羊腸小道,所有就算其他地方在寬也沒有用,這就是木桶效應。比如dubbo 鏈路中某一服務提供者數據庫連接不夠了,那就會產生資源競爭,請求該資源的線程都會被阻塞或者掛起,系統設計差一點的就有可能造成系統雪崩。所以說系統資源不足和系統瓶頸是同一類問題。

如果系統資源不足,就會有大量的線程等待資源,打印線程棧就會發現這個特徵,那就說明該資源就是系統瓶頸。此時打印線程棧,會發現大量線程停在同一個方法調用上。

例如:堆棧大量的http-1-Processor線程都停止在org.apache.commons.pool.impl .GenericObjectPool.borrowObject上面,說明大量的線程正在等待該資源。這就說明了該系統 資源是瓶頸。

 http-8082-Processor84" daemon prio=10 tid=0x0887c000 nid=0x5663 in Object.wait() 
       java.lang.Thread.State: WAITING (on object monitor)
       at java.lang.Object.wait(Object.java:485) 
       at org.apache.commons.pool.impl.GenericObjectPool.borrowObject(Unknown Source)
       - locked <0x75132118> (a org.apache.commons.dbcp.AbandonedObjectPool)
       at org.apache.commons.dbcp.AbandonedObjectPool.borrowObject()
       - locked <0x75132118> (a org.apache.commons.dbcp.AbandonedObjectPool) 
       at org.apache.commons.dbcp.PoolingDataSource.getConnection()
       at org.apache.commons.dbcp.BasicDataSource.getConnection(BasicDataSource.java:312)  
      at dbAccess.FailSafeConnectionPool.getConnection(FailSafeConnectionPool.java:162) 
      at servlets.ControllerServlet.doGet(ObisControllerServlet.java:93)

導致資源不足的原因結合堆棧信息判斷有可能如下幾個方面:

  • 連接池配置不夠導致的

  • 系統當前併發突然增大導致資源耗盡

  • 資源佔用太久不釋放導致耗盡,SQL語句不恰當沒有命中索引,或者沒有索引導致的數據庫訪問太慢

  • 資源用完後,在某種異常情況下,沒有關閉或者回池,導致可用資源泄漏或者減少,從而導致資源競爭

資源不足在系統的表現上來看就是性能問題,往往系統會越來越慢,最後導致不能正常響應。

0x05 鎖鏈分析

先說一點,死鎖的兩個線程或多個線程是不消耗CPU的。只有死循環,並且循環內代碼都是CPU密集型操作,纔有可能造成CPU100%的使用率,像socket或者數據庫等IO操作是不怎麼消耗CPU的。

鎖鏈不是死鎖,有的時候打印出的堆棧,很多線程在等待不同的鎖,有的鎖競爭可能是由於另一個鎖對 象競爭導致,這時候要找到根源到底在哪。如下堆棧信息,等待鎖0xbef17078的線程有40多個,等待0xbc7b4110有10多個。

"Thread-1021" prio=5 tid=0x0164eac0 nid=0x41e waiting for monitor entry[...] 
at meetingmgr.timer.OnMeetingExec.monitorExOverNotify(OnMeetingExec.java:262)
- waiting to lock <0xbef17078> (a [B) //等待鎖0xbef17078  
at meetingmgr.timer.OnMeetingExec.execute(OnMeetingExec.java:189)  
at util.threadpool.RunnableWrapper.run(RunnableWrapper.java:131)  
at EDU.oswego.cs.dl.util.concurrent.PooledExecutor$Worker.run(...)  
at java.lang.Thread.run(Thread.java:534)  

"Thread-196" prio=5 tid=0x01054830 nid=0xe1 waiting for monitor entry[...]  
at meetingmgr.conferencemgr.Operation.prolongResource(Operation.java:474)  
- waiting to lock <0xbc7b4110> (a [B) //等待鎖0xbc7b4110  
at meetingmgr.MeetingAdapter.prolongMeeting(MeetingAdapter.java:171)  
at meetingmgr.FacadeForCallBean.applyProlongMeeting(FacadeFroCallBean.java:190) 
at meetingmgr.timer.OnMeetingExec.monitorExOverNotify(OnMeetingExec.java:278) 
- locked <0xbef17078> (a [B) //佔有鎖0xbef17078  
at meetingmgr.timer.OnMeetingExec.execute(OnMeetingExec.java:189) 
at util.threadpool.RunnableWrapper.run(RunnableWrapper.java:131)  
at EDU.oswego.cs.dl.util.concurrent.PooledExecutor$Worker.run(...)  
at java.lang.Thread.run(Thread.java:534)  


"Thread-609" prio=5 tid=0x01583d88 nid=0x280 runnable [7a680000..7a6819c0] 
at java.net.SocketInputStream.socketRead0(Native method)  
at oracle.jdbc.ttc7.Oall7.recieve(Oall7.java:369) 
at net.sf.hiberante.impl.QueryImpl.list(QueryImpl.java:39) | 
at meetingmgr.conferencemgr.Operation.prolongResource(Operation.java:481) 
- locked <0xbc7b4110> (a [B) //佔有鎖0xbc7b4110  
at meetingmgr.MeetingAdapter.prolongMeeting(MeetingAdapter.java:171) 
at meetingmgr.timer.OnMeetingExec.execute(OnMeetingExec.java:189) 
at util.threadpool.RunnableWrapper.run(RunnableWrapper.java:131) 
at EDU.oswego.cs.dl.util.concurrent.PooledExecutor$Worker.run(...)
at java.lang.Thread.run(Thread.java:534)
  1. 看到有40多個線程再等待鎖0xbef17078,首先找到已經佔有這把鎖的線程,即"Thread-196"

  2. 看到"Thread-196"佔有了鎖0xbef17078,但又在等待鎖<0xbc7b4110>,那麼此時需要再找出 佔有<0xbc7b4110>這個鎖的線程,即"Thread-609"

  3. 那麼佔有鎖<0xbc7b4110>的線程是問題的根源,下一步就要查到底爲什麼這個線程長時 間佔有這個鎖。可能的原因是持有這把鎖的線程正在執行的代碼性能比較低,導致鎖佔用時間過長。

0x06 線程堆棧不能分析什麼

線程堆棧定位問題,只能定位在當前線程上留下痕跡的問題,如死鎖、掛起等因爲鎖導致的系統性能問題。但對於不留痕跡的問題,就無能爲力了。比如:

  • 線程泄漏,爲何沒有被合理的回收
  • 線程併發導致的BUG,這種問題在線程堆棧中沒有任何痕跡,所以這種問題線程堆 棧無法提供任何幫助。
  • 數據庫鎖表的問題,問題往往是由於某個事務沒有提交/回 滾,但這些信息無法在堆棧中表現出來,所以堆棧分析這類問題毫無幫助。

角兒旮旯

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