Java內存泄漏分析系列之二:jstack生成的Thread Dump日誌結構解析

原文地址:http://www.javatang.comhtml

一個典型的thread dump文件主要由一下幾個部分組成:java


上圖將JVM上的線程堆棧信息和線程信息作了詳細的拆解。數組

第一部分:Full thread dump identifier

這一部分是內容最開始的部分,展現了快照文件的生成時間和JVM的版本信息。tomcat

2017-10-19 10:46:44
Full thread dump Java HotSpot(TM) 64-Bit Server VM (24.79-b02 mixed mode):

第二部分:Java EE middleware, third party & custom application Threads

這是整個文件的核心部分,裏面展現了JavaEE容器(如tomcat、resin等)、本身的程序中所使用的線程信息。這一部分詳細的含義見 Java內存泄漏分析系列之四:jstack生成的Thread Dump日誌線程狀態分析bash

"resin-22129" daemon prio=10 tid=0x00007fbe5c34e000 nid=0x4cb1 waiting on condition [0x00007fbe4ff7c000]
   java.lang.Thread.State: WAITING (parking)
    at sun.misc.Unsafe.park(Native Method)
    at java.util.concurrent.locks.LockSupport.park(LockSupport.java:315)
    at com.caucho.env.thread2.ResinThread2.park(ResinThread2.java:196)
    at com.caucho.env.thread2.ResinThread2.runTasks(ResinThread2.java:147)
    at com.caucho.env.thread2.ResinThread2.run(ResinThread2.java:118)

第三部分:HotSpot VM Thread

這一部分展現了JVM內部線程的信息,用於執行內部的原生操做。下面常見的集中內置線程:併發

"Attach Listener"

該線程負責接收外部命令,執行該命令並把結果返回給調用者,此種類型的線程一般在桌面程序中出現。app

"Attach Listener" daemon prio=5 tid=0x00007fc6b6800800 nid=0x3b07 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"DestroyJavaVM"

執行main()的線程在執行完以後調用JNI中的 jni_DestroyJavaVM() 方法會喚起DestroyJavaVM 線程。在JBoss啓動以後,也會喚起DestroyJavaVM線程,處於等待狀態,等待其它線程(java線程和native線程)退出時通知它卸載JVM。jvm

"DestroyJavaVM" prio=5 tid=0x00007fc6b3001000 nid=0x1903 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Service Thread"

用於啓動服務的線程ide

"Service Thread" daemon prio=10 tid=0x00007fbea81b3000 nid=0x5f2 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"CompilerThread"

用來調用JITing,實時編譯裝卸CLASS。一般JVM會啓動多個線程來處理這部分工做,線程名稱後面的數字也會累加,好比CompilerThread1。函數

"C2 CompilerThread1" daemon prio=10 tid=0x00007fbea814b000 nid=0x5f1 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" daemon prio=10 tid=0x00007fbea8142000 nid=0x5f0 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Signal Dispatcher"

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

"Signal Dispatcher" daemon prio=10 tid=0x00007fbea81bf800 nid=0x5ef runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Finalizer"

這個線程也是在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線程來講是一種災難。

"Finalizer" daemon prio=10 tid=0x00007fbea80da000 nid=0x5eb in Object.wait() [0x00007fbeac044000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:135)
    - locked <0x00000006d173c1a8> (a java.lang.ref.ReferenceQueue$Lock)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:151)
    at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)

"Reference Handler"

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

"Reference Handler" daemon prio=10 tid=0x00007fbea80d8000 nid=0x5ea in Object.wait() [0x00007fbeac085000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    at java.lang.Object.wait(Object.java:503)
    at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:133)
    - locked <0x00000006d173c1f0> (a java.lang.ref.Reference$Lock)

"VM Thread"

JVM中線程的母體,根據HotSpot源碼中關於vmThread.hpp裏面的註釋,它是一個單例的對象(最原始的線程)會產生或觸發全部其餘的線程,這個單例的VM線程是會被其餘線程所使用來作一些VM操做(如清掃垃圾等)。
在 VM Thread 的結構體裏有一個VMOperationQueue列隊,全部的VM線程操做(vm_operation)都會被保存到這個列隊當中,VMThread 自己就是一個線程,它的線程負責執行一個自輪詢的loop函數(具體能夠參考:VMThread.cpp裏面的void VMThread::loop()) ,該loop函數從VMOperationQueue列隊中按照優先級取出當前須要執行的操做對象(VM_Operation),而且調用VM_Operation->evaluate函數去執行該操做類型自己的業務邏輯。
VM操做類型被定義在vm_operations.hpp文件內,列舉幾個:ThreadStop、ThreadDump、PrintThreads、GenCollectFull、GenCollectFullConcurrent、CMS_Initial_Mark、CMS_Final_Remark….. 有興趣的同窗,能夠本身去查看源文件。

"VM Thread" prio=10 tid=0x00007fbea80d3800 nid=0x5e9 runnable

第四部分:HotSpot GC Thread

JVM中用於進行資源回收的線程,包括如下幾種類型的線程:

"VM Periodic Task Thread"

該線程是JVM週期性任務調度的線程,它由WatcherThread建立,是一個單例對象。該線程在JVM內使用得比較頻繁,好比:按期的內存監控、JVM運行情況監控。

"VM Periodic Task Thread" prio=10 tid=0x00007fbea82ae800 nid=0x5fa waiting on condition

可使用jstat 命令查看GC的狀況,好比查看某個進程沒有存活必要的引用可使用命令 jstat -gcutil <pid> 250 7 參數中pid是進程id,後面的250和7表示每250毫秒打印一次,總共打印7次。
這對於防止由於應用代碼中直接使用native庫或者第三方的一些監控工具的內存泄漏有很是大的幫助。

"GC task thread#0 (ParallelGC)"

垃圾回收線程,該線程會負責進行垃圾回收。一般JVM會啓動多個線程來處理這個工做,線程名稱中#後面的數字也會累加。

"GC task thread#0 (ParallelGC)" prio=5 tid=0x00007fc6b480d000 nid=0x2503 runnable

"GC task thread#1 (ParallelGC)" prio=5 tid=0x00007fc6b2812000 nid=0x2703 runnable

"GC task thread#2 (ParallelGC)" prio=5 tid=0x00007fc6b2812800 nid=0x2903 runnable

"GC task thread#3 (ParallelGC)" prio=5 tid=0x00007fc6b2813000 nid=0x2b03 runnable

若是在JVM中增長了 -XX:+UseConcMarkSweepGC 參數將會啓用CMS (Concurrent Mark-Sweep)GC Thread方式,如下是該模式下的線程類型:

"Gang worker#0 (Parallel GC Threads)"

原來垃圾回收線程GC task thread#0 (ParallelGC) 被替換爲 Gang worker#0 (Parallel GC Threads)。Gang worker 是JVM用於年輕代垃圾回收(minor gc)的線程。

"Gang worker#0 (Parallel GC Threads)" prio=10 tid=0x00007fbea801b800 nid=0x5e4 runnable 

"Gang worker#1 (Parallel GC Threads)" prio=10 tid=0x00007fbea801d800 nid=0x5e7 runnable 

"Concurrent Mark-Sweep GC Thread"

併發標記清除垃圾回收器(就是一般所說的CMS GC)線程, 該線程主要針對於年老代垃圾回收。

"Concurrent Mark-Sweep GC Thread" prio=10 tid=0x00007fbea8073800 nid=0x5e8 runnable 

"Surrogate Locker Thread (Concurrent GC)"

此線程主要配合CMS垃圾回收器來使用,是一個守護線程,主要負責處理GC過程當中Java層的Reference(指軟引用、弱引用等等)與jvm 內部層面的對象狀態同步。

"Surrogate Locker Thread (Concurrent GC)" daemon prio=10 tid=0x00007fbea8158800 nid=0x5ee waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

這裏以 WeakHashMap 爲例進行說明,首先是一個關鍵點:

  • WeakHashMap和HashMap同樣,內部有一個Entry[]數組;
  • WeakHashMap的Entry比較特殊,它的繼承體系結構爲Entry->WeakReference->Reference;
  • Reference 裏面有一個全局鎖對象:Lock,它也被稱爲pending_lock,注意:它是靜態對象;
  • Reference 裏面有一個靜態變量:pending;
  • Reference 裏面有一個靜態內部類:ReferenceHandler的線程,它在static塊裏面被初始化而且啓動,啓動完成後處於wait狀態,它在一個Lock同步鎖模塊中等待;
  • WeakHashMap裏面還實例化了一個ReferenceQueue列隊

假設,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對象剔除掉。

第五部分:JNI global references count

這一部分主要回收那些在native代碼上被引用,但在java代碼中卻沒有存活必要的引用,對於防止由於應用代碼中直接使用native庫或第三方的一些監控工具的內存泄漏有很是大的幫助。

JNI global references: 830

下一篇文章將要講述一個直接找出CPU 100%線程的例子。

相關文章
相關標籤/搜索