站在巨人的肩膀上:http://sesame.iteye.com/blog/428012
http://jameswxx.iteye.com/blog/1041173
netstat -anp|grep **
我發現現在網上沒有好好講這個的,少數的幾篇文章都是大談自己的工具,卻沒把方法講清楚。我決定以我以前碰到的case爲例寫一篇來分享。到目前爲止,我認爲分析Java代碼問題的最有效的工具仍然是java thread dump。
原因:
- 任何操作系統平臺下都可以使用。
- 在多數情況下,可以在生產環境中使用。
- 和操作系統提供的工具相比,java thread dump給出的信息是直白的,直接對應到應用代碼。
- 它對被分析的系統干擾很小,因此能反應真實的問題。而其它很多profiling或Instrument工具本身對JVM運行有很大的干擾,經常不能暴露出真正的問題,而且這種工具不能用於生產系統。
我覺得在通常情況下分析Java虛擬機死鎖比分析內存泄漏要容易的多。因爲死鎖發生時,JVM通常處於掛起狀態(hang住了),thread dump可以給出靜態穩定的信息,查找死鎖只需要查找有問題的線程。而內存泄漏的問題卻很難界定,一個運行的JVM裏有無數對象存在,只有寫程序的人才知道哪些對象是垃圾,而哪些不是,而且對象的引用關係非常複雜,很難得到一份清晰的對象引用圖。
Java虛擬機死鎖發生時,從操作系統上觀察,虛擬機的CPU佔用率爲零,很快會從top或prstat的輸出中消失。這時你就可以收集thread dump了,Unix/Linux 下是kill -3 <JVM pid> ,在Windows下可以在JVM的console窗口上敲Ctrl-Break。根據不同的設置,thread dump會輸出到當前控制檯上或應用服務器的日誌裏。
拿到java thread dump後,你要做的就是查找"waiting for monitor entry"的thread,如果大量thread都在等待給同一個地址上鎖(因爲對於Java,一個對象只有一把鎖),這說明很可能死鎖發生了。比如:
"service-j2ee" prio=5 tid=0x024f1c28 nid=0x125 waiting for monitor entry
[62a3e000..62a3f690]
[27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: at
com.sun.enterprise.resource.IASNonSharedResourcePool.internalGetResource(IASNonS
haredResourcePool.java:625)
[27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: - waiting to
lock <0x965d8110> (a com.sun.enterprise.resource.IASNonSharedResourcePool)
[27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: at
com.sun.enterprise.resource.IASNonSharedResourcePool.getResource(IASNonSharedRes
ourcePool.java:520)
................
爲了確定問題,常常需要在隔兩分鐘後再次收集一次thread dump,如果得到的輸出相同,仍然是大量thread都在等待給同一個地址上鎖,那麼肯定是死鎖了。
如何找到當前持有鎖的線程是解決問題的關鍵。方法是搜索thread dump,查找"locked <0x965d8110>", 找到持有鎖的線程。
[27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: "Thread-20" daemon prio=5 tid=0x01394f18
nid=0x109 runnable [6716f000..6716fc28]
[27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: at
java.net.SocketInputStream.socketRead0(Native Method)
[27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: at
java.net.SocketInputStream.read(SocketInputStream.java:129)
[27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: at oracle.net.ns.Packet.receive(Unknown
Source)
[27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: at
oracle.net.ns.DataPacket.receive(Unknown Source)
[27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: at
oracle.net.ns.NetInputStream.getNextPacket(Unknown Source)
[27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: at
oracle.net.ns.NetInputStream.read(Unknown Source)
[27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: at
oracle.net.ns.NetInputStream.read(Unknown Source)
[27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: at
oracle.net.ns.NetInputStream.read(Unknown Source)
[27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: at
oracle.jdbc.ttc7.MAREngine.unmarshalUB1(MAREngine.java:929)
[27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: at
oracle.jdbc.ttc7.MAREngine.unmarshalSB1(MAREngine.java:893)
[27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: at
oracle.jdbc.ttc7.Ocommoncall.receive(Ocommoncall.java:106)
[27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: at
oracle.jdbc.ttc7.TTC7Protocol.logoff(TTC7Protocol.java:396)
[27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: - locked <0x954f47a0> (a
oracle.jdbc.ttc7.TTC7Protocol)
[27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: at
oracle.jdbc.driver.OracleConnection.close(OracleConnection.java:1518)
[27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: - locked <0x954f4520> (a
oracle.jdbc.driver.OracleConnection)
[27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: at
com.sun.enterprise.resource.JdbcUrlAllocator.destroyResource(JdbcUrlAllocator.java:122)
[27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: at
com.sun.enterprise.resource.IASNonSharedResourcePool.destroyResource(IASNonSharedResourcePool.java:8
72)
[27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: at
com.sun.enterprise.resource.IASNonSharedResourcePool.resizePool(IASNonSharedResourcePool.java:1086)
[27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: - locked <0x965d8110> (a
com.sun.enterprise.resource.IASNonSharedResourcePool)
[27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: at
com.sun.enterprise.resource.IASNonSharedResourcePool$Resizer.run(IASNonSharedResourcePool.java:1178)
[27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: at
java.util.TimerThread.mainLoop(Timer.java:432)
[27/Jun/2006:10:03:08] WARNING (26140): CORE3283: stderr: at
java.util.TimerThread.run(Timer.java:382)
在這個例子裏,持有鎖的線程在等待Oracle返回結果,卻始終等不到響應,因此發生了死鎖。
如果持有鎖的線程還在等待給另一個對象上鎖,那麼還是按上面的辦法順藤摸瓜,直到找到死鎖的根源爲止。
另外,在thread dump裏還會經常看到這樣的線程,它們是等待一個條件而主動放棄鎖的線程。例如:
"Thread-1" daemon prio=5 tid=0x014e97a8 nid=0x80 in Object.wait() [68c6f000..68c6fc28]
at java.lang.Object.wait(Native Method)
- waiting on <0x95b07178> (a java.util.LinkedList)
at com.iplanet.ias.util.collection.BlockingQueue.remove(BlockingQueue.java:258)
- locked <0x95b07178> (a java.util.LinkedList)
at com.iplanet.ias.util.threadpool.FastThreadPool$ThreadPoolThread.run(FastThreadPool.java:241)
at java.lang.Thread.run(Thread.java:534)
有時也會需要分析這類線程,尤其是線程等待的條件。
其實,Java thread dump並不只用於分析死鎖,其它Java應用運行時古怪的行爲都可以用thread dump來分析。
最後,在Java SE 5裏,增加了jstack的工具,也可以獲取thread dump。在Java SE 6裏, 通過jconsole的圖形化工具也可以方便地查找涉及object monitors 和java.util.concurrent.locks死鎖。
二:線程分析
2.1. JVM 線程
在線程中,有一些 JVM內部的後臺線程,來執行譬如垃圾回收,或者低內存的檢測等等任務,這些線程往往在 JVM初始化的時候就存在,如下所示:
- <SPAN style="FONT-SIZE: small"> "Low Memory Detector" daemon prio=10 tid=0x081465f8 nid=0x7 runnable [0x00000000..0x00000000]
- "CompilerThread0" daemon prio=10 tid=0x08143c58 nid=0x6 waiting on condition [0x00000000..0xfb5fd798]
- "Signal Dispatcher" daemon prio=10 tid=0x08142f08 nid=0x5 waiting on condition [0x00000000..0x00000000]
- "Finalizer" daemon prio=10 tid=0x08137ca0 nid=0x4 in Object.wait() [0xfbeed000..0xfbeeddb8]
- at java.lang.Object.wait(Native Method)
- - waiting on <0xef600848> (a java.lang.ref.ReferenceQueue$Lock)
- at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:116)
- - locked <0xef600848> (a java.lang.ref.ReferenceQueue$Lock)
- at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:132)
- at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:159)
- "Reference Handler" daemon prio=10 tid=0x081370f0 nid=0x3 in Object.wait() [0xfbf4a000..0xfbf4aa38]
- at java.lang.Object.wait(Native Method)
- - waiting on <0xef600758> (a java.lang.ref.Reference$Lock)
- at java.lang.Object.wait(Object.java:474)
- at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:116)
- - locked <0xef600758> (a java.lang.ref.Reference$Lock)
- "VM Thread" prio=10 tid=0x08134878 nid=0x2 runnable
- "VM Periodic Task Thread" prio=10 tid=0x08147768 nid=0x8 waiting on condition</SPAN>
- <span style="font-size: small;"><span style="font-size: x-small;"> "Low Memory Detector" daemon prio=10 tid=0x081465f8 nid=0x7 runnable [0x00000000..0x00000000]
- "CompilerThread0" daemon prio=10 tid=0x08143c58 nid=0x6 waiting on condition [0x00000000..0xfb5fd798]
- "Signal Dispatcher" daemon prio=10 tid=0x08142f08 nid=0x5 waiting on condition [0x00000000..0x00000000]
- "Finalizer" daemon prio=10 tid=0x08137ca0 nid=0x4 in Object.wait() [0xfbeed000..0xfbeeddb8]
- at java.lang.Object.wait(Native Method)
- - waiting on <0xef600848> (a java.lang.ref.ReferenceQueue$Lock)
- at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:116)
- - locked <0xef600848> (a java.lang.ref.ReferenceQueue$Lock)
- at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:132)
- at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:159)
- "Reference Handler" daemon prio=10 tid=0x081370f0 nid=0x3 in Object.wait() [0xfbf4a000..0xfbf4aa38]
- at java.lang.Object.wait(Native Method)
- - waiting on <0xef600758> (a java.lang.ref.Reference$Lock)
- at java.lang.Object.wait(Object.java:474)
- at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:116)
- - locked <0xef600758> (a java.lang.ref.Reference$Lock)
- "VM Thread" prio=10 tid=0x08134878 nid=0x2 runnable
- "VM Periodic Task Thread" prio=10 tid=0x08147768 nid=0x8 waiting on condition</span></span>
我們更多的是要觀察用戶級別的線程,如下所示:
- <SPAN style="FONT-SIZE: small"> "Thread-1" prio=10 tid=0x08223860 nid=0xa waiting on condition [0xef47a000..0xef47ac38]
- at java.lang.Thread.sleep(Native Method)
- at testthread.MySleepingThread.method2(MySleepingThread.java:53)
- - locked <0xef63d600> (a testthread.MySleepingThread)
- at testthread.MySleepingThread.run(MySleepingThread.java:35)
- at java.lang.Thread.run(Thread.java:595) </SPAN>
- <span style="font-size: small;"><span style="font-size: x-small;"> "Thread-1" prio=10 tid=0x08223860 nid=0xa waiting on condition [0xef47a000..0xef47ac38]
- at java.lang.Thread.sleep(Native Method)
- at testthread.MySleepingThread.method2(MySleepingThread.java:53)
- - locked <0xef63d600> (a testthread.MySleepingThread)
- at testthread.MySleepingThread.run(MySleepingThread.java:35)
- at java.lang.Thread.run(Thread.java:595) </span></span>
我們能看到:
* 線程的狀態: waiting on condition
* 線程的調用棧
* 線程的當前鎖住的資源: <0xef63d600>
2.2. 線程的狀態分析
正如我們剛看到的那樣,線程的狀態是一個重要的指標,它會顯示在線程 Stacktrace的頭一行結尾的地方。那麼線程常見的有哪些狀態呢?線程在什麼樣的情況下會進入這種狀態呢?我們能從中發現什麼線索?< /span>
1.1 Runnable
該狀態表示線程具備所有運行條件,在運行隊列中準備操作系統的調度,或者正在運行。
1.2 Wait on condition
該狀態出現在線程等待某個條件的發生。具體是什麼原因,可以結合 stacktrace來分析。最常見的情況是線程在等待網絡的讀寫,比如當網絡數據沒有準備好讀時,線程處於這種等待狀態,而一旦有數據準備好讀之後,線程會重新激活,讀取並處理數據。在 Java引入 NewIO之前,對於每個網絡連接,都有一個對應的線程來處理網絡的讀寫操作,即使沒有可讀寫的數據,線程仍然阻塞在讀寫操作上,這樣有可能造成資源浪費,而且給操作系統的線程調度也帶來壓力。在 NewIO裏採用了新的機制,編寫的服務器程序的性能和可擴展性都得到提高。
如果發現有大量的線程都在處在 Wait on condition,從線程 stack看, 正等待網絡讀寫,這可能是一個網絡瓶頸的徵兆。因爲網絡阻塞導致線程無法執行。一種情況是網絡非常忙,幾 乎消耗了所有的帶寬,仍然有大量數據等待網絡讀 寫;另一種情況也可能是網絡空閒,但由於路由等問題,導致包無法正常的到達。所以要結合系統的一些性能觀察工具來綜合分析,比如 netstat統計單位時間的發送包的數目,如果很明顯超過了所在網絡帶寬的限制 ; 觀察 cpu的利用率,如果系統態的 CPU時間,相對於用戶態的 CPU時間比例較高;如果程序運行在 Solaris 10平臺上,可以用 dtrace工具看系統調用的情況,如果觀察到 read/write的系統調用的次數或者運行時間遙遙領先;這些都指向由於網絡帶寬所限導致的網絡瓶頸。另外一種出現 Wait on condition的常見情況是該線程在 sleep,等待 sleep的時間到了時候,將被喚醒。
1.3 Waiting for monitor entry 和 in Object.wait()
在多線程的 JAVA程序中,實現線程之間的同步,就要說說 Monitor。 Monitor是 Java中用以實現線程之間的互斥與協作的主要手段,它可以看成是對象或者 Class的鎖。每一個對象都有,也僅有一個 monitor。每個 Monitor在某個時刻,只能被一個線程擁有,該線程就是 “Active Thread”,而其它線程都是 “Waiting Thread”,分別在兩個隊列 “ Entry Set”和 “Wait Set”裏面等候。在 “Entry Set”中等待的線程狀態是 “Waiting for monitor entry”,而在 “Wait Set”中等待的線程狀態是 “in Object.wait()”。
先看 “Entry Set”裏面的線程。我們稱被 synchronized保護起來的代碼段爲臨界區。當一個線程申請進入臨界區時,它就進入了 “Entry Set”隊列。對應的 code就像:
synchronized(obj) {
.........
}
這時有兩種可能性:
該 monitor不被其它線程擁有, Entry Set裏面也沒有其它等待線程。本線程即成爲相應類或者對象的 Monitor的 Owner,執行臨界區的代碼
該 monitor被其它線程擁有,本線程在 Entry Set隊列中等待。
在第一種情況下,線程將處於 “Runnable”的狀態,而第二種情況下,線程 DUMP會顯示處於 “waiting for monitor entry”。如下所示:
- "Thread-0" prio=10 tid=0x08222eb0 nid=0x9 waiting for monitor entry [0xf927b000..0xf927bdb8]
- at testthread.WaitThread.run(WaitThread.java:39)
- - waiting to lock <0xef63bf08> (a java.lang.Object)
- - locked <0xef63beb8> (a java.util.ArrayList)
- at java.lang.Thread.run(Thread.java:595)
- "Thread-0" prio=10 tid=0x08222eb0 nid=0x9 waiting for monitor entry [0xf927b000..0xf927bdb8]
- at testthread.WaitThread.run(WaitThread.java:39)
- - waiting to lock <0xef63bf08> (a java.lang.Object)
- - locked <0xef63beb8> (a java.util.ArrayList)
- at java.lang.Thread.run(Thread.java:595)
臨界區的設置,是爲了保證其內部的代碼執行的原子性和完整性。但是因爲臨界區在任何時間只允許線程串行通過,這 和我們多線程的程序的初衷是相反的。 如果在多線程的程序中,大量使用 synchronized,或者不適當的使用了它,會造成大量線程在臨界區的入口等待,造成系統的性能大幅下降。如果在線程 DUMP中發現了這個情況,應該審查源碼,改進程序。
現在我們再來看現在線程爲什麼會進入 “Wait Set”。當線程獲得了 Monitor,進入了臨界區之後,如果發現線程繼續運行的條件沒有滿足,它則調用對象(一般就是被 synchronized 的對象)的 wait() 方法,放棄了 Monitor,進入 “Wait Set”隊列。只有當別的線程在該對象上調用了 notify() 或者 notifyAll() , “ Wait Set”隊列中線程纔得到機會去競爭,但是隻有一個線程獲得對象的 Monitor,恢復到運行態。在 “Wait Set”中的線程, DUMP中表現爲: in Object.wait(),類似於:
- "Thread-1" prio=10 tid=0x08223250 nid=0xa in Object.wait() [0xef47a000..0xef47aa38]
- at java.lang.Object.wait(Native Method)
- - waiting on <0xef63beb8> (a java.util.ArrayList)
- at java.lang.Object.wait(Object.java:474)
- at testthread.MyWaitThread.run(MyWaitThread.java:40)
- - locked <0xef63beb8> (a java.util.ArrayList)
- at java.lang.Thread.run(Thread.java:595)
- "Thread-1" prio=10 tid=0x08223250 nid=0xa in Object.wait() [0xef47a000..0xef47aa38]
- at java.lang.Object.wait(Native Method)
- - waiting on <0xef63beb8> (a java.util.ArrayList)
- at java.lang.Object.wait(Object.java:474)
- at testthread.MyWaitThread.run(MyWaitThread.java:40)
- - locked <0xef63beb8> (a java.util.ArrayList)
- at java.lang.Thread.run(Thread.java:595)
仔細觀察上面的 DUMP信息,你會發現它有以下兩行:
- locked <0xef63beb8> (a java.util.ArrayList)
- waiting on <0xef63beb8> (a java.util.ArrayList)
這裏需要解釋一下,爲什麼先 lock了這個對象,然後又 waiting on同一個對象呢?讓我們看看這個線程對應的代碼:
- synchronized(obj) {
- .........
- obj.wait();
- .........
- }
- synchronized(obj) {
- .........
- obj.wait();
- .........
- }
線程的執行中,先用 synchronized 獲得了這個對象的 Monitor(對應於 locked <0xef63beb8> )。當執行到 obj.wait(), 線程即放棄了 Monitor的所有權,進入 “wait set”隊列(對應於 waiting on <0xef63beb8> )。
往往在你的程序中,會出現多個類似的線程,他們都有相似的 DUMP信息。這也可能是正常的。比如,在程序中,有多個服務線程,設計成從一個隊列裏面讀取請求數據。這個隊列就是 lock以及 waiting on的對象。當隊列爲空的時候,這些線程都會在這個隊列上等待,直到隊列有了數據,這些線程被 Notify,當然只有一個線程獲得了 lock,繼續執行,而其它線程繼續等待。
3. JDK 5.0 的 lock
上面我們提到如果 synchronized和 monitor機制運用不當,可能會造成多線程程序的性能問題。在 JDK 5.0中,引入了 Lock機制,從而使開發者能更靈活的開發高性能的併發多線程程序,可以替代以往 JDK中的 synchronized和 Monitor的 機制。但是,要注意的是,因爲 Lock類只是一個普通類, JVM無從得知 Lock對象的佔用情況,所以在線程 DUMP中,也不會包含關於 Lock的信息, 關於死鎖等問題,就不如用 synchronized的編程方式容易識別。
4.案例分析
1. 死鎖
在多線程程序的編寫中,如果不適當的運用同步機制,則有可能造成程序的死鎖,經常表現爲程序的停頓,或者不再響應用戶的請求。比如在下面這個示例中,是個較爲典型的死鎖情況:
- "Thread-1" prio=5 tid=0x00acc490 nid=0xe50 waiting for monitor entry [0x02d3f000
- ..0x02d3fd68]
- at deadlockthreads.TestThread.run(TestThread.java:31)
- - waiting to lock <0x22c19f18> (a java.lang.Object)
- - locked <0x22c19f20> (a java.lang.Object)
- "Thread-0" prio=5 tid=0x00accdb0 nid=0xdec waiting for monitor entry [0x02cff000
- ..0x02cff9e8]
- at deadlockthreads.TestThread.run(TestThread.java:31)
- - waiting to lock <0x22c19f20> (a java.lang.Object)
- - locked <0x22c19f18> (a java.lang.Object)
- "Thread-1" prio=5 tid=0x00acc490 nid=0xe50 waiting for monitor entry [0x02d3f000
- ..0x02d3fd68]
- at deadlockthreads.TestThread.run(TestThread.java:31)
- - waiting to lock <0x22c19f18> (a java.lang.Object)
- - locked <0x22c19f20> (a java.lang.Object)
- "Thread-0" prio=5 tid=0x00accdb0 nid=0xdec waiting for monitor entry [0x02cff000
- ..0x02cff9e8]
- at deadlockthreads.TestThread.run(TestThread.java:31)
- - waiting to lock <0x22c19f20> (a java.lang.Object)
- - locked <0x22c19f18> (a java.lang.Object)
在 JAVA 5中加強了對死鎖的檢測。線程 Dump中可以直接報告出 Java級別的死鎖,如下所示:
- Found one Java-level deadlock:
- =============================
- "Thread-1":
- waiting to lock monitor 0x0003f334 (object 0x22c19f18, a java.lang.Object),
- which is held by "Thread-0"
- "Thread-0":
- waiting to lock monitor 0x0003f314 (object 0x22c19f20, a java.lang.Object),
- which is held by "Thread-1"
- Found one Java-level deadlock:
- =============================
- "Thread-1":
- waiting to lock monitor 0x0003f334 (object 0x22c19f18, a java.lang.Object),
- which is held by "Thread-0"
- "Thread-0":
- waiting to lock monitor 0x0003f314 (object 0x22c19f20, a java.lang.Object),
- which is held by "Thread-1"
2. 熱鎖
熱鎖,也往往是導致系統性能瓶頸的主要因素。其表現特徵爲,由於多個線程對臨界區,或者鎖的競爭,可能出現:
* 頻繁的線程的上下文切換:從操作系統對線程的調度來看,當 線程在等待資源而阻塞的時候,操作系統會將之切換出來,放到等待的隊列,當線程獲得資源之後,調度算法會將這個線程切換進去,放到執行隊列中。
* 大量的系統調用:因爲線程的上下文切換,以及熱鎖的競爭,或 者臨界區的頻繁的進出,都可能導致大量的系統調用。
* 大部分 CPU開銷用在 “系統態 ”:線程上下文切換,和系統調用,都會導致 CPU在 “系統態 ”運行,換而言之,雖然系統很忙碌,但是 CPU用在 “用戶態 ”的比例較小,應用程序得不到充分的 CPU資源。
* 隨着 CPU數目的增多,系統的性能反而下降。因爲 CPU數目多,同 時運行的線程就越多,可能就會造成更頻繁的線程上下文切換和系統態的 CPU開銷,從而導致更糟糕的性能。
上面的描述,都是一個 scalability(可擴展性)很差的系統的表現。從整體的性能指標看,由於線程熱鎖的存在,程序的響應時間會變長,吞吐量會降低。< /span>
那麼,怎麼去了解 “熱鎖 ”出現在什麼地方呢?一個重要的方法還是結合操作系統的各種工具觀察系統資源使用狀況,以及收集 Java線程的 DUMP信息,看線程都阻塞在什麼方法上,瞭解原因,才能找到對應的解決方法。
我們曾經遇到過這樣的例子,程序運行時,出現了以上指出的各種現象,通過觀察操作系統的資源使用統計信息,以及線程 DUMP信息,確定了程序中熱鎖的存在,並發現大多數的線程狀態都是 Waiting for monitor entry或者 Wait on monitor,且是阻塞在壓縮和解壓縮的方法上。後來採用第三方的壓縮包 javalib替代 JDK自帶的壓縮包後,系統的性能提高了幾倍。