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

一、Thread Dump介紹

1.1 什么是Thread Dump?

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

1.2 Thread Dump特點

1. 能在各種操作系統(tǒng)下使用

2. 能在各種Java應(yīng)用服務(wù)器下使用

3. 可以在生產(chǎn)環(huán)境下使用而不影響系統(tǒng)的性能

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

1.3 Thread Dump 能診斷的問題

1. 查找內(nèi)存泄露,常見的是程序里load大量的數(shù)據(jù)到緩存;

2. 發(fā)現(xiàn)死鎖線程;

1.4 如何抓取Thread Dump

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

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

操作系統(tǒng)命令獲取ThreadDump:

Windows:

1.轉(zhuǎn)向服務(wù)器的標(biāo)準(zhǔn)輸出窗口并按下Control + Break組合鍵, 之后需要將線程堆棧復(fù)制到文件中;

UNIX/ Linux

首先查找到服務(wù)器的進(jìn)程號(process id), 然后獲取線程堆棧.

1. ps –ef | grep java

2. kill -3 <pid>

注意:一定要謹(jǐn)慎, 一步不慎就可能讓服務(wù)器進(jìn)程被殺死。kill -9 命令會殺死進(jìn)程。

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

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

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

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

二、java線程的狀態(tài)轉(zhuǎn)換介紹(為后續(xù)分析做準(zhǔn)備)

image.png

2.1 新建狀態(tài)(New)

用new語句創(chuàng)建的線程處于新建狀態(tài),此時它和其他Java對象一樣,僅僅在堆區(qū)中被分配了內(nèi)存。

2.2 就緒狀態(tài)(Runnable)

當(dāng)一個線程對象創(chuàng)建后,其他線程調(diào)用它的start()方法,該線程就進(jìn)入就緒狀態(tài),Java虛擬機(jī)會為它創(chuàng)建方法調(diào)用棧和程序計數(shù)器。處于這個狀態(tài)的線程位于可運行池中,等待獲得CPU的使用權(quán)。

2.3 運行狀態(tài)(Running)

處于這個狀態(tài)的線程占用CPU,執(zhí)行程序代碼。只有處于就緒狀態(tài)的線程才有機(jī)會轉(zhuǎn)到運行狀態(tài)。

2.4 阻塞狀態(tài)(Blocked)

阻塞狀態(tài)是指線程因為某些原因放棄CPU,暫時停止運行。當(dāng)線程處于阻塞狀態(tài)時,Java虛擬機(jī)不會給線程分配CPU。直到線程重新進(jìn)入就緒狀態(tài),它才有機(jī)會轉(zhuǎn)到運行狀態(tài)。

阻塞狀態(tài)可分為以下3種:

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

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

3)其他阻塞狀態(tài)(Otherwise Blocked):當(dāng)前線程執(zhí)行了sleep()方法,或者調(diào)用了其他線程的join()方法,或者發(fā)出了I/O請求時,就會進(jìn)入這個狀態(tài)。

2.5 死亡狀態(tài)(Dead)

當(dāng)線程退出run()方法時,就進(jìn)入死亡狀態(tài),該線程結(jié)束生命周期。

三、Thread Dump分析

通過前面1.4部分的方法,獲取Thread Dump信息后,對其進(jìn)行分析;

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=10 tid=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) ###繼續(xù)wait
  5. atjava.util.TimerThread.mainLoop(Timer.java:509)
  6. -locked <0xb3885f60> (a java.util.TaskQueue) ###已經(jīng)locked
  7. atjava.util.TimerThread.run(Timer.java:462)

第一行
線程名稱:Timer-0
線程類型:daemon
優(yōu)先級: 10,默認(rèn)是5
jvm線程id:tid=0xac190c00,jvm內(nèi)部線程的唯一標(biāo)識(通過java.lang.Thread.getId()獲取,通常用自增方式實現(xiàn)。)
對應(yīng)系統(tǒng)線程id(NativeThread ID):nid=0xaef,和top命令查看的線程pid對應(yīng),不過一個是10進(jìn)制,一個是16進(jìn)制。(通過命令:top -H -p pid,可以查看該進(jìn)程的所有線程信息)
線程狀態(tài):in Object.wait().
起始棧地址:[0xae77d000]

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

對于thread dump信息,主要關(guān)注的是線程的狀態(tài)和其執(zhí)行堆?!,F(xiàn)在針對這兩個重點部分進(jìn)行講解:

1)Java thread statck trace詳解:

堆棧信息應(yīng)該逆向解讀:程序先執(zhí)行的是第7行,然后是第6行,依次類推。

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

也就是說對象先上鎖,鎖住對象0xb3885f60,然后釋放該對象鎖,進(jìn)入waiting狀態(tài)。
為啥會出現(xiàn)這樣的情況呢?看看下面的java代碼示例,就會明白:

synchronized(obj) {  
       .........  
       obj.wait();  
       .........  
}  

在堆棧的第一行信息中,進(jìn)一步標(biāo)明了線程在代碼級的狀態(tài),例如:

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 called Object.wait() on an object. The thread will remain there until some other thread sends a notification to that object.

|sleeping|

This thread called java.lang.Thread.sleep().

|parked|

This thread called java.util.concurrent.locks.LockSupport.park().

|suspended|

The thread's execution was suspended by java.lang.Thread.suspend() or a JVMTI agent call.

2) 線程狀態(tài)詳解:

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.
該狀態(tài)出現(xiàn)在線程等待某個條件的發(fā)生或者sleep。具體是什么原因,可以結(jié)合 stacktrace來分析。最常見的情況是線程在等待網(wǎng)絡(luò)的讀寫,比如當(dāng)網(wǎng)絡(luò)數(shù)據(jù)沒有準(zhǔn)備好讀時,線程處于這種等待狀態(tài),而一旦有數(shù)據(jù)準(zhǔn)備好讀之后,線程會重新激活,讀取并處理數(shù)據(jù)。在Java引入 New IO之前,對于每個網(wǎng)絡(luò)連接,都有一個對應(yīng)的線程來處理網(wǎng)絡(luò)的讀寫操作,即使沒有可讀寫的數(shù)據(jù),線程仍然阻塞在讀寫操作上,這樣有可能造成資源浪費,而且給操作系統(tǒng)的線程調(diào)度也帶來壓力。在 New IO里采用了新的機(jī)制,編寫的服務(wù)器程序的性能和可擴(kuò)展性都得到提高。
如果發(fā)現(xiàn)有大量的線程都處在 Wait on condition,從線程 stack看, 正等待網(wǎng)絡(luò)讀寫,這可能是一個網(wǎng)絡(luò)瓶頸的征兆。因為網(wǎng)絡(luò)阻塞導(dǎo)致線程無法執(zhí)行。一種情況是網(wǎng)絡(luò)非常忙,幾乎消耗了所有的帶寬,仍然有大量數(shù)據(jù)等待網(wǎng)絡(luò)讀寫;另一種情況也可能是網(wǎng)絡(luò)空閑,但由于路由等問題,導(dǎo)致包無法正常的到達(dá)。所以要結(jié)合系統(tǒng)的一些性能觀察工具來綜合分析,比如 netstat統(tǒng)計單位時間的發(fā)送包的數(shù)目,看是否很明顯超過了所在網(wǎng)絡(luò)帶寬的限制;觀察cpu的利用率,看系統(tǒng)態(tài)的CPU時間是否明顯大于用戶態(tài)的CPU時間;如果程序運行在 Solaris 10平臺上,可以用dtrace工具看系統(tǒng)調(diào)用的情況,如果觀察到 read/write的系統(tǒng)調(diào)用的次數(shù)或者運行時間遙遙領(lǐng)先;這些都指向由于網(wǎng)絡(luò)帶寬所限導(dǎo)致的網(wǎng)絡(luò)瓶頸。另外一種出現(xiàn) Wait on condition的常見情況是該線程在 sleep,等待 sleep的時間到了,將被喚醒。

Waiting for Monitor Entry and in Object.wait()
The thread is waiting to get the lock for an object (some other thread may be holding the lock). This happens if two or more threads try to execute synchronized code. Note that the lock is always for an object and not for individual methods.
在多線程的 JAVA程序中,實現(xiàn)線程之間的同步,就要說說 Monitor。 Monitor是Java中用以實現(xiàn)線程之間的互斥與協(xié)作的主要手段,它可以看成是對象或者 Class的鎖。每一個對象都有,也僅有一個 monitor。每個 Monitor在某個時刻,只能被一個線程擁有,該線程就是 “ActiveThread”,而其它線程都是 “Waiting Thread”,分別在兩個隊列 “Entry Set” 和 “Wait Set”里面等候。在 “Entry Set”中等待的線程狀態(tài)是 “Waiting for monitor entry”,而在 “Wait Set”中等待的線程狀態(tài)是“in Object.wait()”。
先看 “Entry Set”里面的線程。我們稱被 synchronized保護(hù)起來的代碼段為臨界區(qū)。當(dāng)一個線程申請進(jìn)入臨界區(qū)時,它就進(jìn)入了 “Entry Set”隊列。對應(yīng)的 code就像:

synchronized(obj) {
    .........
}

這時有兩種可能性:

  1. 該 monitor不被其它線程擁有, Entry Set里面也沒有其它等待線程。本線程即成為相應(yīng)類或者對象的 Monitor的 Owner,執(zhí)行臨界區(qū)的代碼。
  2. 該 monitor被其它線程擁有,本線程在 Entry Set隊列中等待。
    在第一種情況下,線程將處于 “Runnable”的狀態(tài),而第二種情況下,線程 DUMP會顯示處于 “waiting for monitor entry”。

臨界區(qū)的設(shè)置,是為了保證其內(nèi)部的代碼執(zhí)行的原子性和完整性。但是因為臨界區(qū)在任何時間只允許線程串行通過,這和我們多線程的程序的初衷是相反的。如果在多線程的程序中,大量使用 synchronized,或者不適當(dāng)?shù)氖褂昧怂?,會造成大量線程在臨界區(qū)的入口等待,造成系統(tǒng)的性能大幅下降。如果在線程 DUMP中發(fā)現(xiàn)了這個情況,應(yīng)該審查源碼,改進(jìn)程序。

再看“Wait Set”里面的線程。當(dāng)線程獲得了 Monitor,進(jìn)入了臨界區(qū)之后,如果發(fā)現(xiàn)線程繼續(xù)運行的條件沒有滿足,它則調(diào)用對象(一般就是被 synchronized 的對象)的 wait() 方法,放棄 Monitor,進(jìn)入 “Wait Set”隊列。只有當(dāng)別的線程在該對象上調(diào)用了 notify() 或者 notifyAll(),“Wait Set”隊列中線程才得到機(jī)會去競爭,但是只有一個線程獲得對象的Monitor,恢復(fù)到運行態(tài)。在 “Wait Set”中的線程, DUMP中表現(xiàn)為: in Object.wait()。

一般,Cpu很忙時,則關(guān)注runnable的線程,Cpu很閑時,則關(guān)注waiting for monitor entry的線程。

3.2 JVM線程介紹

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

HotSpot VM Thread

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

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

HotSpot GC Thread

當(dāng)使用HotSpot parallel GC,HotSpot VM默認(rèn)創(chuàng)建一定數(shù)目的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
……

當(dāng)面對過多GC,內(nèi)存泄露等問題時,這些是關(guān)鍵的數(shù)據(jù)。使用native id,可以將從OS/Java進(jìn)程觀測到的高CPU與這些線程關(guān)聯(lián)起來。

JNI global references count

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

JNI global references: 832

Java Heap utilization view

從jdk1.6開始在thread dump快照底部,可以找到崩潰點的內(nèi)存空間利用情況: YongGen,OldGen和PermGen。目前我測試的系統(tǒng)導(dǎo)出的thread dump,還未見到這一部分內(nèi)容(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高,響應(yīng)很慢

方案:

  • 一個請求過程中多次dump

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

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

方案:

  • 使用命令: top -H -p pid(pid為被測系統(tǒng)的進(jìn)程號),找到導(dǎo)致cpu高的線程id。
    上述Top命令找到的線程id,對應(yīng)著dump thread信息中線程的nid,只不過一個是十進(jìn)制,一個是十六進(jìn)制。

  • 在thread dump中,根據(jù)top命令查找的線程id,查找對應(yīng)的線程堆棧信息。

cpu使用率不高但是響應(yīng)很慢

方案:

  • 進(jìn)行dump,查看是否有很多thread struck在了i/o、數(shù)據(jù)庫等地方,定位瓶頸原因。
請求無法響應(yīng)

方案:

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

4.2 案例分析:

1. 死鎖:

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

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

image.png

(圖 1)

image.png

(圖2)

說明:

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

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

2.熱鎖

熱鎖,也往往是導(dǎo)致系統(tǒng)性能瓶頸的主要因素。其表現(xiàn)特征為:由于多個線程對臨界區(qū),或者鎖的競爭,可能出現(xiàn):
* 頻繁的線程的上下文切換:從操作系統(tǒng)對線程的調(diào)度來看,當(dāng)線程在等待資源而阻塞的時候,操作系統(tǒng)會將之切換出來,放到等待的隊列,當(dāng)線程獲得資源之后,調(diào)度算法會將這個線程切換進(jìn)去,放到執(zhí)行隊列中。
* 大量的系統(tǒng)調(diào)用:因為線程的上下文切換,以及熱鎖的競爭,或者臨界區(qū)的頻繁的進(jìn)出,都可能導(dǎo)致大量的系統(tǒng)調(diào)用。
* 大部分CPU開銷用在“系統(tǒng)態(tài) ”:線程上下文切換,和系統(tǒng)調(diào)用,都會導(dǎo)致 CPU在 “系統(tǒng)態(tài) ”運行,換而言之,雖然系統(tǒng)很忙碌,但是 CPU用在 “用戶態(tài) ”的比例較小,應(yīng)用程序得不到充分的 CPU資源。
* 隨著 CPU數(shù)目的增多,系統(tǒng)的性能反而下降。因為CPU數(shù)目多,同時運行的線程就越多,可能就會造成更頻繁的線程上下文切換和系統(tǒng)態(tài)的CPU開銷,從而導(dǎo)致更糟糕的性能。

上面的描述,都是一個 scalability(可擴(kuò)展性)很差的系統(tǒng)的表現(xiàn)。從整體的性能指標(biāo)看,由于線程熱鎖的存在,程序的響應(yīng)時間會變長,吞吐量會降低。

那么,怎么去了解 “熱鎖 ”出現(xiàn)在什么地方呢?一個重要的方法還是結(jié)合操作系統(tǒng)的各種工具觀察系統(tǒng)資源使用狀況,以及收集Java線程的DUMP信息,看線程都阻塞在什么方法上,了解原因,才能找到對應(yīng)的解決方法。

我們曾經(jīng)遇到過這樣的例子,程序運行時,出現(xiàn)了以上指出的各種現(xiàn)象,通過觀察操作系統(tǒng)的資源使用統(tǒng)計信息,以及線程 DUMP信息,確定了程序中熱鎖的存在,并發(fā)現(xiàn)大多數(shù)的線程狀態(tài)都是 Waitingfor monitor entry或者 Wait on monitor,且是阻塞在壓縮和解壓縮的方法上。后來采用第三方的壓縮包 javalib替代 JDK自帶的壓縮包后,系統(tǒng)的性能提高了幾倍。

五、附件:

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

線程名稱 所屬 解釋說明
Attach Listener JVM Attach Listener 線程是負(fù)責(zé)接收到外部的命令,而對該命令進(jìn)行執(zhí)行的并且吧結(jié)果返回給發(fā)送者。通常我們會用一些命令去要求jvm給我們一些反饋信息,如:java -version、jmap、jstack等等。 如果該線程在jvm啟動的時候沒有初始化,那么,則會在用戶第一次執(zhí)行jvm命令時,得到啟動。
Signal Dispatcher JVM 前面我們提到第一個Attach Listener線程的職責(zé)是接收外部jvm命令,當(dāng)命令接收成功后,會交給signal dispather 線程去進(jìn)行分發(fā)到各個不同的模塊處理命令,并且返回處理結(jié)果。 signal dispather線程也是在第一次接收外部jvm命令時,進(jìn)行初始化工作。
CompilerThread0 JVM 用來調(diào)用JITing,實時編譯裝卸class 。 通常,jvm會啟動多個線程來處理這部分工作,線程名稱后面的數(shù)字也會累加,例如:CompilerThread1
Concurrent Mark-Sweep GC Thread JVM 并發(fā)標(biāo)記清除垃圾回收器(就是通常所說的CMS GC)線程, 該線程主要針對于老年代垃圾回收。ps:啟用該垃圾回收器,需要在jvm啟動參數(shù)中加上: -XX:+UseConcMarkSweepGC
DestroyJavaVM JVM 執(zhí)行main()的線程在main執(zhí)行完后調(diào)用JNI中的 jni_DestroyJavaVM() 方法喚起DestroyJavaVM 線程。
JVM在 Jboss 服務(wù)器啟動之后,就會喚起DestroyJavaVM線程,處于等待狀態(tài),等待其它線程(java線程和native線程)退出時通知它卸載JVM。線程退出時,都會判斷自己當(dāng)前是否是整個JVM中最后一個非deamon線程,如果是,則通知DestroyJavaVM 線程卸載JVM。
ps:
擴(kuò)展一下:
1.如果線程退出時判斷自己不為最后一個非deamon線程,那么調(diào)用thread->exit(false) ,并在其中拋出thread_end事件,jvm不退出。
2.如果線程退出時判斷自己為最后一個非deamon線程,那么調(diào)用before_exit() 方法,拋出兩個事件:
事件1:thread_end 線程結(jié)束事件;
事件2:VM的death事件。
然后調(diào)用thread->exit(true) 方法,接下來把線程從active list卸下,刪除線程等等一系列工作執(zhí)行完成后,則通知正在等待的DestroyJavaVM 線程執(zhí)行卸載JVM操作。
ContainerBackgroundProcessor 線程 JBOSS 它是一個守護(hù)線程, 在jboss服務(wù)器在啟動的時候就初始化了,主要工作是定期去檢查有沒有Session過期.過期則清除.
參考:
http://liudeh-009.iteye.com/blog/1584876
Dispatcher-Thread-3 線程 Log4j Log4j具有異步打印日志的功能,需要異步打印日志的Appender都需要注冊到 AsyncAppender對象里面去,由AsyncAppender進(jìn)行監(jiān)聽,決定何時觸發(fā)日志打印操作。 AsyncAppender如果監(jiān)聽到它管轄范圍內(nèi)的Appender有打印日志的操作,則給這個Appender生成一個相應(yīng)的event,并將該event保存在一個buffuer區(qū)域內(nèi)。
Dispatcher-Thread-3線程負(fù)責(zé)判斷這個event緩存區(qū)是否已經(jīng)滿了,如果已經(jīng)滿了,則將緩存區(qū)內(nèi)的所有event分發(fā)到Appender容器里面去,那些注冊上來的Appender收到自己的event后,則開始處理自己的日志打印工作。 Dispatcher-Thread-3線程是一個守護(hù)線程。
Finalizer線程 JVM 這個線程也是在main線程之后創(chuàng)建的,其優(yōu)先級為10,主要用于在垃圾收集前,調(diào)用對象的finalize()方法;關(guān)于Finalizer線程的幾點:
1) 只有當(dāng)開始一輪垃圾收集時,才會開始調(diào)用finalize()方法;因此并不是所有對象的finalize()方法都會被執(zhí)行;
2) 該線程也是daemon線程,因此如果虛擬機(jī)中沒有其他非daemon線程,不管該線程有沒有執(zhí)行完finalize()方法,JVM也會退出;
3) JVM在垃圾收集時會將失去引用的對象包裝成Finalizer對象(Reference的實現(xiàn)),并放入ReferenceQueue,由Finalizer線程來處理;最后將該Finalizer對象的引用置為null,由垃圾收集器來回收;
4) JVM為什么要單獨用一個線程來執(zhí)行finalize()方法呢?如果JVM的垃圾收集線程自己來做,很有可能由于在finalize()方法中誤操作導(dǎo)致GC線程停止或不可控,這對GC線程來說是一種災(zāi)難;
Gang worker#0 JVM JVM 用于做新生代垃圾回收(monir gc)的一個線程。#號后面是線程編號,例如:Gang worker#1
GC Daemon JVM GC Daemon 線程是JVM為RMI提供遠(yuǎn)程分布式GC使用的,GC Daemon線程里面會主動調(diào)用System.gc()方法,對服務(wù)器進(jìn)行Full GC。 其初衷是當(dāng) RMI 服務(wù)器返回一個對象到其客戶機(jī)(遠(yuǎn)程方法的調(diào)用方)時,其跟蹤遠(yuǎn)程對象在客戶機(jī)中的使用。當(dāng)再沒有更多的對客戶機(jī)上遠(yuǎn)程對象的引用時,或者如果引用的“租借”過期并且沒有更新,服務(wù)器將垃圾回收遠(yuǎn)程對象。
不過,我們現(xiàn)在jvm啟動參數(shù)都加上了-XX:+DisableExplicitGC配置,所以,這個線程只有打醬油的份了。
IdleRemover JBOSS Jboss連接池有一個最小值, 該線程每過一段時間都會被Jboss喚起,用于檢查和銷毀連接池中空閑和無效的連接,直到剩余的連接數(shù)小于等于它的最小值。
Java2D Disposer JVM 這個線程主要服務(wù)于awt的各個組件。 說起該線程的主要工作職責(zé)前,需要先介紹一下Disposer類是干嘛的。 Disposer提供一個addRecord方法。 如果你想在一個對象被銷毀前再做一些善后工作,那么,你可以調(diào)用Disposer#addRecord方法,將這個對象和一個自定義的DisposerRecord接口實現(xiàn)類,一起傳入進(jìn)去,進(jìn)行注冊。

Disposer類會喚起“Java2D Disposer”線程,該線程會掃描已注冊的這些對象是否要被回收了,如果是,則調(diào)用該對象對應(yīng)的DisposerRecord實現(xiàn)類里面的dispose方法。

Disposer實際上不限于在awt應(yīng)用場景,只是awt里面的很多組件需要訪問很多操作系統(tǒng)資源,所以,這些組件在被回收時,需要先釋放這些資源。
InsttoolCacheScheduler_QuartzSchedulerThread Quartz InsttoolCacheScheduler_QuartzSchedulerThread是Quartz的主線程,它主要負(fù)責(zé)實時的獲取下一個時間點要觸發(fā)的觸發(fā)器,然后執(zhí)行觸發(fā)器相關(guān)聯(lián)的作業(yè) 。

原理大致如下:
Spring和Quartz結(jié)合使用的場景下,Spring IOC容器初始化時會創(chuàng)建并初始化Quartz線程池(TreadPool),并啟動它。剛啟動時線程池中每個線程都處于等待狀態(tài),等待外界給他分配Runnable(持有作業(yè)對象的線程)。
繼而接著初始化并啟動Quartz的主線程
(InsttoolCacheScheduler_QuartzSchedulerThread),該線程自啟動后就會處于等待狀態(tài)。等待外界給出工作信號之后,該主線程的run方法才實質(zhì)上開始工作。run中會獲取JobStore中下一次要觸發(fā)的作業(yè),拿到之后會一直等待到該作業(yè)的真正觸發(fā)時間,然后將該作業(yè)包裝成一個JobRunShell對象(該對象實現(xiàn)了Runnable接口,其實看是上面TreadPool中等待外界分配給他的Runnable),然后將剛創(chuàng)建的JobRunShell交給線程池,由線程池負(fù)責(zé)執(zhí)行作業(yè)。
線程池收到Runnable后,從線程池一個線程啟動Runnable,反射調(diào)用JobRunShell中的run方法,run方法執(zhí)行完成之后, TreadPool將該線程回收至空閑線程中。
InsttoolCacheScheduler_Worker-2 Quartz InsttoolCacheScheduler_Worker-2線程就是ThreadPool線程的一個簡單實現(xiàn),它主要負(fù)責(zé)分配線程資源去執(zhí)行

InsttoolCacheScheduler_QuartzSchedulerThread線程交給它的調(diào)度任務(wù)(也就是JobRunShell)。
JBossLifeThread Jboss Jboss主線程啟動成功,應(yīng)用程序部署完畢之后將JBossLifeThread線程實例化并且start,JBossLifeThread線程啟動成功之后就處于等待狀態(tài),以保持Jboss Java進(jìn)程處于存活中。 所得比較通俗一點,就是Jboss啟動流程執(zhí)行完畢之后,為什么沒有結(jié)束? 就是因為有這個線程hold主了它。
JBoss System Threads(1)-1 Jboss 該線程是一個socket服務(wù),默認(rèn)端口號為: 1099。
主要用于接收外部naming service(Jboss JNDI)請求。
JCA PoolFiller Jboss 該線程主要為JBoss內(nèi)部提供連接池的托管。
簡單介紹一下工作原理 :
Jboss內(nèi)部凡是有遠(yuǎn)程連接需求的類,都需要實現(xiàn)ManagedConnectionFactory接口,例如需要做JDBC連接的XAManagedConnectionFactory對象,就實現(xiàn)了該接口。然后將XAManagedConnectionFactory對象,還有其它信息一起包裝到InternalManagedConnectionPool對象里面,接著將InternalManagedConnectionPool交給PoolFiller對象里面的列隊進(jìn)行管理。 JCA PoolFiller線程會定期判斷列隊內(nèi)是否有需要創(chuàng)建和管理的InternalManagedConnectionPool對象,如果有的話,則調(diào)用該對象的fillToMin方法, 觸發(fā)它去創(chuàng)建相應(yīng)的遠(yuǎn)程連接,并且將這個連接維護(hù)到它相應(yīng)的連接池里面去。
JDWP Event Helper Thread JVM JDWP是通訊交互協(xié)議,它定義了調(diào)試器和被調(diào)試程序之間傳遞信息的格式。它詳細(xì)完整地定義了請求命令、回應(yīng)數(shù)據(jù)和錯誤代碼,保證了前端和后端的JVMTI和JDI的通信通暢。 該線程主要負(fù)責(zé)將JDI事件映射成JVMTI信號,以達(dá)到調(diào)試過程中操作JVM的目的。
JDWP Transport Listener: dt_socket JVM 該線程是一個Java Debugger的監(jiān)聽器線程,負(fù)責(zé)受理客戶端的debug請求。 通常我們習(xí)慣將它的監(jiān)聽端口設(shè)置為8787。
Low Memory Detector JVM 這個線程是負(fù)責(zé)對可使用內(nèi)存進(jìn)行檢測,如果發(fā)現(xiàn)可用內(nèi)存低,分配新的內(nèi)存空間。
process reaper JVM 該線程負(fù)責(zé)去執(zhí)行一個 OS 命令行的操作。
Reference Handler JVM JVM在創(chuàng)建main線程后就創(chuàng)建Reference Handler線程,其優(yōu)先級最高,為10,它主要用于處理引用對象本身(軟引用、弱引用、虛引用)的垃圾回收問題 。
Surrogate Locker Thread (CMS) JVM 這個線程主要用于配合CMS垃圾回收器使用,它是一個守護(hù)線程,其主要負(fù)責(zé)處理GC過程中,Java層的Reference(指軟引用、弱引用等等)與jvm 內(nèi)部層面的對象狀態(tài)同步。 這里對它們的實現(xiàn)稍微做一下介紹:這里拿 WeakHashMap做例子,將一些關(guān)鍵點先列出來(我們后面會將這些關(guān)鍵點全部串起來):
1. 我們知道HashMap用Entry[]數(shù)組來存儲數(shù)據(jù)的,WeakHashMap也不例外, 內(nèi)部有一個Entry[]數(shù)組。
2. WeakHashMap的Entry比較特殊,它的繼承體系結(jié)構(gòu)為Entry->WeakReference->Reference 。
3.Reference 里面有一個全局鎖對象:Lock,它也被稱為pending_lock.注意:它是靜態(tài)對象。
4. Reference 里面有一個靜態(tài)變量:pending。
5. Reference里面有一個靜態(tài)內(nèi)部類:ReferenceHandler的線程,它在static塊里面被初始化并且啟動,啟動完成后處于wait狀態(tài),它在一個Lock同步鎖模塊中等待。
6.另外,WeakHashMap里面還實例化了一個ReferenceQueue列隊,這個列隊的作用,后面會提到。
7.上面關(guān)鍵點就介紹完畢了,下面我們把他們串起來。
假設(shè),WeakHashMap對象里面已經(jīng)保存了很多對象的引用。
JVM 在進(jìn)行CMS GC的時候,會創(chuàng)建一個ConcurrentMarkSweepThread(簡稱CMST)線程去進(jìn)行GC,ConcurrentMarkSweepThread線程被創(chuàng)建的同時會創(chuàng)建一個SurrogateLockerThread(簡稱SLT)線程并且啟動它,SLT啟動之后,處于等待階段。CMST開始GC時,會發(fā)一個消息給SLT讓它去獲取Java層Reference對象的全局鎖:Lock。 直到CMS GC完畢之后,JVM 會將WeakHashMap中所有被回收的對象所屬的WeakReference容器對象放入到Reference 的pending屬性當(dāng)中(每次GC完畢之后,pending屬性基本上都不會為null了),然后通知SLT釋放并且notify全局鎖:Lock。此時激活了ReferenceHandler線程的run方法,使其脫離wait狀態(tài),開始工作了。ReferenceHandler這個線程會將pending中的所有WeakReference對象都移動到它們各自的列隊當(dāng)中,比如當(dāng)前這個WeakReference屬于某個WeakHashMap對象,那么它就會被放入相應(yīng)的ReferenceQueue列隊里面(該列隊是鏈表結(jié)構(gòu))。 當(dāng)我們下次從WeakHashMap對象里面get、put數(shù)據(jù)或者調(diào)用size方法的時候,WeakHashMap就會將ReferenceQueue列隊中的WeakReference依依poll出來去和Entry[]數(shù)據(jù)做比較,如果發(fā)現(xiàn)相同的,則說明這個Entry所保存的對象已經(jīng)被GC掉了,那么將Entry[]內(nèi)的Entry對象剔除掉。
taskObjectTimerFactory JVM 顧名思義,該線程就是用來執(zhí)行任務(wù)的。 當(dāng)我們把一個認(rèn)為交給Timer對象,并且告訴它執(zhí)行時間,周期時間后,Timer就會將該任務(wù)放入任務(wù)列隊,并且通知taskObjectTimerFactory線程去處理任務(wù),taskObjectTimerFactory線程會將狀態(tài)為取消的任務(wù)從任務(wù)列隊中移除,如果任務(wù)是非重復(fù)執(zhí)行類型的,則在執(zhí)行完該任務(wù)后,將它從任務(wù)列隊中移除,如果該任務(wù)是需要重復(fù)執(zhí)行的,則計算出它下一次執(zhí)行的時間點。
VM Periodic Task Thread JVM 該線程是JVM周期性任務(wù)調(diào)度的線程,它由WatcherThread創(chuàng)建,是一個單例對象。 該線程在JVM內(nèi)使用得比較頻繁,比如:定期的內(nèi)存監(jiān)控、JVM運行狀況監(jiān)控,還有我們經(jīng)常需要去執(zhí)行一些jstat 這類命令查看gc的情況,如下:
jstat -gcutil 23483 250 7 這個命令告訴jvm在控制臺打印PID為:23483的gc情況,間隔250毫秒打印一次,一共打印7次。
VM Thread JVM 這個線程就比較牛b了,是jvm里面的線程母體,根據(jù)hotspot源碼(vmThread.hpp)里面的注釋,它是一個單例的對象(最原始的線程)會產(chǎn)生或觸發(fā)所有其他的線程,這個單個的VM線程是會被其他線程所使用來做一些VM操作(如,清掃垃圾等)。
在 VMThread 的結(jié)構(gòu)體里有一個VMOperationQueue列隊,所有的VM線程操作(vm_operation)都會被保存到這個列隊當(dāng)中,VMThread 本身就是一個線程,它的線程負(fù)責(zé)執(zhí)行一個自輪詢的loop函數(shù)(具體可以參考:
VMThread.cpp里面的void VMThread::loop()) ,該loop函數(shù)從VMOperationQueue列隊中按照優(yōu)先級取出當(dāng)前需要執(zhí)行的操作對象(VM_Operation),并且調(diào)用VM_Operation->evaluate函數(shù)去執(zhí)行該操作類型本身的業(yè)務(wù)邏輯。
ps:VM操作類型被定義在vm_operations.hpp文件內(nèi),列舉幾個: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

原文鏈接:https://blog.csdn.net/rachel_luo/java/article/details/8920596

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容