hi,all:
最近抽時間把JVM運行過程中產生的一些線程進行了整理,主要是圍繞著我們系統jstack生成的文件為參照依據。 前段時間因為系統代碼問題,造成性能到了天花板,于是就dump了一份stack出來進行分析。 看stack其實也需要一定的經驗,畢竟它里面很多線程不可能都是有問題,所以,需要對他們有一定認識。 現在市面上很少有人對這一塊做整理,所以,導致很多新人在拿到一個stack文件之后,也是一頭霧水。下面我把這次整理的一些個人認為比較重要的線程列出來,供大家參考。如果發現有什么寫得不對或者可以補充的地方,也請朋友們抱著樂于分享的態度灌灌水。
(來自:阿里技術沙龍: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過期.過期則清除. |
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主了它。牛b吧~~ |
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…..有興趣的同學,可以自己去查看源文件。 |
文章列表