線程堆棧:
線程堆棧也稱線程調用堆棧,是虛擬機中線程(包括鎖)狀態的一個瞬間快照,即系統在某一個時刻所有線程的運行狀態,包括每一個線程的調用堆棧,鎖的持有情況。雖然不同的虛擬機打印出來的格式有些不同,但是線程堆棧的信息都包含:
- 線程名字,id,線程的數量等。
- 線程的運行狀態,鎖的狀態(鎖被哪個線程持有,哪個線程在等待鎖等)
- 調用堆棧(即函數的調用層次關系)調用堆棧包含完整的類名,所執行的方法,源代碼的行數。
借助堆棧信息可以幫助分析很多問題,如線程死鎖,鎖爭用,死循環,識別耗時操作等等。在多線程場合下的穩定性問題分析和性能問題分析,線程堆棧分析濕最有效的方法,在多數情況下,無需對系統了解就可以進行相應的分析。
由於線程堆棧是系統某個時刻的線程運行狀況(即瞬間快照),對於歷史痕跡無法追蹤。只能結合日志分析。總的來說線程堆棧是多線程類應用程序非功能型問題定位的最有效手段,最善於分析如下類型問題:
- 系統無緣無故的cpu過高
- 系統掛起,無響應
- 系統運行越來越慢
- 性能瓶頸(如無法充分利用cpu等)
- 線程死鎖,死循環等
- 由於線程數量太多導致的內存溢出(如無法創建線程等)
死鎖:
一個 web 服務器使用幾十到幾百個線程來處理大量並發用戶,如果一個或多個線程使用相同的資源,線程之間的競爭就不可避免了,並且可能會發生死鎖。
死鎖是線程競爭的一個特殊狀態,一個或是多個線程在等待其他線程完成它們的任務。
線程狀態:
- NEW:線程剛被創建,但是還沒有被處理。
- RUNNABLE:當調用thread.start()后,線程變成為Runnable狀態。只要得到CPU,就可以執行
- Running:線程正在執行
- BLOCKED:該線程正在等待另外的不同的線程釋放鎖,阻塞狀態
- WAITING:該線程正在等待,通過使用了 wait, join 或者是 park 方法
- TIMED_WAITING:該線程正在等待,通過使用了 sleep, wait, join 或者是 park 方法,與Waiting的區別在於Timed_Waiting的等待有時間限制
- Dead:線程執行完畢,或者拋出了未捕獲的異常之后,會進入dead狀態,表示該線程結束
輸出堆棧:
通常將堆棧信息重定向到一個文件中,
命令 : $jstack [option] pid >> 文件
堆棧解讀:
只需要關注java用戶線程,其他由虛擬機自動創建的,在實際分析中可以暫時忽略:
一般來講,我們只需要關注處於RUNNABLE狀態的線程並且包含com.xxx的一行(從下往上看)

"http-nio-40243-exec-9" #125 daemon prio=5 os_prio=0 tid=0x00007fd234d9f800 nid=0x7114 runnable [0x00007fd194e87000] java.lang.Thread.State: RUNNABLE at java.net.SocketInputStream.socketRead0(Native Method) at java.net.SocketInputStream.socketRead(SocketInputStream.java:116) at java.net.SocketInputStream.read(SocketInputStream.java:170) at java.net.SocketInputStream.read(SocketInputStream.java:141) at okio.Okio$2.read(Okio.java:139) at okio.AsyncTimeout$2.read(AsyncTimeout.java:237) at okio.RealBufferedSource.indexOf(RealBufferedSource.java:345) at okio.RealBufferedSource.readUtf8LineStrict(RealBufferedSource.java:217) at okio.RealBufferedSource.readUtf8LineStrict(RealBufferedSource.java:211) at okhttp3.internal.http1.Http1Codec.readResponseHeaders(Http1Codec.java:189) at okhttp3.internal.http.CallServerInterceptor.intercept(CallServerInterceptor.java:75) at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92) at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.java:45) at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:92) xxxxxxxxxxxxxxxx at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:103) at com.sun.proxy.$Proxy176.getCampaignShopCouponLimitRule(Unknown Source) at com.yazuo.kbpos.scan.dinner.service.impl.CouponsServiceImpl.queryCouponLimitRuleList(CouponsServiceImpl.java:478) at com.yazuo.kbpos.scan.dinner.controller.CouponsController.queryCouponLimitRuleList(CouponsController.java:115) at com.yazuo.kbpos.scan.dinner.controller.CouponsController$$FastClassBySpringCGLIB$$95e09cc2.invoke(<generated>) at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:738
線程解讀:
- 線程名,“http-nio-40243-exec-10”
- 線程屬性(如果是Daemon線程,會有Daemon標識,否則,什么都沒有)
- 線程優先級,prio
- java線程對應的本地線程的優先級os_prio
- java線程標識tid
- java線程對應的本地線程標識nid
- 線程狀態(運行中、等待等)
- 線程的棧信息
- 線程鎖信息
線程堆棧里面的最直觀的信息是當前線程的調用上下文,即從哪個函數調用到哪個函數(從下往上看),正執行到哪一類的哪一行,借助這些信息,我們就對當前系統正在做什么一目了然。
示例中基本可以定位到出現問題的方法為getCampaignShopCouponLimitRule
BLOCKED狀態示例:
情況一:直接打印出代碼類名,這種情況就很好定位是代碼的問題,優化代碼即可
情況二:"C2 CompilerThread*"開頭的堆棧信息,此信息表示java編譯的線程,說明java編譯器編譯過於頻繁,tomcat程序則加上參數 -XX:CICompilerCount=4 此設置表示改變編譯器線程為4線程並行處理
情況三:"catalina-exec-***"開頭的堆棧信息,此信息表示程序正常處理的線程,則表示程序本身有待優化
基本思路只需要關注處於RUNNABLE狀態的線程並且包含com.xxx的一行即可(從下往上看)
2、CPU正常,但是系統性能比較差,同時可能繁忙線程高
此時需要多次dump,然后找出 BLOCKED
狀態的線程列表。
總結:
如果說系統慢,那么要特別關注Blocked,Waiting on condition
如果說系統的cpu耗的高,那么肯定是線程執行有死循環,那么此時要關注下Runable狀態。
備注:
1.處於RUNNABLE狀態的線程不一定會消耗cpu,像socket IO操作,線程正在從網絡上讀取數據,盡管線程狀態RUNNABLE,但實際上網絡io,線程絕大多數時間是被掛起的,只有當數據到達后,線程才會被喚起,掛起發生在本地代碼(native)中,虛擬機無法知道真正的線程狀態,因此一概顯示為RUNNABLE。如果是純java運算代碼,則消耗cpu,如果網絡io,很少消耗cpu。
2.處於timed_waiting,waiting狀態的線程一定不消耗cpu.