公司內部同事分享的一篇文章
週末看到一個用jstack查看死鎖的例子。昨天晚上總結了一下jstack(查看線程)、jmap(查看內存)和jstat(性能分析)命令。供你們參考
1.Jstack
1.1 jstack能獲得運行java程序的java stack和native stack的信息。能夠輕鬆得知當前線程的運行狀況。以下圖所示
注:這個和thread dump是一樣的結果。可是thread dump是用kill -3 pid命令,仍是服務器上面少用kill爲妙
1.2 命名行格式
jstack [ option ] pid
jstack [ option ] executable core
jstack [ option ] [server-id@]remote-hostname-or-IP
最經常使用的仍是jstack pid
1.3 在thread dump中,要留意下面幾種狀態
死鎖,Deadlock(重點關注)
等待資源,Waiting on condition(重點關注)
• 等待獲取監視器,Waiting on monitor entry(重點關注)
阻塞,Blocked(重點關注)
• 執行中,Runnable
• 暫停,Suspended
• 對象等待中,Object.wait() 或 TIMED_WAITING
• 中止,Parked
下面有詳細的例子講這種分析,你們參考原著
http://www.cnblogs.com/zhengyun_ustc/archive/2013/01/06/dumpanalysis.html
1.4 在thread dump中,有幾種線程的定義以下
線程名稱 所屬 解釋說明
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主了它。 牛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….. 有興趣的同窗,能夠本身去查看源文件。
(搬運自 http://blog.csdn.net/a43350860/article/details/8134234 感謝原著做者)
2.Jmap
2.1 獲得運行java程序的內存分配的詳細狀況。例如實例個數,大小等
2.2 命名行格式
jmap [ option ] pid
jmap [ option ] executable core
jmap [ option ] [server-id@]remote-hostname-or-IP
-dump:[live,]format=b,file=<filename> 使用hprof二進制形式,輸出jvm的heap內容到文件=. live子選項是可選的,假如指定live選項,那麼只輸出活的對象到文件.
-finalizerinfo 打印正等候回收的對象的信息.
-heap 打印heap的概要信息,GC使用的算法,heap的配置及wise heap的使用狀況.
-histo[:live] 打印每一個class的實例數目,內存佔用,類全名信息. VM的內部類名字開頭會加上前綴」*」. 若是live子參數加上後,只統計活的對象數量.
-permstat 打印classload和jvm heap長久層的信息. 包含每一個classloader的名字,活潑性,地址,父classloader和加載的class數量. 另外,內部String的數量和佔用內存數也會打印出來.
-F 強迫.在pid沒有相應的時候使用-dump或者-histo參數. 在這個模式下,live子參數無效.
-h | -help 打印輔助信息
-J 傳遞參數給jmap啓動的jvm.
2.3 使用例子
jmap -histo pid(查看實例)
jmap -dump:format=b,file=heap.bin pid(導出內存,聽說對性能有影響,當心使用)
(format=b是經過二進制的意思,可是能不能導出文本文件我沒找到,知道的告訴我)
把內存結構所有dump到二進制文件中,經過IBM的HeapAnalyzer和eclipse的MemoryAnalyzer均可以分析內存結構。
這個是我用HeapAnalyzer查看出的咱們daily的內存結構,已經列出了可能存在的問題。(這個工具我不熟悉,只供你們參考)
下面是我用eclipse 的MemoryAnalyzer查看內存結構圖
上面的是eclipse分析內存泄漏分析出的。這個功能點很是多。能夠慢慢學習
3.Jstat
3.1 這是一個比較實用的一個命令,能夠觀察到classloader,compiler,gc相關信息。能夠時時監控資源和性能
3.2 命令格式
-class:統計class loader行爲信息
-compile:統計編譯行爲信息
-gc:統計jdk gc時heap信息
-gccapacity:統計不一樣的generations(不知道怎麼翻譯好,包括新生區,老年區,permanent區)相應的heap容量狀況
-gccause:統計gc的狀況,(同-gcutil)和引發gc的事件
-gcnew:統計gc時,新生代的狀況
-gcnewcapacity:統計gc時,新生代heap容量
-gcold:統計gc時,老年區的狀況
-gcoldcapacity:統計gc時,老年區heap容量
-gcpermcapacity:統計gc時,permanent區heap容量
-gcutil:統計gc時,heap狀況
3.3 輸出參數內容
S0 — Heap上的 Survivor space 0 區已使用空間的百分比
S0C:S0當前容量的大小
S0U:S0已經使用的大小
S1 — Heap上的 Survivor space 1 區已使用空間的百分比
S1C:S1當前容量的大小
S1U:S1已經使用的大小
E — Heap上的 Eden space 區已使用空間的百分比
EC:Eden space當前容量的大小
EU:Eden space已經使用的大小
O — Heap上的 Old space 區已使用空間的百分比
OC:Old space當前容量的大小
OU:Old space已經使用的大小
P — Perm space 區已使用空間的百分比
OC:Perm space當前容量的大小
OU:Perm space已經使用的大小
YGC — 從應用程序啓動到採樣時發生 Young GC 的次數
YGCT– 從應用程序啓動到採樣時 Young GC 所用的時間(單位秒)
FGC — 從應用程序啓動到採樣時發生 Full GC 的次數
FGCT– 從應用程序啓動到採樣時 Full GC 所用的時間(單位秒)
GCT — 從應用程序啓動到採樣時用於垃圾回收的總時間(單位秒),它的值等於YGC+FGC
例子1
例子2(連續5次)
例子3(PGCMN顯示的是最小perm的內存使用量,PGCMX顯示的是perm的內存最大使用量,PGC是當前新生成的perm內存佔用量,PC是但前perm內存佔用量)
這個工具的參數很是多,聽說基本能覆蓋jprofile等收費工具的全部功能了。多用用對於系統調優仍是頗有幫助的
注1:咱們在daily用這樣命令時,都要用-F參數的。由於咱們的用戶都不是啓動命令的用戶
注2:daily的這些命令好像都沒有配置到環境變量裏面,這個是我在本身應用機器裏面看到的。須要去jdk目錄底下執行。Sudo固然是必須的了html