1. 程式人生 > >性能分析之-- JAVA Thread Dump 分析綜述

性能分析之-- JAVA Thread Dump 分析綜述

頻繁 之前 監控 平臺 released 端口 16進制 後來 code

https://blog.csdn.net/rachel_luo/article/details/8920596

最近在做性能測試,需要對線程堆棧進行分析,在網上收集了一些資料,學習完後,將相關知識整理在一起,輸出文章如下。

一、Thread Dump介紹

1.1什麽是Thread Dump?

Thread Dump是非常有用的診斷Java應用問題的工具。每一個Java虛擬機都有及時生成所有線程在某一點狀態的thread-dump的能力,雖然各個 Java虛擬機打印的thread dump略有不同,但是大多都提供了當前活動線程的快照,及JVM中所有Java線程的堆棧跟蹤信息,堆棧信息一般包含完整的類名及所執行的方法,如果可能的話還有源代碼的行數。

1.2 Thread Dump特點

1. 能在各種操作系統下使用

2. 能在各種Java應用服務器下使用

3. 可以在生產環境下使用而不影響系統的性能

4. 可以將問題直接定位到應用程序的代碼行上

1.3 Thread Dump 能診斷的問題

1. 查找內存泄露,常見的是程序裏load大量的數據到緩存;

2. 發現死鎖線程;

1.4如何抓取Thread Dump

一般當服務器掛起,崩潰或者性能底下時,就需要抓取服務器的線程堆棧(Thread Dump)用於後續的分析. 在實際運行中,往往一次 dump的信息,還不足以確認問題。為了反映線程狀態的動態變化,需要接連多次做threaddump,每次間隔10-20s,建議至少產生三次 dump信息,如果每次 dump都指向同一個問題,我們才確定問題的典型性。

有很多方式可用於獲取ThreadDump, 下面列出一部分獲取方式:

操作系統命令獲取ThreadDump:

Windows:

1.轉向服務器的標準輸出窗口並按下Control + Break組合鍵, 之後需要將線程堆棧復制到文件中;

UNIX/ Linux:

首先查找到服務器的進程號(process id), 然後獲取線程堆棧.

1. ps –ef | grep java

2. kill -3 <pid>

註意:一定要謹慎, 一步不慎就可能讓服務器進程被殺死。kill -9 命令會殺死進程。

JVM 自帶的工具獲取線程堆棧:

JDK自帶命令行工具獲取PID,再獲取ThreadDump:

1. jps 或 ps –ef|grepjava (獲取PID)

2. jstack [-l ]<pid> | tee -a jstack.log (獲取ThreadDump)

二、java線程的狀態轉換介紹(為後續分析做準備)

技術分享圖片

2.1 新建狀態(New)

用new語句創建的線程處於新建狀態,此時它和其他Java對象一樣,僅僅在堆區中被分配了內存。

2.2 就緒狀態(Runnable)

當一個線程對象創建後,其他線程調用它的start()方法,該線程就進入就緒狀態,Java虛擬機會為它創建方法調用棧和程序計數器。處於這個狀態的線程位於可運行池中,等待獲得CPU的使用權。

2.3 運行狀態(Running)

處於這個狀態的線程占用CPU,執行程序代碼。只有處於就緒狀態的線程才有機會轉到運行狀態。

2.4 阻塞狀態(Blocked)

阻塞狀態是指線程因為某些原因放棄CPU,暫時停止運行。當線程處於阻塞狀態時,Java虛擬機不會給線程分配CPU。直到線程重新進入就緒狀態,它才有機會轉到運行狀態。

阻塞狀態可分為以下3種:

1)位於對象等待池中的阻塞狀態(Blocked in object’s wait pool):當線程處於運行狀態時,如果執行了某個對象的wait()方法,Java虛擬機就會把線程放到這個對象的等待池中,這涉及到“線程通信”的內容。

2)位於對象鎖池中的阻塞狀態(Blocked in object’s lock pool):當線程處於運行狀態時,試圖獲得某個對象的同步鎖時,如果該對象的同步鎖已經被其他線程占用,Java虛擬機就會把這個線程放到這個對象的鎖池中,這涉及到“線程同步”的內容。

3)其他阻塞狀態(Otherwise Blocked):當前線程執行了sleep()方法,或者調用了其他線程的join()方法,或者發出了I/O請求時,就會進入這個狀態。

2.5 死亡狀態(Dead)

當線程退出run()方法時,就進入死亡狀態,該線程結束生命周期。

三、Thread Dump分析

通過前面1.4部分的方法,獲取Thread Dump信息後,對其進行分析;

3.1 首先介紹一下Thread Dump信息的各個部分

頭部信息:

時間,jvm信息

2011-11-02 19:05:06

Full thread dump Java HotSpot(TM) Server VM (16.3-b01 mixed mode):

線程info信息塊:

1. "Timer-0" daemon prio=10tid=0xac190c00 nid=0xaef in Object.wait() [0xae77d000]

2. java.lang.Thread.State: TIMED_WAITING (on object monitor)

3. atjava.lang.Object.wait(Native Method)

4. -waiting on <0xb3885f60> (a java.util.TaskQueue) ###繼續wait

5. atjava.util.TimerThread.mainLoop(Timer.java:509)

6. -locked <0xb3885f60> (a java.util.TaskQueue) ###已經locked

7. atjava.util.TimerThread.run(Timer.java:462)

* 線程名稱:Timer-0
* 線程類型:daemon
* 優先級: 10,默認是5
* jvm線程id:tid=0xac190c00,jvm內部線程的唯一標識(通過java.lang.Thread.getId()獲取,通常用自增方式實現。)

* 對應系統線程id(NativeThread ID):nid=0xaef,和top命令查看的線程pid對應,不過一個是10進制,一個是16進制。(通過命令:top -H -p pid,可以查看該進程的所有線程信息)
* 線程狀態:in Object.wait().
* 起始棧地址:[0xae77d000]

* Java thread statck trace:是上面2-7行的信息。到目前為止這是最重要的數據,Java stack trace提供了大部分信息來精確定位問題根源。

對於thread dump信息,主要關註的是線程的狀態和其執行堆棧。現在針對這兩個重點部分進行講解:

1)Java thread statck trace詳解:

堆棧信息應該逆向解讀:程序先執行的是第7行,然後是第6行,依次類推。

- locked <0xb3885f60> (a java.util.ArrayList)
- waiting on <0xb3885f60> (a java.util.ArrayList)

也就是說對象先上鎖,鎖住對象0xb3885f60,然後釋放該對象鎖,進入waiting狀態。

為啥會出現這樣的情況呢?看看下面的java代碼示例,就會明白:

synchronized(obj) {

.........

obj.wait();

.........

}

在堆棧的第一行信息中,進一步標明了線程在代碼級的狀態,例如:

java.lang.Thread.State: TIMED_WAITING (parking)

解釋如下:

|blocked|

This thread tried to enter asynchronized block, but the lock was taken by another thread. This thread isblocked until the lock gets released.

|blocked (on thin lock)|

This is the same state asblocked, but the lock in question is a thin lock.

|waiting|

This thread calledObject.wait() on an object. The thread will remain there until some otherthread sends a notification to that object.

|sleeping|

This thread calledjava.lang.Thread.sleep().

|parked|

This thread calledjava.util.concurrent.locks.LockSupport.park().

|suspended|

The thread‘s execution wassuspended by java.lang.Thread.suspend() or a JVMTI agent call.

2) 線程狀態詳解:

Runnable
_The thread is either running or ready to run when it gets its CPU turn._

Wait on condition
_The thread is either sleeping or waiting to be notified by another thread._
該狀態出現在線程等待某個條件的發生或者sleep。具體是什麽原因,可以結合 stacktrace來分析。最常見的情況是線程在等待網絡的讀寫,比如當網絡數據沒有準備好讀時,線程處於這種等待狀態,而一旦有數據準備好讀之後,線程會重新激活,讀取並處理數據。在Java引入 New IO之前,對於每個網絡連接,都有一個對應的線程來處理網絡的讀寫操作,即使沒有可讀寫的數據,線程仍然阻塞在讀寫操作上,這樣有可能造成資源浪費,而且給操作系統的線程調度也帶來壓力。在 New IO裏采用了新的機制,編寫的服務器程序的性能和可擴展性都得到提高。
如果發現有大量的線程都處在 Wait on condition,從線程 stack看, 正等待網絡讀寫,這可能是一個網絡瓶頸的征兆。因為網絡阻塞導致線程無法執行。一種情況是網絡非常忙,幾乎消耗了所有的帶寬,仍然有大量數據等待網絡讀寫;另一種情況也可能是網絡空閑,但由於路由等問題,導致包無法正常的到達。所以要結合系統的一些性能觀察工具來綜合分析,比如 netstat統計單位時間的發送包的數目,看是否很明顯超過了所在網絡帶寬的限制;觀察cpu的利用率,看系統態的CPU時間是否明顯大於用戶態的CPU時間;如果程序運行在 Solaris 10平臺上,可以用dtrace工具看系統調用的情況,如果觀察到 read/write的系統調用的次數或者運行時間遙遙領先;這些都指向由於網絡帶寬所限導致的網絡瓶頸。另外一種出現 Wait on condition的常見情況是該線程在 sleep,等待 sleep的時間到了,將被喚醒。


Waiting for Monitor Entry and in Object.wait()

_The thread is waiting to getthe lock for an object (some other thread may be holding the lock). Thishappens if two or more threads try to execute synchronized code. Note that thelock is always for an object and not for individual methods._

在多線程的 JAVA程序中,實現線程之間的同步,就要說說 Monitor。 Monitor是Java中用以實現線程之間的互斥與協作的主要手段,它可以看成是對象或者 Class的鎖。每一個對象都有,也僅有一個 monitor。每個 Monitor在某個時刻,只能被一個線程擁有,該線程就是 “ActiveThread”,而其它線程都是 “Waiting Thread”,分別在兩個隊列 “ Entry Set”和 “Wait Set”裏面等候。在 “Entry Set”中等待的線程狀態是 “Waiting for monitorentry”,而在 “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”。

臨界區的設置,是為了保證其內部的代碼執行的原子性和完整性。但是因為臨界區在任何時間只允許線程串行通過,這和我們多線程的程序的初衷是相反的。如果在多線程的程序中,大量使用 synchronized,或者不適當的使用了它,會造成大量線程在臨界區的入口等待,造成系統的性能大幅下降。如果在線程 DUMP中發現了這個情況,應該審查源碼,改進程序。

再看“Wait Set”裏面的線程。當線程獲得了 Monitor,進入了臨界區之後,如果發現線程繼續運行的條件沒有滿足,它則調用對象(一般就是被 synchronized 的對象)的 wait() 方法,放棄 Monitor,進入 “Wait Set”隊列。只有當別的線程在該對象上調用了 notify() 或者 notifyAll(),“Wait Set”隊列中線程才得到機會去競爭,但是只有一個線程獲得對象的Monitor,恢復到運行態。在 “Wait Set”中的線程, DUMP中表現為: in Object.wait()。

一般,Cpu很忙時,則關註runnable的線程,Cpu很閑時,則關註waiting for monitor entry的線程。

3.2 JVM線程介紹

在Thread Dump中,有一些 JVM內部的後臺線程,來執行譬如垃圾回收,或者低內存的檢測等等任務,這些線程往往在 JVM初始化的時候就存在,如下所示:

HotSpot VM Thread

被HotSpot VM管理的內部線程為了完成內部本地操作,一般來說不需要擔心它們,除非CPU很高。

"VM Periodic Task Thread" prio=10tid=0xad909400 nid=0xaed waiting on condition

HotSpot GC Thread

當使用HotSpot parallel GC,HotSpot VM默認創建一定數目的GC thread。

"GC task thread#0 (ParallelGC)"prio=10 tid=0xf690b400 nid=0xade runnable

"GC task thread#1 (ParallelGC)"prio=10 tid=0xf690cc00 nid=0xadf runnable

"GC task thread#2 (ParallelGC)"prio=10 tid=0xf690e000 nid=0xae0 runnable

……

當面對過多GC,內存泄露等問題時,這些是關鍵的數據。使用native id,可以將從OS/Java進程觀測到的高CPU與這些線程關聯起來。

JNI global references count

JNI global reference是基本的對象引用,從本地代碼到被Java GC管理的Java對象的引用。其角色是阻止仍然被本地代碼使用的對象集合,但在Java代碼中沒有引用。在探測JNI相關內存泄露時,關註JNI references很重要。如果你的程序直接使用JNI或使用第三方工具,如檢測工具,檢測本地內存泄露。

JNI global references: 832

Java Heap utilization view

從jdk1.6開始在thread dump快照底部,可以找到崩潰點的內存空間利用情況:YongGen,OldGen和PermGen。目前我測試的系統導出的thread dump,還未見到這一部分內容(sun jdk1.6)。以下例子,摘自他人文章:

Heap

PSYoungGen total 466944K, used 178734K [0xffffffff45c00000, 0xffffffff70800000, 0xffffffff70800000)

eden space 233472K, 76% used [0xffffffff45c00000,0xffffffff50ab7c50,0xffffffff54000000)

from space 233472K, 0% used [0xffffffff62400000,0xffffffff62400000,0xffffffff70800000)

to space 233472K, 0% used [0xffffffff54000000,0xffffffff54000000,0xffffffff62400000)

PSOldGen total 1400832K, used 1400831K [0xfffffffef0400000, 0xffffffff45c00000, 0xffffffff45c00000)

object space 1400832K, 99% used [0xfffffffef0400000,0xffffffff45bfffb8,0xffffffff45c00000)

PSPermGen total 262144K, used 248475K [0xfffffffed0400000, 0xfffffffee0400000, 0xfffffffef0400000)

object space 262144K, 94% used [0xfffffffed0400000,0xfffffffedf6a6f08,0xfffffffee0400000)

還有一些其他的線程(如下),不一一介紹了,有興趣,可查看文章最後的附件信息。

"Low Memory Detector" daemon prio=10tid=0xad907400 nid=0xaec runnable [0x00000000]

"CompilerThread1" daemon prio=10tid=0xad905400 nid=0xaeb waiting on condition [0x00000000]

"CompilerThread0" daemon prio=10tid=0xad903c00 nid=0xaea waiting on condition [0x00000000]

"Signal Dispatcher" daemon prio=10tid=0xad902400 nid=0xae9 runnable [0x00000000]

"Finalizer" daemon prio=10tid=0xf69eec00 nid=0xae8 in Object.wait() [0xaf17d000]

"Reference Handler" daemon prio=10tid=0xf69ed800 nid=0xae7 in Object.wait() [0xae1e7000]

"VM Thread" prio=10 tid=0xf69e9800nid=0xae6 runnable

四、案例分析:

4.1、使用方案

cpu飆高,load高,響應很慢

方案:
* 一個請求過程中多次dump

* 對比多次dump文件的runnable線程,如果執行的方法有比較大變化,說明比較正常。如果在執行同一個方法,就有一些問題了。

查找占用cpu最多的線程信息

方案:
* 使用命令: top -H -p pid(pid為被測系統的進程號),找到導致cpu高的線程id。

上述Top命令找到的線程id,對應著dump thread信息中線程的nid,只不過一個是十進制,一個是十六進制。

* 在thread dump中,根據top命令查找的線程id,查找對應的線程堆棧信息。

cpu使用率不高但是響應很慢

方案:
* 進行dump,查看是否有很多thread struck在了i/o、數據庫等地方,定位瓶頸原因。

請求無法響應

方案:
* 多次dump,對比是否所有的runnable線程都一直在執行相同的方法,如果是的,恭喜你,鎖住了!

4.2 案例分析:

1.死鎖:

死鎖經常表現為程序的停頓,或者不再響應用戶的請求。從操作系統上觀察,對應進程的CPU占用率為零,很快會從top或prstat的輸出中消失。

在thread dump中,會看到類似於這樣的信息:

技術分享圖片(圖 1)

技術分享圖片

(圖2)

說明:

(圖1)中有一個“Waiting formonitor entry”,可以看出,兩個線程各持有一個鎖,又在等待另一個鎖,很明顯這兩個線程互相持有對方正在等待的鎖。所以造成了死鎖現象;

(圖2)中對死鎖的現象做了說明,可以看到,是“DeadLockTest.java”的39行造成的死鎖現象。這樣就能到相應的代碼下去查看,定位問題。

2.熱鎖

熱鎖,也往往是導致系統性能瓶頸的主要因素。其表現特征為:由於多個線程對臨界區,或者鎖的競爭,可能出現:
* 頻繁的線程的上下文切換:從操作系統對線程的調度來看,當線程在等待資源而阻塞的時候,操作系統會將之切換出來,放到等待的隊列,當線程獲得資源之後,調度算法會將這個線程切換進去,放到執行隊列中。
* 大量的系統調用:因為線程的上下文切換,以及熱鎖的競爭,或者臨界區的頻繁的進出,都可能導致大量的系統調用。
* 大部分CPU開銷用在“系統態 ”:線程上下文切換,和系統調用,都會導致 CPU在 “系統態 ”運行,換而言之,雖然系統很忙碌,但是 CPU用在 “用戶態 ”的比例較小,應用程序得不到充分的 CPU資源。
* 隨著 CPU數目的增多,系統的性能反而下降。因為CPU數目多,同時運行的線程就越多,可能就會造成更頻繁的線程上下文切換和系統態的CPU開銷,從而導致更糟糕的性能。
上面的描述,都是一個 scalability(可擴展性)很差的系統的表現。從整體的性能指標看,由於線程熱鎖的存在,程序的響應時間會變長,吞吐量會降低。
那麽,怎麽去了解 “熱鎖 ”出現在什麽地方呢?一個重要的方法還是結合操作系統的各種工具觀察系統資源使用狀況,以及收集Java線程的DUMP信息,看線程都阻塞在什麽方法上,了解原因,才能找到對應的解決方法。
我們曾經遇到過這樣的例子,程序運行時,出現了以上指出的各種現象,通過觀察操作系統的資源使用統計信息,以及線程 DUMP信息,確定了程序中熱鎖的存在,並發現大多數的線程狀態都是 Waitingfor monitor entry或者 Wait on monitor,且是阻塞在壓縮和解壓縮的方法上。後來采用第三方的壓縮包 javalib替代 JDK自帶的壓縮包後,系統的性能提高了幾倍。

五、附件:

JVM運行過程中產生的一些比較重要的線程羅列如下:

線程名稱

所屬

解釋說明

Attach Listener

JVM

Attach Listener 線程是負責接收到外部的命令,而對該命令進行執行的並且吧結果返回給發送者。通常我們會用一些命令去要求jvm給我們一些反饋信息,如:java -version、jmap、jstack等等。 如果該線程在jvm啟動的時候沒有初始化,那麽,則會在用戶第一次執行jvm命令時,得到啟動。

Signal Dispatcher

JVM

前面我們提到第一個Attach Listener線程的職責是接收外部jvm命令,當命令接收成功後,會交給signal dispather 線程去進行分發到各個不同的模塊處理命令,並且返回處理結果。 signal dispather線程也是在第一次接收外部jvm命令時,進行初始化工作。

CompilerThread0

JVM

用來調用JITing,實時編譯裝卸class 。 通常,jvm會啟動多個線程來處理這部分工作,線程名稱後面的數字也會累加,例如:CompilerThread1

Concurrent Mark-Sweep GC Thread

JVM

並發標記清除垃圾回收器(就是通常所說的CMS GC)線程, 該線程主要針對於老年代垃圾回收。ps:啟用該垃圾回收器,需要在jvm啟動參數中加上: -XX:+UseConcMarkSweepGC

DestroyJavaVM

JVM

執行main()的線程在main執行完後調用JNI中的 jni_DestroyJavaVM() 方法喚起DestroyJavaVM 線程。

JVM在 Jboss 服務器啟動之後,就會喚起DestroyJavaVM線程,處於等待狀態,等待其它線程(java線程和native線程)退出時通知它卸載JVM。線程退出時,都會判斷自己當前是否是整個JVM中最後一個非deamon線程,如果是,則通知DestroyJavaVM 線程卸載JVM。

ps:

擴展一下:

1.如果線程退出時判斷自己不為最後一個非deamon線程,那麽調用thread->exit(false) ,並在其中拋出thread_end事件,jvm不退出。

2.如果線程退出時判斷自己為最後一個非deamon線程,那麽調用before_exit() 方法,拋出兩個事件:

事件1:thread_end 線程結束事件;

事件2:VM的death事件。

然後調用thread->exit(true) 方法,接下來把線程從active list卸下,刪除線程等等一系列工作執行完成後,則通知正在等待的DestroyJavaVM 線程執行卸載JVM操作。

ContainerBackgroundProcessor 線程

JBOSS

它是一個守護線程, 在jboss服務器在啟動的時候就初始化了,主要工作是定期去檢查有沒有Session過期.過期則清除.

參考:

http://liudeh-009.iteye.com/blog/1584876

Dispatcher-Thread-3 線程

Log4j

Log4j具有異步打印日誌的功能,需要異步打印日誌的Appender都需要註冊到 AsyncAppender對象裏面去,由AsyncAppender進行監聽,決定何時觸發日誌打印操作。 AsyncAppender如果監聽到它管轄範圍內的Appender有打印日誌的操作,則給這個Appender生成一個相應的event,並將該event保存在一個buffuer區域內。

Dispatcher-Thread-3線程負責判斷這個event緩存區是否已經滿了,如果已經滿了,則將緩存區內的所有event分發到Appender容器裏面去,那些註冊上來的Appender收到自己的event後,則開始處理自己的日誌打印工作。 Dispatcher-Thread-3線程是一個守護線程。

Finalizer線程

JVM

這個線程也是在main線程之後創建的,其優先級為10,主要用於在垃圾收集前,調用對象的finalize()方法;關於Finalizer線程的幾點:

1) 只有當開始一輪垃圾收集時,才會開始調用finalize()方法;因此並不是所有對象的finalize()方法都會被執行;

2) 該線程也是daemon線程,因此如果虛擬機中沒有其他非daemon線程,不管該線程有沒有執行完finalize()方法,JVM也會退出;

3) JVM在垃圾收集時會將失去引用的對象包裝成Finalizer對象(Reference的實現),並放入ReferenceQueue,由Finalizer線程來處理;最後將該Finalizer對象的引用置為null,由垃圾收集器來回收;

4) JVM為什麽要單獨用一個線程來執行finalize()方法呢?如果JVM的垃圾收集線程自己來做,很有可能由於在finalize()方法中誤操作導致GC線程停止或不可控,這對GC線程來說是一種災難;

Gang worker#0

JVM

JVM 用於做新生代垃圾回收(monir gc)的一個線程。#號後面是線程編號,例如:Gang worker#1

GC Daemon

JVM

GC Daemon 線程是JVM為RMI提供遠程分布式GC使用的,GC Daemon線程裏面會主動調用System.gc()方法,對服務器進行Full GC。 其初衷是當 RMI 服務器返回一個對象到其客戶機(遠程方法的調用方)時,其跟蹤遠程對象在客戶機中的使用。當再沒有更多的對客戶機上遠程對象的引用時,或者如果引用的“租借”過期並且沒有更新,服務器將垃圾回收遠程對象。

不過,我們現在jvm啟動參數都加上了-XX:+DisableExplicitGC配置,所以,這個線程只有打醬油的份了。

IdleRemover

JBOSS

Jboss連接池有一個最小值, 該線程每過一段時間都會被Jboss喚起,用於檢查和銷毀連接池中空閑和無效的連接,直到剩余的連接數小於等於它的最小值。

Java2D Disposer

JVM

這個線程主要服務於awt的各個組件。 說起該線程的主要工作職責前,需要先介紹一下Disposer類是幹嘛的。 Disposer提供一個addRecord方法。 如果你想在一個對象被銷毀前再做一些善後工作,那麽,你可以調用Disposer#addRecord方法,將這個對象和一個自定義的DisposerRecord接口實現類,一起傳入進去,進行註冊。

Disposer類會喚起“Java2D Disposer”線程,該線程會掃描已註冊的這些對象是否要被回收了,如果是,則調用該對象對應的DisposerRecord實現類裏面的dispose方法。

Disposer實際上不限於在awt應用場景,只是awt裏面的很多組件需要訪問很多操作系統資源,所以,這些組件在被回收時,需要先釋放這些資源。

InsttoolCacheScheduler_QuartzSchedulerThread

Quartz

InsttoolCacheScheduler_QuartzSchedulerThread是Quartz的主線程,它主要負責實時的獲取下一個時間點要觸發的觸發器,然後執行觸發器相關聯的作業 。

原理大致如下:

Spring和Quartz結合使用的場景下,Spring IOC容器初始化時會創建並初始化Quartz線程池(TreadPool),並啟動它。剛啟動時線程池中每個線程都處於等待狀態,等待外界給他分配Runnable(持有作業對象的線程)。

繼而接著初始化並啟動Quartz的主線程

(InsttoolCacheScheduler_QuartzSchedulerThread),該線程自啟動後就會處於等待狀態。等待外界給出工作信號之後,該主線程的run方法才實質上開始工作。run中會獲取JobStore中下一次要觸發的作業,拿到之後會一直等待到該作業的真正觸發時間,然後將該作業包裝成一個JobRunShell對象(該對象實現了Runnable接口,其實看是上面TreadPool中等待外界分配給他的Runnable),然後將剛創建的JobRunShell交給線程池,由線程池負責執行作業。

線程池收到Runnable後,從線程池一個線程啟動Runnable,反射調用JobRunShell中的run方法,run方法執行完成之後, TreadPool將該線程回收至空閑線程中。

InsttoolCacheScheduler_Worker-2

Quartz

InsttoolCacheScheduler_Worker-2線程就是ThreadPool線程的一個簡單實現,它主要負責分配線程資源去執行

InsttoolCacheScheduler_QuartzSchedulerThread線程交給它的調度任務(也就是JobRunShell)。

JBossLifeThread

Jboss

Jboss主線程啟動成功,應用程序部署完畢之後將JBossLifeThread線程實例化並且start,JBossLifeThread線程啟動成功之後就處於等待狀態,以保持Jboss Java進程處於存活中。 所得比較通俗一點,就是Jboss啟動流程執行完畢之後,為什麽沒有結束? 就是因為有這個線程hold主了它。

JBoss System Threads(1)-1

Jboss

該線程是一個socket服務,默認端口號為: 1099。

主要用於接收外部naming service(Jboss JNDI)請求。

JCA PoolFiller

Jboss

該線程主要為JBoss內部提供連接池的托管。

簡單介紹一下工作原理 :

Jboss內部凡是有遠程連接需求的類,都需要實現

ManagedConnectionFactory接口,例如需要做JDBC連接的

XAManagedConnectionFactory對象,就實現了該接口。

然後將XAManagedConnectionFactory對象,

還有其它信息一起包裝到

InternalManagedConnectionPool

對象裏面,接著將

InternalManagedConnectionPool

交給PoolFiller對象裏面的列隊進行管理。

JCA PoolFiller線程會定期判斷列隊內是否有需要創建和管理的

InternalManagedConnectionPool

對象,如果有的話,則調用該對象的fillToMin方法, 觸發它去創建相應的遠程連接,並且將這個連接維護到它相應的連接池裏面去。

JDWP Event Helper Thread

JVM

JDWP是通訊交互協議,它定義了調試器和被調試程序之間傳遞信息的格式。它詳細完整地定義了請求命令、回應數據和錯誤代碼,保證了前端和後端的JVMTI和JDI的通信通暢。 該線程主要負責將JDI事件映射成JVMTI信號,以達到調試過程中操作JVM的目的。

JDWP Transport Listener:

dt_socket

JVM

該線程是一個Java Debugger的監聽器線程,負責受理客戶端的debug請求。 通常我們習慣將它的監聽端口設置為8787。

Low Memory Detector

JVM

這個線程是負責對可使用內存進行檢測,如果發現可用內存低,分配新的內存空間。

process reaper

JVM

該線程負責去執行一個 OS 命令行的操作。

Reference Handler

JVM

JVM在創建main線程後就創建Reference Handler線程,其優先級最高,為10,它主要用於處理引用對象本身(軟引用、弱引用、虛引用)的垃圾回收問題 。

Surrogate Locker Thread (CMS)

JVM

這個線程主要用於配合CMS垃圾回收器使用,它是一個守護線程,其主要負責處理GC過程中,Java層的Reference(指軟引用、弱引用等等)與jvm 內部層面的對象狀態同步。 這裏對它們的實現稍微做一下介紹:這裏拿 WeakHashMap做例子,將一些關鍵點先列出來(我們後面會將這些關鍵點全部串起來):

1.我們知道HashMap用Entry[]數組來存儲數據的,WeakHashMap也不例外, 內部有一個Entry[]數組。

2. WeakHashMap的Entry比較特殊,它的繼承體系結構為

Entry->WeakReference->Reference 。

3.Reference 裏面有一個全局鎖對象:Lock,

它也被稱為pending_lock.註意:它是靜態對象。

4. Reference 裏面有一個靜態變量:pending。

5. Reference裏面有一個靜態內部類:ReferenceHandler的線程,它在static塊裏面被初始化並且啟動,啟動完成後處於wait狀態,它在一個Lock同步鎖模塊中等待。

6.另外,WeakHashMap裏面還實例化了一個ReferenceQueue列隊,這個列隊的作用,後面會提到。

7.上面關鍵點就介紹完畢了,下面我們把他們串起來。

假設,WeakHashMap對象裏面已經保存了很多對象的引用。

JVM 在進行CMS GC的時候,會創建一個ConcurrentMarkSweepThread(簡稱CMST)線程去進行GC,ConcurrentMarkSweepThread線程被創建的同時會創建一個SurrogateLockerThread(簡稱SLT)線程並且啟動它,SLT啟動之後,處於等待階段。CMST開始GC時,會發一個消息給SLT讓它去獲取Java層Reference對象的全局鎖:Lock。 直到CMS GC完畢之後,JVM 會將WeakHashMap中所有被回收的對象所屬的WeakReference容器對象放入到Reference 的pending屬性當中(每次GC完畢之後,pending屬性基本上都不會為null了),然後通知SLT釋放並且notify全局鎖:Lock。此時激活了ReferenceHandler線程的run方法,使其脫離wait狀態,開始工作了。ReferenceHandler這個線程會將pending中的所有WeakReference對象都移動到它們各自的列隊當中,比如當前這個WeakReference屬於某個WeakHashMap對象,那麽它就會被放入相應的ReferenceQueue列隊裏面(該列隊是鏈表結構)。 當我們下次從WeakHashMap對象裏面get、put數據或者調用size方法的時候,WeakHashMap就會將ReferenceQueue列隊中的WeakReference依依poll出來去和Entry[]數據做比較,如果發現相同的,則說明這個Entry所保存的對象已經被GC掉了,那麽將Entry[]內的Entry對象剔除掉。

taskObjectTimerFactory

JVM

顧名思義,該線程就是用來執行任務的。 當我們把一個認為交給Timer對象,並且告訴它執行時間,周期時間後,Timer就會將該任務放入任務列隊,並且通知taskObjectTimerFactory線程去處理任務,taskObjectTimerFactory線程會將狀態為取消的任務從任務列隊中移除,如果任務是非重復執行類型的,則在執行完該任務後,將它從任務列隊中移除,如果該任務是需要重復執行的,則計算出它下一次執行的時間點。

VM Periodic Task Thread

JVM

該線程是JVM周期性任務調度的線程,它由WatcherThread創建,是一個單例對象。 該線程在JVM內使用得比較頻繁,比如:定期的內存監控、JVM運行狀況監控,還有我們經常需要去執行一些jstat 這類命令查看gc的情況,如下:

jstat -gcutil 23483 250 7 這個命令告訴jvm在控制臺打印PID為:23483的gc情況,間隔250毫秒打印一次,一共打印7次。

VM Thread

JVM

這個線程就比較牛b了,是jvm裏面的線程母體,根據hotspot源碼(vmThread.hpp)裏面的註釋,它是一個單例的對象(最原始的線程)會產生或觸發所有其他的線程,這個單個的VM線程是會被其他線程所使用來做一些VM操作(如,清掃垃圾等)。

在 VMThread 的結構體裏有一個VMOperationQueue列隊,所有的VM線程操作(vm_operation)都會被保存到這個列隊當中,VMThread 本身就是一個線程,它的線程負責執行一個自輪詢的loop函數(具體可以參考:

VMThread.cpp裏面的

void VMThread::loop()) ,該loop函數從VMOperationQueue列隊中按照優先級取出當前需要執行的操作對象(VM_Operation),

並且調用VM_Operation->evaluate函數去執行該操作類型本身的業務邏輯。

ps:VM操作類型被定義在

vm_operations.hpp文件內,列舉幾個:ThreadStop、

ThreadDump、

PrintThreads、

GenCollectFull、GenCollectFullConcurrent、CMS_Initial_Mark、CMS_Final_Remark…..

參考文章:

http://jameswxx.iteye.com/blog/1041173

http://blog.csdn.net/wanyanxgf/article/details/6944987

http://blog.csdn.net/fanshadoop/article/details/8509218

http://www.7dtest.com/site/article-80-1.html

http://blog.csdn.net/a43350860/article/details/8134234

https://weblogs.java.net/blog/mandychung/archive/2005/11/thread_dump_and_1.html

性能分析之-- JAVA Thread Dump 分析綜述