java的線程轉儲能夠被定義爲JVM中在某一個給定的時刻運行的全部線程的快照。一個線程轉儲可能包含一個單獨的線程或者多個線程。在多線程環境中,好比J2EE應用服務器,將會有許多線程和線程組。每個線程都有它本身的調用堆棧,在一個給定時刻,表現爲一個獨立功能。線程轉儲將會提供JVM中全部線程的堆棧信息,對於特定的線程也會給出更多信息。html
java虛擬機,或者稱爲JVM,是一個操做系統級別的進程。java線程是JVM進程的子進程或者輕量級進程(Solar中的叫法)。java
在java程序中有兩種經常使用的方法可讓咱們生成線程轉儲,這些方法對於linux或者Unix操做系統來講是有效的,window狀況稍有不一樣。 linux
咱們可使用VisualVM很容易的爲任何java程序生成線程轉儲。只須要在運行的java進程上點右鍵,選擇「Thread Dump」選項來生成。 web
java提供了自帶的工具jstack,經過這個工具咱們能夠一樣爲java進程產生線程轉儲。經過兩個步驟實現數據庫
2.1 用「ps -eaf | grep java」命令找到java進程的PID。 緩存
2.2 用jstack PID 命令來生成線程轉儲到控制檯,也能夠用命令「jstack PID >> mydumps將輸出保存到文件中mydumps中。 服務器
線程的狀態是一個重要的指標,它會顯示在線程 Stacktrace的頭一行結尾的地方。那麼線程常見的有哪些狀態呢?線程在什麼樣的狀況下會進入這種狀態呢?咱們能從中發現什麼線索?
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之間關係,以及線程的狀態轉換圖:
從圖中能夠看出,每一個 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) { ......... }
這時有兩種可能性: jsp
1)該 monitor不被其它線程擁有, Entry Set裏面也沒有其它等待線程。本線程即成爲相應類或者對象的 Monitor的 Owner,執行臨界區的代碼
2)該 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)
臨界區的設置,是爲了保證其內部的代碼執行的原子性和完整性。可是由於臨界區在任什麼時候間只容許線程串行經過,這 和咱們多線程的程序的初衷是相反的。 若是在多線程的程序中,大量使用 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)
仔細觀察上面的 DUMP信息,你會發現它有如下兩行:
- locked <0xef63beb8> (a java.util.ArrayList)
- waiting on <0xef63beb8> (a java.util.ArrayList)
這裏須要解釋一下,爲何先 lock了這個對象,而後又 waiting on同一個對象呢?讓咱們看看這個線程對應的代碼:
synchronized(obj) { ......... obj.wait(); ......... }
線程的執行中,先用 synchronized 得到了這個對象的 Monitor(對應於 locked <0xef63beb8> )。當執行到 obj.wait(), 線程即放棄了 Monitor的全部權,進入 「wait set」隊列(對應於 waiting on <0xef63beb8> )。
每每在你的程序中,會出現多個相似的線程,他們都有類似的 DUMP信息。這也多是正常的。好比,在程序中,有多個服務線程,設計成從一個隊列裏面讀取請求數據。這個隊列就是 lock以及 waiting on的對象。當隊列爲空的時候,這些線程都會在這個隊列上等待,直到隊列有了數據,這些線程被 Notify,固然只有一個線程得到了 lock,繼續執行,而其它線程繼續等待。
爲了能夠理解/分析線程轉儲,首先要理解線程轉儲的各個部分。讓咱們先拿一個簡單的線程堆棧爲例,而且去了解他的每一個部分。
"ExecuteThread: '1' " daemon prio=5 tid=0x628330 nid=0xf runnable [0xe4881000..0xe48819e0] at com.vantive.vanjavi.VanJavi.VanCreateForm(Native Method) at com.vantive.vanjavi.VanMain.open(VanMain.java:53) at jsp_servlet._so.__newServiceOrder.printSOSection( __newServiceOrder.java:3547) at jsp_servlet._so.__newServiceOrder._jspService (__newServiceOrder.java:5652) at weblogic.servlet.jsp.JspBase.service(JspBase.java:27) at weblogic.servlet.internal.ServletStubImpl.invokeServlet (ServletStubImpl.java:265) at weblogic.servlet.internal.ServletStubImpl.invokeServlet (ServletStubImpl.java:200) at weblogic.servlet.internal.WebAppServletContext.invokeServlet (WebAppServletContext.java:2495) at weblogic.servlet.internal.ServletRequestImpl.execute (ServletRequestImpl.java:2204) at weblogic.kernel.ExecuteThread.execute (ExecuteThread.java:139) at weblogic.kernel.ExecuteThread.run(ExecuteThread.java:120) In the above Thread Dump, the interesting part to is the first line. The rest of the stuff is nothing more than a general stack trace. Lets analyze the first line here
Execute Thread : 1 說明了線程的名字
daemon 代表這個線程是一個守護線程
prio=5 線程的優先級 (默認是5)
tid Java的線程Id (這個線程在當前虛擬機中的惟一標識).
nid 線程本地標識. 也就是Solaris中的LWP,線程在操做系統中的標識
runnable 線程的狀態 (參考上面的)
[x..y] 當前運行的線程在堆中的地址範圍
這個線程轉儲的剩餘部分是調用堆棧。在這個例子中,這個線程(Execute Thread 1)是操做系統守護線程,當前正在執行一個本地方法vanCreateForm()。
在這部分,我將描述幾個用例來講明線程轉儲是很是有用的。
應用程序看起來幾乎讓CPU的佔用率達到了100%,可是系統吞吐量卻明顯降低。開始於高負載的CPU性能不好。
經過全部的線程轉儲,能夠看到一個或多個線程在同一個操做中罷工了。
這一般在一個高I/O限制的系統處於高負載的時候發生。CPU的佔用率很低,只有幾個線程在消耗CPU的時間片。然而應用的響應時間卻很長。
一部分或者所有運行線程看起來就像是在一個I/O操做中罷工了,好比文件讀/寫或者數據庫的操做。
瞭解你係統中的I/O操做。使用緩存以減小應用與數據庫之間的交互。
一個應用或者一個運行這個應用的服務JVM宕機(變得中止響應)