Java線程堆棧是一個運行中的Java應用程式的所有線程的一個快照。它會顯示一些像當前的堆疊追蹤、狀態以及線程名稱之類的資訊。線程列表中包括由JVM本身建立的線程(負責垃圾收集、訊號處理等管理工作)和由應用程式建立的線程。
通過給JVM發送一個SIGQUIT訊號,您可以得到一個線程堆。在Unix作業系統(Solaris/Linux/HP-Unix等)中,通過kill-3<pid>命令可以得到線程堆,(在啟動指令碼中將輸出重新導向到檔案中是一個很好的習慣,start.sh>trace.log 2>&1)。在Windows作業系統中,您可以在命令視窗鍵入ctrl-break得到線程堆。線程堆會輸出到JVM的stdout或者stderr。輸出出線程堆之後,應用程式繼續正常運行。當您給JVM發送SIGQUIT訊號時,JVM的訊號處理器會通過輸出線程堆來響應這一訊號。當程式啟動並執行時候,您可以在任何點得到線程堆。
線程堆的一個例子
下面顯示的就是一個使用Sun JVM 1.4.1的單線程應用程式中的線程堆的例子。main線程是主應用程式線程。所有其他的線程都是由JVM建立的,負責完成一些管理工作。當分析應用程式級的問題時,我們通常只關心應用程式線程。下面,我們來分析清單1中main線程的堆疊追蹤。
"main" prio=5 tid=0x002358B8 nid=0x7f8 runnable [6f000..6fc40]
at test.method1(test.java:10)
at test.main(test.java:5)
從這個代碼片斷中您可以看到,一個線程堆疊追蹤有一個名稱、線程優先順序(prio=5)、狀態(runnable)、原始碼行號,以及方法調用。從這個堆疊追蹤中可以得到如下結論:main線程執行test類的method1方法中的一些代碼。而對method1方法的調用是由同一個類的main方法完成的。您也可以看到那些方法中確切的原始碼行號。
在從一些更複雜的情況去分析線程堆之前,我們先來討論那些可以線上程堆中看到的線程的不同狀態以及它們的意義。
· runnable:當獲得CPU的使用權時就可以運行或準備好啟動並執行狀態。
"Thread-5" daemon prio=6 tid=0x00aa4b88 nid=0x3f0 runnable [0x02def000..0x02def9e8]
at java.io.WinNTFileSystem.getBooleanAttributes(Native Method)
at java.io.File.exists(Unknown Source)
at com.xmliu.threadheap.ThreadPool$TaskQueue.getTask(ThreadPool.java:66)
- locked <0x230c39c8> (a com.xmliu.threadheap.ThreadPool$TaskQueue)
at com.xmliu.threadheap.ThreadPool$Worker.run(ThreadPool.java:41)
Thread-5" 正在正在執行ThreadPool$Worker.run()方法,並且獲得了鎖<0x230c39c8> 。
· waiting for monitor entry:等待獲得鎖。
"Thread-4" daemon prio=6 tid=0x00aae3d0 nid=0x9c0 waiting for monitor entry [0x02daf000..0x02dafa68]
at com.xmliu.threadheap.ThreadPool$TaskQueue.getTask(ThreadPool.java:59)
- waiting to lock <0x230c39c8> (a com.xmliu.threadheap.ThreadPool$TaskQueue)
at com.xmliu.threadheap.ThreadPool$Worker.run(ThreadPool.java:41)
"Thread-4" 在執行ThreadPool$Worker.run()時需要獲得鎖<0x230c39c8>(waiting to lock <0x230c39c8>),正在等待其他線程釋放。
· waiting on condition:這種情況會出現在對Thread對象調用任何sleep() 方法。
"Thread-2" daemon prio=6 tid=0x00a952b8 nid=0xea4 waiting on condition [0x02d2f000..0x02d2fd68]
at java.lang.Thread.sleep(Native Method)
at com.xmliu.threadheap.ThreadPool$TaskQueue.getTask(ThreadPool.java:61)
- locked <0x22c01288> (a com.xmliu.threadheap.ThreadPool$TaskQueue)
at com.xmliu.threadheap.ThreadPool$Worker.run(ThreadPool.java:40)
"Thread-2" 調用了sleep()方法。
· Object.wait():這種情況出現在對Thread對象調用了任何wait()方法,釋放了已獲得的鎖並等待其他線程的喚醒或等待逾時後重新競爭鎖。
"Thread-1" daemon prio=6 tid=0x00a94cc8 nid=0xf0c in Object.wait() [0x02cef000..0x02cef9e8]
at java.lang.Object.wait(Native Method)
- waiting on <0x22c01288> (a com.xmliu.threadheap.ThreadPool$TaskQueue)
at com.xmliu.threadheap.ThreadPool$TaskQueue.getTask(ThreadPool.java:61)
- locked <0x22c01288> (a com.xmliu.threadheap.ThreadPool$TaskQueue)
at com.xmliu.threadheap.ThreadPool$Worker.run(ThreadPool.java:40)
"Thread-1"在獲得鎖<0x22c01288> 後,調用wait()方法釋放了該鎖,等待其他線程的喚醒。
從線程堆棧中,我們就定位出死結問題、線程掛起、JVM崩潰等等情況;還能協助我們定位效能瓶頸,我們的線程都在幹什麼,哪些資源存在爭用。比如我們的任務分發線程做了很多的事情,而背景工作執行緒大都在等待分發線程的任務,那麼我們就知道需要最佳化以下分發線程的代碼,處理工作交給背景工作執行緒來做,自己只做消耗很少資源的分發工作。