Java 線程轉儲

jopen 11年前發布 | 24K 次閱讀 Java Java開發

軟件維護是一個枯燥而又有挑戰性的工作。只要軟件功能符合預期,那么這個工作就是好的。設想一個這樣的情景,你的電話半夜也一直在響(這不是一個令人愉快的感受,是吧?)

任何軟件系統,無論它當初是被設計的多好,也無論它經歷了怎樣的質量測試,仍然是有可能出現運行時性能問題。原因可能是內部功能限制或者外部環境影響。軟件系統是在某種假定的情景和先入為主的觀念之上被建立的。然而,當他們實際運行時,這些假定的情況可能是錯誤的,由此就會引起系統故障。

企業的J2EE系統通常擁有龐大的用戶基數,并且涉及多種系統間的交互,一個常見的運行時問題報告是系統的速度降低或者系統“掛起”。在這樣的情形下,常用的故障處理手段就是分析java線程的轉儲來找到引起系統減速或者掛起的線程。這篇文章就是討論java的堆棧跟蹤信息,匿名線程和怎樣讀取線程轉儲的通用方法。

異常和堆棧信息

我們當中的所有人在學習/開發的過程中都會遇到或者曾經遇到過異常。異常是java報告運行時錯誤的一種方式。異常分為兩部分:消息和堆棧信息。消息是告訴你什么出錯了。堆棧信息提供了一個涉及到的所有類的完整的調用流程來作為運行時錯誤的一部分。

下面的例子是一個ArrayIndexOutOfBoundsException(數組下標越界異常)的堆棧信息:

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 4
at Test.run(Test.java:13)
at Test.<init>(Test.java:5)
at Test.main(Test.java:20)

在上面的異常中,第一行“ Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 4”告訴你JVM在嘗試訪問數組下標為4的元素的值時遇到這個異常。遇到異常的java線程是“main”。

接下來讓我們讀一下堆棧信息。翻閱異常信息的規則是從第一行(消息行)了解是什么異常,然后接著讀下去,來了解調用流程。上面的例子中,調用流程開始于 Test.java的第20行(main方法),然后他調用了Test的構造方法。構造方法在堆棧信息中用<init>表示。然后它跳轉到 Test類的run()方法,然后在13行遇到了這個拋出的異常。

從上面的堆棧信息中,我們能夠得出結論,在Test.java中,嘗試讀取的值超過了傳遞的數組的大小。

java線程轉儲

java的線程轉儲可以被定義為JVM中在某一個給定的時刻運行的所有線程的快照。一個線程轉儲可能包含一個單獨的線程或者多個線程。在多線程環境中,比如J2EE應用服務器,將會有許多線程和線程組。每一個線程都有它自己的調用堆棧,在一個給定時刻,表現為一個獨立功能。線程轉儲將會提供JVM中所有線程的堆棧信息,對于特定的線程也會給出更多信息。

java虛擬機進程和java線程

java虛擬機,或者稱為JVM,是一個操作系統級別的進程。java線程是JVM進程的子進程或者輕量級進程(Solar中的叫法)。

生成java線程轉儲

線程轉儲可以通過向JVM進程發送一個SIGQUIT信號來生成。有兩種不同方式來向進程發送這個信號:

在Unix中,使用“kill -3<pid>”命令,pid表示JVM進程的ID。

在Windows中,在JVM運行時按下CTRL+BREAK鍵。

java線程狀態

每一個java線程總是處于其生命周期的四個狀態之一。

Runnable-線程正在運行,或者準備好獲取CPU時間后運行。JRockit線程轉儲中把這種狀態當做Active。

Waiting on Monitor-線程休眠,或者在等待一個對象,或者等待被其他線程喚醒。在線程對象中調用sleep()方法,或者在一個對象中調用wait()方法時就會有這種情況發生

舉個例子,在WebLogc服務器中,空閑的執行線程處于這種狀態,他們會一直等待直到一個Socket reader線程有新的任務才喚醒他們。堆棧信息就會如下所示:

"ExecuteThread: '2' for queue: 'weblogic.admin.RMI'" daemon prio=5 tid=0x1752F040 nid=0x180c in Object.wait() [1887f000..1887fd8c]
at java.lang.Object.wait(Native Method) waiting on <04134D98> (a weblogic.kernel.ExecuteThread)
at java.lang.Object.wait(Object.java:426)
at weblogic.kernel.ExecuteThread.waitForRequest(ExecuteThread.java:126)
locked <04134D98> (a weblogic.kernel.ExecuteThread)
at weblogic.kernel.ExecuteThread.run(ExecuteThread.java:145)

在某些別的版本的JVM中稱這種狀態是CW,Object.wait()(像上面那樣)。JRockit稱之為WAITING。

Waiting for Monitor Entry——線程在等待獲取一個對象的鎖(其他線程可能持有這個同步鎖)。這種情況發生在當兩個或者更多線程嘗試執行一段同步代碼時。注意“鎖”總是針對于一個對象而不是針對一個單獨的方法。

這種情況的線程的簡單堆棧信息如下:

"ExecuteThread: '24' for queue: 'DisplayExecuteQueue'" daemon prio=5 tid=0x5541b0 nid=0x3b waiting for monitor entry [49b7f000..49b7fc24]

at weblogic.cluster.replication.ReplicationManager.createSecondary (ReplicationManager.java:908)
- waiting to lock <6c4b9130> (a java.lang.Object)
at weblogic.cluster.replication.ReplicationManager.updateSecondary (ReplicationManager.java:715)
at weblogic.servlet.internal.session.ReplicatedSessionData.syncSession (ReplicatedSessonData.java:459)
- locked <6c408700> (a weblogic.servlet.internal.session.ReplicatedSessionData)
at weblogic.servlet.internal.session.ReplicatedSessionContext.sync (ReplicatedSessionContext.java:134)
- locked <6c408700> (aweblogic.servlet.internal.session.ReplicatedSessionData)
at weblogic.servlet.internal.ServletRequestImpl.syncSession (ServletRequestImpl.java:2418)
at weblogic.servlet.internal.WebAppServletContext.invokeServlet (WebAppServletContext.java:3137)
at weblogic.servlet.internal.ServletRequestImpl.execute (ServletRequestImpl.java:2544)
at weblogic.kernel.ExecuteThread.execute(ExecuteThread.java:153)
at weblogic.kernel.ExecuteThread.run(ExecuteThread.java:134)

在以上堆棧信息中,你可以看到這個線程持有一個對象鎖 (6c408700) ,并在等待另一個對象鎖(6c4b9130)。

某些別的版本的JVM可能不會在堆棧信息中給出對象的ID和鎖的信息。同樣的狀態,在某些JVM版本中可能被稱為“MW”。JRockit稱之為LOCKED。

分析一個Java線程

為了可以理解/分析線程轉儲,首先要理解線程轉儲的各個部分。讓我們先拿一個簡單的線程堆棧為例,并且去了解他的每個部分。

"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占用率

診斷

應用程序看起來幾乎讓CPU的占用率達到了100%,但是系統吞吐量卻明顯下降。開始于高負載的CPU性能很差。

線程轉儲

通過所有的線程轉儲,可以看到一個或多個線程在同一個操作中罷工了。

解決辦法

  • 為一個特定的調用流程(比如說網頁上的form提交),在流程完成之前,生成一系列的線程轉儲(大約5~7個)
  • 查找線程轉儲中的“runnable”線程。如果每一個線程看起來運行良好(每一個線程調用的方法都不相同),這些線程就是正在處理事務中,而且有可能并不是這次事件的罪魁禍首。如果通過所有的線程轉儲,發現線程正在執行同一個方法(同樣的行號),幾乎就可以確定這就是罪魁禍首了。那就可以查看代碼,來做代碼級別的分析了。你肯定也能從代碼中找到解決問題的靈感。

低CPU負載率和很長的響應時間

診斷

這通常在一個高I/O限制的系統處于高負載的時候發生。CPU的占用率很低,只有幾個線程在消耗CPU的時間片。然而應用的響應時間卻很長。

線程轉儲

一部分或者全部運行線程看起來就像是在一個I/O操作中罷工了,比如文件讀/寫或者數據庫的操作。

解決方法

了解你系統中的I/O操作。使用緩存以減少應用與數據庫之間的交互。

應用/服務宕機

診斷

一個應用或者一個運行這個應用的服務JVM宕機(變得停止響應)

線程轉儲

  • 在獲得的所有線程轉儲中,可以看到所有的運行線程都在同一個操作中罷工了。服務器沒有可用的線程,因為沒有一個線程能夠完成他自己的操作。
  • 或許有很多線程在等待一個鎖。當一個運行的線程持有一個對象鎖不釋放,而其他的線程恰好在等待這個對象鎖的時候就會發生。
解決方法
  • 檢查死鎖,通常簡單情況下(線程A在等待線程B,同時B也在等待A),JVM通常會檢測到死鎖。但是,你需要了解在這個時刻鎖的狀態,以確認這時候是否涉及到一個復雜的死鎖了。
  • 復查同步方法/代碼塊,盡可能的將不需要同步的代碼移出同步區,以減少同步區的大小。
  • 這種問題還有一個可能,就是訪問一個遠程的資源/組件的響應超時設置的太長。在訪問遠程對象時設置一個合理的超時時間,這樣就能夠在遠程系統失去響應時拋出一個可以捕獲的異常。
  • 如果所有的線程在等待一個資源(比如EJB/DB連接),考慮增加這些資源的對象池大小。
工具

對于線程轉儲分析,既有商業工具也有開源工具。其中有一個叫做Samurial的工具。它是一個輕量級的開源工具,和Java web啟動程序一樣,也是從命令提示行里啟動。想要了解更多信息和文檔,請訪問http://yusuke.homeip.net/samurai/en/index.html

總結

在生產環境中維護一個J2EE企業應用是一個艱巨的任務。在生產環境中,隨著事務的動態變化,J2EE企業應用的變化可能會表現出運行不穩定。影響一個 J2EE應用的主要因素就是高負載。雖然大多數的系統被設計成可伸縮的,但是環境條件的限制仍然有可能讓這些系統變得不響應。

Java線程轉儲是識別,診斷,檢測和解決典型生產問題的絕佳機制。由于應用概覽和其他機制的存在,分析java的線程轉儲將會讓我們對常見生產級別的問題有一個明確的早期認識,從而能夠節省時間,并讓我們的產品應用提供更好的用戶體驗。

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!