線程阻塞是我們在 java 多線程編程中經常遇到的問題。由于對后端有限資源的爭用以及過度同步等問題,經常會發現 Java dump 中某個資源(鎖對象)下有太多的線程處于等待狀態,這時候我們通常需要從以下三個方面去診斷這個問題:
這個鎖存在的目的是什么?有沒有可能去掉這個鎖或者縮小這個鎖保護的范圍,從而減少線程等待問題發生的幾率。
有哪些線程需要用到這個鎖,有沒有可能改用其它更好的替代方案。
當前哪個線程正在持有這個鎖,持有的時間是多長,有沒有可能縮短持有的時間。
下面通過實際測試中的dump文件來談談如何讀懂一個dump文件。
可以看到一共有4種線程的狀態,WAITING,RUNNABLE,TIMED_WAITING (sleeping),BLOCKED。線程阻塞就是BLOCKED。搜索文件中BLOCKED的部分,我們可以看到其中一個:
"[ACTIVE] ExecuteThread: '91' for queue: 'weblogic.kernel.Default (self-tuning)'" daemon prio=10 tid=0x00002aaae8181000 nid=0x4849 waiting for monitor entry [0x0000000047d4d000]
java.lang.Thread.State: BLOCKED (on object monitor)
* 線程名稱:
* 線程類型:daemon
* 優先級:10,默認是5
* jvm線程id:jvm內部線程的唯一標識,0x00002aaae8181000
* 對應系統線程id:和top命令查看的pid對應,不過一個是10進制,一個是16 進制。0x4849
* 線程狀態:BLOCKED
* 起始棧地址:
可以看到很多線程狀態都是BLOCKED,表示當前線程A正要進入一個同步塊,但是被另外一個線程B持有該鎖,于是需要等待B,釋放鎖才有機會重新獲取。
用一種輕松的方式來思考鎖,其實挺簡單的。數據庫中也存在這樣的鎖。為什么會有鎖,打個比喻,一個java對象就像一個大房子,大門永遠打開。房子里有很多房間(方法)。這些房間有上鎖的和不上鎖之分。房門口只放著一把鑰匙,這把鑰匙可以打開所有上鎖的房間。把所有想調用該對象方法的線程比喻成想進入這房子某個房間的人。試想如果只有一個人,肯定是不存在等待別人使用完鑰匙歸還的時候。如果要是很多人,勢必會需要等這把鑰匙歸還,等鑰匙還回來以后,就會有一個人優先得到鑰匙。
由此可以看到,鎖是不可避免的,要想正確獲取對象的狀態,或者修改對象的狀態,必須存在這樣一種lock。也就意味著,鎖的持有時間越久,對性能的影響也就越大,直接結果是大并發情況下,響應時間的延長?;氐揭郧暗膖hread dump,如果通過工具我們發現,線程阻塞比較頻繁,并且持續時間很久,不妨多次收集這種轉儲文件,可以有效的幫助我們分析問題。
我們剛才看到的轉儲文件就是XXX測試中捕獲到的,通過這些地方,我們發現了一些性能方面的問題。除了線程阻塞,我們分析dump文件還可以找到消耗CPU最多的地方。如果不是windows操作系統,可以通過top(top –H)或者topas命令來查看應用程序的線程信息及占用CPU的情況。找到排序第一位的pid值,按照我們前面解釋的,換算成16進制,然后在thread dump日志中搜索該數值nid就能找到耗費CPU的源代碼的具體位置,可以精確到行號。
另外還可以分析是否有很多thread struck在了I/O,例如:
"New I/O server worker #1-1" prio=10 tid=0x00000000423e0800 nid=0x5bfd runnable [0x00007f7d0a2f4000]
java.lang.Thread.State: RUNNABLE
at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)
at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:215)
或者是thread struck在數據庫,例如:
"[ACTIVE] ExecuteThread: '99' for queue: 'weblogic.kernel.Default (self-tuning)'" daemon prio=10 tid=0x00002aaae8190800 nid=0x4857 runnable [0x0000000048554000]
java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.read(SocketInputStream.java:129)
at oracle.net.ns.Packet.receive(Packet.java:293)
at oracle.net.ns.DataPacket.receive(DataPacket.java:92)
等地方,便于我們定位瓶頸原因。
原文轉自:http://blog.csdn.net/xuyubotest/article/details/8158241