jstack
jstack
是
java虛拟機
自帶的一種
堆棧跟蹤工具
。
jstack
用于列印出給定的
java程序ID
或
core file
或
遠端調試服務
的Java堆棧資訊。
jstack主要用于生成java虛拟機目前時刻的線程快照,線程快照是目前java虛拟機内每一條線程正在執行的方法堆棧的集合,生成線程快照的主要目的是定位線程出現長時間停頓的原因,如線程間死鎖、死循環、請求外部資源導緻的長時間等待等。
線程出現停頓的時候通過jstack來檢視各個線程的調用堆棧,就可以知道沒有響應的線程到底在背景做什麼事情,或者等待什麼資源。
top指令
在linux環境下,可以通過
top
指令檢視各個程序的cpu使用情況,預設按cpu使用率排序。
jstack指令
通過top指令定位到cpu占用率較高的線程之後,繼續使用
jstack pid
指令檢視目前java程序的堆棧狀态。
jstack指令生成的thread dump資訊包含了JVM中所有存活的線程,為了分析指定線程,必須找出對應線程的調用棧,應該如何找?
在top指令中,已經擷取到了占用cpu資源較高的線程pid,将該pid轉成16進制的值,在thread dump中每個線程都有一個nid,找到對應的nid即可;隔段時間再執行一次stack指令擷取thread dump,區分兩份dump是否有差别。
線程5種狀态
建立狀态(New) 新建立了一個線程對象。
就緒狀态(Runnable) 線程對象建立後,其他線程調用了該對象的start()方法。該狀态的線程位于可運作線程池中,變得可運作,等待擷取CPU的使用權。
運作狀态(Running) 就緒狀态的線程擷取了CPU,執行程式代碼。
阻塞狀态(Blocked) 阻塞狀态是線程因為某種原因放棄CPU使用權,暫時停止運作。直到線程進入就緒狀态,才有機會轉到運作狀态。
阻塞的情況分三種:
- 等待阻塞:運作的線程執行wait()方法,JVM會把該線程放入等待池中。
- 同步阻塞:運作的線程在擷取對象的同步鎖時,若該同步鎖被别的線程占用,則JVM會把該線程放入鎖池中。
- 其他阻塞:運作的線程執行sleep()或join()方法,或者發出了I/O請求時,JVM會把該線程置為阻塞狀态。當sleep()狀态逾時、join()等待線程終止或者逾時、或者I/O處理完畢時,線程重新轉入就緒狀态。
死亡狀态(Dead):線程執行完了或者因異常退出了run()方法,該線程結束生命周期。
通過thread dump分析線程狀态
基于thead dump分析目前各個線程的運作情況,如是否存在死鎖、是否存在一個線程長時間持有鎖不放等等。
在dump中,線程一般存在如下幾種狀态:
- RUNNABLE,線程運作中或I/O等待
- BLOCKED,線程被阻塞,在等待monitor鎖(synchronized關鍵字)
- TIMED_WAITING 線程在等待喚醒,但設定了時限
- WAITING 線程在無限等待喚醒
log4j 1.X版本引發線程blocked死鎖問題(synchronized同步鎖)
線程處于BLOCK狀态。
- 該線程是blocked在了log4j.Category.callAppenders上,顯然可以發現這是個log4j的問題。
- 解決方法:使用log4j2或者使用slf4j替代直接使用log4j
wait挂起線程
線程1和2都處于WAITING狀态
- 線程1和2都是先
,再locked <0x000000076bf62500>
,之是以先鎖再等同一個對象,是因為wait方法需要先通過synchronized獲得該位址對象的monitor;waiting on <0x000000076bf62500>
-
說明線程執行了wait方法之後,釋放了monitor,進入到"Wait Set"隊列,等待其它線程執行位址為0x000000076bf62500對象的notify方法,并喚醒自己waiting on <0x000000076bf62500>
連接配接池
線程處于WAITING狀态
- 多執行幾次jstack可以發現大約有部分的線程處于waitting狀态,該狀态表明該線程正在執行obj.wait()方法,放棄了 Monitor,進入 “Wait Set”隊列,為什麼阻塞住呢,繼續往下看堆棧資訊,可以看到該線程正在做borrowobject操作,可以大概看到這裡是一個資料庫連接配接池的相關操作,可以适當調整資料庫連接配接池大小