一蝠筑、命令介紹
jstack是jdk自帶的jvm分析工具肖卧,用于打印指定 java進(jìn)程,core文件 或者遠(yuǎn)程 調(diào)試服務(wù) 的java線程棧信息沿盅,從而分析java程序性能不佳或者崩潰的問題。另外該命令是實(shí)驗(yàn)性的纫溃,不被支持嗡呼。
jstack命令非常簡單,使用自描述的幫助文檔皇耗,可以快速掌握其使用方法:
jstack -help
Usage:
jstack [-l] <pid>
(to connect to running process)
jstack -F [-m] [-l] <pid>
(to connect to a hung process)
jstack [-m] [-l] <executable> <core>
(to connect to a core file)
jstack [-m] [-l] [server_id@]<remote server IP or hostname>
(to connect to a remote debug server)
Options:
-F to force a thread dump. Use when jstack <pid> does not respond (process is hung)
-m to print both java and native frames (mixed mode)
-l long listing. Prints additional information about locks
-h or -help to print this help message
如上南窗,jstack命令作用的對(duì)象有三種,不同對(duì)象命令格式如下:
-
進(jìn)程: 使用
jstack [-l] <pid>
來連接到正在運(yùn)行的進(jìn)程郎楼,使用jstack -F [-m] [-l] <pid>
來連接到掛起的進(jìn)程万伤。 -
core/executable: core文件是Linux下程序不正常退出產(chǎn)生的文件;executable文件是可產(chǎn)生core dump文件的java可執(zhí)行程序呜袁;使用
jstack [-m] [-l] <executable> <core>
命令敌买。 -
遠(yuǎn)程服務(wù):
jstack [-m] [-l] [server_id@]<remote server IP or hostname>
連接遠(yuǎn)程服務(wù)。
命令詳情參考: java 8關(guān)于jstack命令的介紹
snapshoot 快照
一個(gè)活躍的java程序阶界,其虛擬機(jī)內(nèi)的線程也是活躍的虹钮,不斷新建銷毀,在各個(gè)線程狀態(tài)之間轉(zhuǎn)移膘融。jstack命令芙粱,其實(shí)是對(duì)命令執(zhí)行時(shí)刻的虛擬機(jī)線程集合做一個(gè)快照,包含了當(dāng)前時(shí)刻虛擬機(jī)內(nèi)所有線程方法棧氧映。便于通過線程方法棧的行為春畔,定位程序性能不佳或者崩潰問題。這些行為主要是線程之間的同步岛都,如死鎖律姨,死循環(huán),等待外部資源競(jìng)爭鎖的行為臼疫。通過分析離線文件或者附著到正在運(yùn)行的java進(jìn)程择份,定位問題。
一窺方法棧
一段線程方法棧信息如下:
"localhost-startStop-1-SendThread(10.0.24.14:2181)" daemon prio=10 tid=0x00002b0ee8b4e000 nid=0x4b37 waiting for monitor entry [0x00002b0ed5162000]
java.lang.Thread.State: BLOCKED (on object monitor)
at org.apache.log4j.Category.callAppenders(Category.java:204)
- waiting to lock <0x00000000db301138> (a org.apache.log4j.spi.RootLogger)
at org.apache.log4j.Category.forcedLog(Category.java:391)
at org.apache.log4j.Category.log(Category.java:856)
at org.slf4j.impl.Log4jLoggerAdapter.info(Log4jLoggerAdapter.java:305)
at org.apache.zookeeper.ClientCnxn$SendThread.run(ClientCnxn.java:1156)
跟普通的java方法調(diào)用棧很相似烫堤,但是又多了些額外的信息荣赶,下面重點(diǎn)介紹這些額外的信息凤价。
二、線程狀態(tài)
jstack打印出的線程狀態(tài)與java線程狀態(tài)很相似讯壶,但并不是嚴(yán)格意義上相同。
java線程的狀態(tài)圖大概如下:
- NEW: 實(shí)例化Thread類后的線程對(duì)象湾盗,不可執(zhí)行伏蚊。
- RUNNABLE: 調(diào)用線程的start()方法后;等待CPU時(shí)間片格粪。
- RUNNING: 獲得CPU時(shí)間片躏吊,正在執(zhí)行。
- WAITING: 線程等待其他線程執(zhí)行特定操作帐萎,等待時(shí)間不確定比伏。
- TIMED_WAITING: 線程等待其他線程執(zhí)行特定操作,等待時(shí)間確定疆导。
- BLOCKED: 進(jìn)入同步方法或者同步代碼塊赁项,沒有獲取到鎖,進(jìn)入該狀態(tài)澈段。
- TERMINATED: 線程執(zhí)行完畢悠菜,或者拋出未處理的異常,線程結(jié)束败富。
實(shí)際上悔醋,Thread的內(nèi)部枚舉類 java.lang.Thread.State
也對(duì)java線程狀態(tài)做了介紹,其中沒有 RUNNING
狀態(tài)兽叮,以上是考慮CPU調(diào)度引入的這個(gè)狀態(tài)芬骄。
jstack導(dǎo)出的線程方法棧狀態(tài)也是以java線程狀態(tài)為準(zhǔn),不過鹦聪,jvm中的實(shí)際線程狀態(tài)不包括NEW以及TERMINATED账阻,只包括:
- RUNNABLE
- WAITING
- TIMED_WAITING
- BLOCKED
Monitor機(jī)制
操作系統(tǒng)為支持進(jìn)程/線程間的同步,提供了一些基本的同步原語泽本,其中semaphore
信號(hào)量 和 mutex
互斥量是其中最重要的同步原語宰僧。但是使用基礎(chǔ)同步原語控制并發(fā)時(shí),程序員必須維護(hù)繁瑣的細(xì)節(jié)观挎,何時(shí)應(yīng)該加鎖琴儿,何時(shí)應(yīng)該喚醒進(jìn)程/線程;為便于開發(fā)并發(fā)程序嘁捷,一些高級(jí)語言支持了Monitor機(jī)制造成,類似語法糖,操作系統(tǒng)本身不支持Monitor雄嚣,其實(shí)現(xiàn)依賴基礎(chǔ)同步原語晒屎。
具體到高級(jí)語言Java喘蟆,synchronized ,Object(wait, notify/notifyAll)等元素提供了對(duì)Monitor機(jī)制的支持鼓鲁。直觀的代碼體現(xiàn)是 同步方法 或者 同步代碼塊(雖然java的鎖機(jī)制也提供線程同步蕴轨,但鎖機(jī)制與Monitor機(jī)制是不同的)。
下圖描述了線程狀態(tài)轉(zhuǎn)移與Monitor的關(guān)系:
整個(gè)monitor分為三個(gè)區(qū)域骇吭,處于不同區(qū)域的線程有不同的狀態(tài):
- Entry Set(進(jìn)入?yún)^(qū)):線程欲獲取鎖橙弱,獲取鎖成功,進(jìn)入擁有者區(qū)域燥狰,否者在進(jìn)入?yún)^(qū)等待鎖棘脐;鎖釋放后,重新參與競(jìng)爭鎖龙致。
- The Owner(擁有者):線程成功獲得鎖蛀缝。
- Wait Set(等待區(qū)):由于必要條件不滿足,線程通過調(diào)用對(duì)象的wait方法目代,釋放鎖屈梁,并進(jìn)入等待區(qū)等待被notify/notifyAll喚醒。
Monitor機(jī)制中榛了,有且僅有一個(gè)線程可以成為Monitor的擁有者俘闯,這是這個(gè)線程是 An Active Thread;處于 Entry Set以及Wait Set的線程都是 Waiting Thread忽冻。
三真朗、線程轉(zhuǎn)儲(chǔ)堆棧分析
一條典型的jstack線程棧格式如下:
"線程名" [daemon] prio= os_prio= tid= nid= 線程動(dòng)作 [線程棧的起始地址]
java.lang.Thread.State:線程狀態(tài) [(進(jìn)入該狀態(tài)的原因)]
方法調(diào)用棧
[-調(diào)用修飾]
Locked ownable synchronizers:
- <地址> (可持有同步器對(duì)象)
第一行說明線程相關(guān)信息,包括:
- 線程名僧诚。
- 是否守護(hù)線程遮婶,daemon標(biāo)識(shí),非守護(hù)線程沒有湖笨。
- 線程優(yōu)先級(jí)旗扑。
- 線程操作系統(tǒng)優(yōu)先級(jí)。
- 線程id慈省。
- 操作系統(tǒng)映射的線程id臀防,十六進(jìn)制字符串,使用這個(gè)id與實(shí)際操作系統(tǒng)線程id關(guān)聯(lián)边败。
- 線程動(dòng)作袱衷。
- 線程棧的起始地址。
線程動(dòng)作
需要特別說明的是笑窜,線程動(dòng)作致燥,它提供的額外信息利于定位問題;線程動(dòng)作包括:
- runnable: 線程可執(zhí)行排截,對(duì)應(yīng)的線程狀態(tài)一般為RUNNABLE, 也有例外對(duì)應(yīng)TIMED_WAITING嫌蚤。
- waiting on condition: 調(diào)用park阻塞辐益、等待區(qū)等待。
- waiting for monitor entry: 處于Monitor Entry Set脱吱,對(duì)應(yīng)的線程狀態(tài)一般是BLOCKED智政。
- in Object.wait(): 處于Monitor Wait Set,狀態(tài)為WAITING或TIMED_WAITING箱蝠。
- sleeping: 調(diào)用了sleep续捂,休眠。
第二行是線程的狀態(tài)抡锈,是java.lang.Thread.State中的一種疾忍;后面的括號(hào)里是進(jìn)入該狀態(tài)的原因(可選)乔外。
方法調(diào)用棧緊隨其后床三,重要的同步信息也會(huì)被輸出。
調(diào)用修飾
調(diào)用修飾是線程方法調(diào)用過程中杨幼,重要的同步信息撇簿;調(diào)用修飾包括:
- locked <地址> (目標(biāo)): 使用synchronized成功獲得對(duì)象鎖,即Monitor的擁有者差购。
- waiting to lock <地址> (目標(biāo)): 使用synchronized獲取對(duì)象鎖失敗四瘫,進(jìn)入Entry Set等待。
- waiting on <地址> (目標(biāo)): 使用synchronized獲取對(duì)象鎖成功后欲逃,必要條件不滿足找蜜,調(diào)用object.wait()進(jìn)入Wait Set等待。
- parking to wait for <地址> (目標(biāo)): 調(diào)用park稳析。
同步原語park比較特殊洗做,不屬于Monitor機(jī)制,他是鎖機(jī)制的基礎(chǔ)支持彰居。由Unsafe類的native方法park實(shí)現(xiàn)诚纸。
最后一行是指定了 -l
選項(xiàng)才會(huì)輸出的,額外的鎖信息陈惰。表示當(dāng)前線程獲得的可持有同步器畦徘。由此可見Monitor機(jī)制(synchronized系列)的得天獨(dú)厚,線程方法棧對(duì)Monitor機(jī)制的同步信息進(jìn)行了詳盡的說明抬闯。Monitor同步機(jī)制下井辆,Locked ownable synchronizers為None。由于鎖機(jī)制的Lock只是普通的java類溶握,jvm無從得知其詳盡的線程同步情況掘剪,因此使用鎖機(jī)制實(shí)現(xiàn)的線程同步,出現(xiàn)問題時(shí)奈虾,不如monitor機(jī)制的同步實(shí)現(xiàn)夺谁,不利于辨識(shí)廉赔。
三、線程動(dòng)作&調(diào)用修飾實(shí)踐
分析jstack的線程方法棧匾鸥,主要就是分析線程之間的同步信息蜡塌,以及其方法調(diào)用棧。以上詳細(xì)介紹了分析jstack的理論知識(shí)勿负,下面從實(shí)際代碼角度馏艾,分析jstack線程方法棧。
首先準(zhǔn)備好幾個(gè)同步方法奴愉,用于多線程環(huán)境下調(diào)用:
public class JStack {
// 鎖機(jī)制
static Lock lock = new ReentrantLock();
// 鎖條件對(duì)象
static Condition condition = lock.newCondition();
static boolean first = true;
/**
* Monitor機(jī)制下的同步
*/
static void monitorSync() {
synchronized (JStack.class) {
while (true) ;
}
}
/**
* Monitor機(jī)制下 條件等待
*
* @throws InterruptedException
*/
static void monitorSyncWait() throws InterruptedException {
synchronized (JStack.class) {
if (first) {
first = false;
while (!first) {
JStack.class.wait();
}
}
while (true) ;
}
}
/**
* lock機(jī)制下的同步
*/
static void lockSync() {
lock.lock();
try {
while (true) ;
} finally {
lock.unlock();
}
}
/**
* lock機(jī)制下條件等待
*
* @throws InterruptedException
*/
static void lockSyncAwait() throws InterruptedException {
lock.lock();
try {
if (first) {
first = false;
while (!first) {
condition.await();
}
}
while (true) ;
} finally {
lock.unlock();
}
}
}
1) Monitor機(jī)制琅摩,synchronized競(jìng)爭鎖
多線程synchronized競(jìng)爭鎖:
public static void main(String[] args) {
// Monitor機(jī)制,synchronized競(jìng)爭鎖
Thread monitorSync1 = new Thread(JStack::monitorSync);
monitorSync1.setName("SYNC monitor#1");
Thread monitorSync2 = new Thread(JStack::monitorSync);
monitorSync2.setName("SYNC monitor#2");
monitorSync1.start();
monitorSync2.start();
}
"SYNC monitor#2" #12 prio=5 os_prio=0 tid=0x000000001f299800 nid=0x3630 waiting for monitor entry [0x000000001fc7f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at jstack.JStack.monitorSync(JStack.java:27)
- waiting to lock <0x000000076b87f128> (a java.lang.Class for jstack.JStack)
at jstack.JStack$$Lambda$2/1989780873.run(Unknown Source)
at java.lang.Thread.run(Thread.java:745)
Locked ownable synchronizers:
- None
"SYNC monitor#1" #11 prio=5 os_prio=0 tid=0x000000001f299000 nid=0x1854 runnable [0x000000001fb7f000]
java.lang.Thread.State: RUNNABLE
at jstack.JStack.monitorSync(JStack.java:27)
- locked <0x000000076b87f128> (a java.lang.Class for jstack.JStack)
at jstack.JStack$$Lambda$1/2093631819.run(Unknown Source)
at java.lang.Thread.run(Thread.java:745)
Locked ownable synchronizers:
- None
可見锭硼,Monitor機(jī)制下房资,使用synchronized競(jìng)爭鎖,
- Monitor擁有者
SYNC monitor#1
的調(diào)用修飾是 lock檀头,線程動(dòng)作是runnable轰异,狀態(tài)是RUNNABLE - 競(jìng)爭鎖失敗的線程
SYNC monitor#2
的調(diào)用修飾是 waiting to lock,地址和目標(biāo)與擁有者線程相同暑始,線程動(dòng)作是waiting for monitor entry(在Entry Set等待)搭独,狀態(tài)是BLOCKED(在對(duì)象監(jiān)視器上阻塞)。
2) 鎖機(jī)制廊镜,競(jìng)爭鎖
多線程競(jìng)爭lock鎖:
public static void main(String[] args) {
// 鎖機(jī)制牙肝,競(jìng)爭鎖
Thread lockSync1 = new Thread(JStack::lockSync);
lockSync1.setName("SYNC lock#1");
Thread lockSync2 = new Thread(JStack::lockSync);
lockSync2.setName("SYNC lock#2");
lockSync1.start();
lockSync2.start();
}
"SYNC lock#2" #12 prio=5 os_prio=0 tid=0x000000001f1e8800 nid=0x4d8 waiting on condition [0x000000001fbde000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x000000076b8876d0> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLock.java:209)
at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
at jstack.JStack.lockSync(JStack.java:52)
at jstack.JStack$$Lambda$2/1989780873.run(Unknown Source)
at java.lang.Thread.run(Thread.java:745)
Locked ownable synchronizers:
- None
"SYNC lock#1" #11 prio=5 os_prio=0 tid=0x000000001f1e8000 nid=0x172c runnable [0x000000001fadf000]
java.lang.Thread.State: RUNNABLE
at jstack.JStack.lockSync(JStack.java:54)
at jstack.JStack$$Lambda$1/2093631819.run(Unknown Source)
at java.lang.Thread.run(Thread.java:745)
Locked ownable synchronizers:
- <0x000000076b8876d0> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
可見,鎖機(jī)制下嗤朴,鎖競(jìng)爭同步原語都是park:
- 鎖擁有鎖線程
SYNC lock#1
獲取鎖并沒有調(diào)用修飾配椭,而在Locked ownable synchronizers 指明其擁有的可持有同步器(鎖),線程動(dòng)作是runnable播赁,狀態(tài)時(shí)RUNNABLE颂郎。 - 競(jìng)爭鎖失敗線程
SYNC lock#2
的調(diào)用修飾是parking to wait for
,地址和對(duì)象正是擁有者線程持有的鎖容为,線程動(dòng)作是waiting on condition乓序,狀態(tài)是WAITING(parking),不同于Monitor未獲得鎖處于BLOCKED狀態(tài)坎背。
3) Monitor機(jī)制替劈,條件對(duì)象上等待
多線程環(huán)境下,在monitor機(jī)制的條件對(duì)象上等待:
public static void main(String[] args) {
// Monitor機(jī)制得滤,條件對(duì)象上等待
Thread monitorSyncCon1 = new Thread(() -> {
try {
monitorSyncWait();
} catch (InterruptedException e) {
// suppressed
}
});
monitorSyncCon1.setName("SYNC monitor condition#1");
Thread monitorSyncCon2 = new Thread(() -> {
try {
monitorSyncWait();
} catch (InterruptedException e) {
// suppressed
}
});
monitorSyncCon2.setName("SYNC monitor condition#2");
monitorSyncCon1.start();
monitorSyncCon2.start();
}
"SYNC monitor condition#2" #12 prio=5 os_prio=0 tid=0x000000001ed0b000 nid=0x31f8 runnable [0x000000001f6ff000]
java.lang.Thread.State: RUNNABLE
at jstack.JStack.monitorSyncWait(JStack.java:44)
- locked <0x000000076b87f348> (a java.lang.Class for jstack.JStack)
at jstack.JStack.lambda$main$1(JStack.java:110)
at jstack.JStack$$Lambda$2/1989780873.run(Unknown Source)
at java.lang.Thread.run(Thread.java:745)
Locked ownable synchronizers:
- None
"SYNC monitor condition#1" #11 prio=5 os_prio=0 tid=0x000000001ed0a000 nid=0x30d0 in Object.wait() [0x000000001f5fe000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076b87f348> (a java.lang.Class for jstack.JStack)
at java.lang.Object.wait(Object.java:502)
at jstack.JStack.monitorSyncWait(JStack.java:41)
- locked <0x000000076b87f348> (a java.lang.Class for jstack.JStack)
at jstack.JStack.lambda$main$0(JStack.java:102)
at jstack.JStack$$Lambda$1/2093631819.run(Unknown Source)
at java.lang.Thread.run(Thread.java:745)
Locked ownable synchronizers:
- None
可見陨献,Monitor機(jī)制下,在條件對(duì)象上等待:
- 在條件對(duì)象上等待的線程
SYNC monitor condition#1
, 首先locked <0x000000076b87f348>
獲得鎖成為擁有者懂更,接著主動(dòng)調(diào)用wait方法眨业,waiting on <0x000000076b87f348>
后釋放鎖急膀,在條件對(duì)象上等待,進(jìn)入Wait Set龄捡,線程動(dòng)作是in Object.wait()
卓嫂,狀態(tài)是WAITING(在對(duì)象監(jiān)視器上等待)。 - 此時(shí)成為擁有者的線程
SYNC monitor condition#2
聘殖,在線程SYNC monitor condition#1
主動(dòng)放棄鎖后locked <0x000000076b87f348>
獲得同一個(gè)鎖晨雳,線程動(dòng)作是runnable,線程狀態(tài)是RUNNABLE奸腺。
4) Lock機(jī)制餐禁,條件對(duì)象上等待
多線程環(huán)境下,競(jìng)爭lock對(duì)象的條件對(duì)象:
public static void main(String[] args) {
// Lock機(jī)制突照,條件對(duì)象上等待
Thread lockSyncCon1 = new Thread(() -> {
try {
lockSyncAwait();
} catch (InterruptedException e) {
// suppressed
}
});
lockSyncCon1.setName("SYNC lock condition#1");
Thread lockSyncCon2 = new Thread(() -> {
try {
lockSyncAwait();
} catch (InterruptedException e) {
// suppressed
}
});
lockSyncCon2.setName("SYNC monitor condition#2");
lockSyncCon1.start();
lockSyncCon2.start();
}
"SYNC monitor condition#2" #12 prio=5 os_prio=0 tid=0x000000001f1aa800 nid=0x135c waiting on condition [0x000000001fb9e000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x000000076b8894f8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
at jstack.JStack.lockSyncAwait(JStack.java:71)
at jstack.JStack.lambda$main$1(JStack.java:131)
at jstack.JStack$$Lambda$2/1989780873.run(Unknown Source)
at java.lang.Thread.run(Thread.java:745)
Locked ownable synchronizers:
- None
"SYNC lock condition#1" #11 prio=5 os_prio=0 tid=0x000000001f1aa000 nid=0x330c runnable [0x000000001fa9f000]
java.lang.Thread.State: RUNNABLE
at jstack.JStack.lockSyncAwait(JStack.java:74)
at jstack.JStack.lambda$main$0(JStack.java:123)
at jstack.JStack$$Lambda$1/2093631819.run(Unknown Source)
at java.lang.Thread.run(Thread.java:745)
Locked ownable synchronizers:
- <0x000000076b887af0> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
可見帮非,在lock條件對(duì)象上等待:
- 一開始獲得鎖的線程
SYNC monitor condition#2
,后來放棄鎖在鎖的條件對(duì)象上等待绷旗,方法調(diào)用棧中并沒有提現(xiàn)這一個(gè)過程喜鼓,只是在最后說明副砍,該線程parking to wait for <0x000000076b8894f8>
衔肢,線程動(dòng)作是waiting on condition
,狀態(tài)是WAITING (parking) - 由于線程
SYNC monitor condition#2
主動(dòng)釋放鎖而獲得鎖的線程SYNC lock condition#1
豁翎,Locked ownable synchronizers指示它獲得一個(gè)鎖角骤,處于RUNNABLE狀態(tài)。
以上心剥,wait或者await方法加上一個(gè)超時(shí)時(shí)間邦尊,WAITING狀態(tài)變?yōu)門IMED_WAITING狀態(tài)
4) sleep
try {
Thread.sleep(50_000);
} catch (InterruptedException e) {
// suppressed
}
"main" #1 prio=5 os_prio=0 tid=0x00000000036e2800 nid=0x2aa8 waiting on condition [0x00000000035df000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at jstack.JStack.main(JStack.java:141)
Locked ownable synchronizers:
- None
REFER TO
[1] linux:core文件的產(chǎn)生和調(diào)試
[2] Java 中的 Monitor 機(jī)制