感覺(jué)寫的非常不錯(cuò)
前言
如果有一天北滥,你的Java程序長(zhǎng)時(shí)間停頓,也許是它病了恬汁,需要用jstack拍個(gè)片子分析分析伶椿,才能診斷具體什么病癥,是死鎖綜合征氓侧,還是死循環(huán)等其他病癥脊另,本文我們一起來(lái)學(xué)習(xí)jstack命令~
jstack 的功能
jstack用法
線程狀態(tài)等基礎(chǔ)回顧
實(shí)戰(zhàn)案例1:jstack 分析死鎖
實(shí)戰(zhàn)案例2:jstack 分析CPU 過(guò)高
jstack 的功能
jstack是JVM自帶的Java堆棧跟蹤工具,它用于打印出給定的java進(jìn)程ID甘苍、core file尝蠕、遠(yuǎn)程調(diào)試服務(wù)的Java堆棧信息.
jstack prints Java stack traces of Java threads for a given Java process or
core file or a remote debug server.
jstack命令用于生成虛擬機(jī)當(dāng)前時(shí)刻的線程快照。
線程快照是當(dāng)前虛擬機(jī)內(nèi)每一條線程正在執(zhí)行的方法堆棧的集合载庭,生成線程快照的主要目的是定位線程出現(xiàn)長(zhǎng)時(shí)間停頓的原因看彼, 如線程間死鎖、死循環(huán)囚聚、請(qǐng)求外部資源導(dǎo)致的長(zhǎng)時(shí)間等待等問(wèn)題靖榕。
線程出現(xiàn)停頓的時(shí)候通過(guò)jstack來(lái)查看各個(gè)線程的調(diào)用堆棧,就可以知道沒(méi)有響應(yīng)的線程到底在后臺(tái)做什么事情顽铸,或者等待什么資源茁计。
如果java程序崩潰生成core文件,jstack工具可以用來(lái)獲得core文件的java stack和native stack的信息谓松,從而可以輕松地知道java程序是如何崩潰和在程序何處發(fā)生問(wèn)題星压。
另外践剂,jstack工具還可以附屬到正在運(yùn)行的java程序中,看到當(dāng)時(shí)運(yùn)行的java程序的java stack和native stack的信息, 如果現(xiàn)在運(yùn)行的java程序呈現(xiàn)hung的狀態(tài)娜膘,jstack是非常有用的逊脯。
jstack用法
jstack 命令格式如下
jstack [ option ] pid
jstack [ option ] executable core
jstack [ option ] [server-id@]remote-hostname-or-IP
executable Java executable from which the core dump was produced.(可能是產(chǎn)生core dump的java可執(zhí)行程序)
core 將被打印信息的core dump文件
remote-hostname-or-IP 遠(yuǎn)程debug服務(wù)的主機(jī)名或ip
server-id 唯一id,假如一臺(tái)主機(jī)上多個(gè)遠(yuǎn)程debug服務(wù)
最常用的是
jstack [option] <pid>? // 打印某個(gè)進(jìn)程的堆棧信息
option參數(shù)說(shuō)明如下:
選項(xiàng)作用
-F當(dāng)正常輸出的請(qǐng)求不被響應(yīng)時(shí),強(qiáng)制輸出線程堆棧
-m如果調(diào)用到本地方法的話竣贪,可以顯示C/C++的堆棧
-l除堆棧外军洼,顯示關(guān)于鎖的附加信息,在發(fā)生死鎖時(shí)可以用jstack -l pid來(lái)觀察鎖持有情況
線程狀態(tài)等基礎(chǔ)回顧
線程狀態(tài)簡(jiǎn)介
jstack用于生成線程快照的演怎,我們分析線程的情況匕争,需要復(fù)習(xí)一下線程狀態(tài)吧,拿小凳子坐好爷耀,復(fù)習(xí)一下啦~
Java語(yǔ)言定義了6種線程池狀態(tài):
New:創(chuàng)建后尚未啟動(dòng)的線程處于這種狀態(tài)甘桑,不會(huì)出現(xiàn)在Dump中。
RUNNABLE:包括Running和Ready歹叮。線程開(kāi)啟start()方法扇住,會(huì)進(jìn)入該狀態(tài),在虛擬機(jī)內(nèi)執(zhí)行的盗胀。
Waiting:無(wú)限的等待另一個(gè)線程的特定操作艘蹋。
Timed Waiting:有時(shí)限的等待另一個(gè)線程的特定操作。
阻塞(Blocked):在程序等待進(jìn)入同步區(qū)域的時(shí)候票灰,線程將進(jìn)入這種狀態(tài)女阀,在等待監(jiān)視器鎖。
結(jié)束(Terminated):已終止線程的線程狀態(tài)屑迂,線程已經(jīng)結(jié)束執(zhí)行浸策。
Dump文件的線程狀態(tài)一般其實(shí)就以下3種:
RUNNABLE,線程處于執(zhí)行中
BLOCKED惹盼,線程被阻塞
WAITING庸汗,線程正在等待
Monitor 監(jiān)視鎖
因?yàn)镴ava程序一般都是多線程運(yùn)行的,Java多線程跟監(jiān)視鎖環(huán)環(huán)相扣手报,所以我們分析線程狀態(tài)時(shí)蚯舱,也需要回顧一下Monitor監(jiān)視鎖知識(shí)。
有關(guān)于線程同步關(guān)鍵字Synchronized與監(jiān)視鎖的愛(ài)恨情仇掩蛤,有興趣的伙伴可以看一下我這篇文章Synchronized解析——如果你愿意一層一層剝開(kāi)我的心
Monitor的工作原理圖如下:
線程想要獲取monitor,首先會(huì)進(jìn)入Entry Set隊(duì)列枉昏,它是Waiting Thread,線程狀態(tài)是Waiting for monitor entry揍鸟。
當(dāng)某個(gè)線程成功獲取對(duì)象的monitor后,進(jìn)入Owner區(qū)域兄裂,它就是Active Thread。
如果線程調(diào)用了wait()方法,則會(huì)進(jìn)入Wait Set隊(duì)列晰奖,它會(huì)釋放monitor鎖谈撒,它也是Waiting Thread,線程狀態(tài)in Object.wait()
如果其他線程調(diào)用 notify() / notifyAll() 匾南,會(huì)喚醒Wait Set中的某個(gè)線程港华,該線程再次嘗試獲取monitor鎖,成功即進(jìn)入Owner區(qū)域午衰。
Dump 文件分析關(guān)注重點(diǎn)
runnable,線程處于執(zhí)行中
deadlock冒萄,死鎖(重點(diǎn)關(guān)注)
blocked臊岸,線程被阻塞 (重點(diǎn)關(guān)注)
Parked,停止
locked尊流,對(duì)象加鎖
waiting帅戒,線程正在等待
waiting to lock 等待上鎖
Object.wait(),對(duì)象等待中
waiting for monitor entry 等待獲取監(jiān)視器(重點(diǎn)關(guān)注)
Waiting on condition崖技,等待資源(重點(diǎn)關(guān)注)逻住,最常見(jiàn)的情況是線程在等待網(wǎng)絡(luò)的讀寫
實(shí)戰(zhàn)案例1:jstack 分析死鎖問(wèn)題
什么是死鎖?
如何用jstack排查死鎖迎献?
什么是死鎖瞎访?
死鎖是指兩個(gè)或兩個(gè)以上的線程在執(zhí)行過(guò)程中,因爭(zhēng)奪資源而造成的一種互相等待的現(xiàn)象吁恍,若無(wú)外力作用扒秸,它們都將無(wú)法進(jìn)行下去。
如何用如何用jstack排查死鎖問(wèn)題
先來(lái)看一段會(huì)產(chǎn)生死鎖的Java程序冀瓦,源碼如下:
/**
* Java 死鎖demo
*/
public class DeathLockTest {
? ? private static Lock lock1 = new ReentrantLock();
? ? private static Lock lock2 = new ReentrantLock();
? ? public static void deathLock() {
? ? ? ? Thread t1 = new Thread() {
? ? ? ? ? ? @Override
? ? ? ? ? ? public void run() {
? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? lock1.lock();
? ? ? ? ? ? ? ? ? ? System.out.println(Thread.currentThread().getName() + " get the lock1");
? ? ? ? ? ? ? ? ? ? Thread.sleep(1000);
? ? ? ? ? ? ? ? ? ? lock2.lock();
? ? ? ? ? ? ? ? ? ? System.out.println(Thread.currentThread().getName() + " get the lock2");
? ? ? ? ? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? };
? ? ? ? Thread t2 = new Thread() {
? ? ? ? ? ? @Override
? ? ? ? ? ? public void run() {
? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? lock2.lock();
? ? ? ? ? ? ? ? ? ? System.out.println(Thread.currentThread().getName() + " get the lock2");
? ? ? ? ? ? ? ? ? ? Thread.sleep(1000);
? ? ? ? ? ? ? ? ? ? lock1.lock();
? ? ? ? ? ? ? ? ? ? System.out.println(Thread.currentThread().getName() + " get the lock1");
? ? ? ? ? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? };
? ? ? ? //設(shè)置線程名字伴奥,方便分析堆棧信息
? ? ? ? t1.setName("mythread-jay");
? ? ? ? t2.setName("mythread-tianluo");
? ? ? ? t1.start();
? ? ? ? t2.start();
? ? }
? ? public static void main(String[] args) {
? ? ? ? deathLock();
? ? }
}
運(yùn)行結(jié)果:
顯然,線程jay和線程tianluo都是只執(zhí)行到一半翼闽,就陷入了阻塞等待狀態(tài)~
jstack排查Java死鎖步驟
在終端中輸入jsp查看當(dāng)前運(yùn)行的java程序
使用 jstack -l pid 查看線程堆棧信息
分析堆棧信息
在終端中輸入jsp查看當(dāng)前運(yùn)行的java程序
通過(guò)使用 jps 命令獲取需要監(jiān)控的進(jìn)程的pid拾徙,我們找到了23780DeathLockTest
使用 jstack -l pid 查看線程堆棧信息
由上圖,可以清晰看到死鎖信息:
mythread-tianluo 等待這個(gè)鎖 “0x00000000d61ae3a0”感局,這個(gè)鎖是由mythread-jay線程持有尼啡。
mythread-jay線程等待這個(gè)鎖“0x00000000d61ae3d0”,這個(gè)鎖是由mythread-tianluo 線程持有。
還原死鎖真相
“mythread-tianluo"線程堆棧信息分析如下:
mythread-tianluo的線程處于等待(waiting)狀態(tài)询微,持有“0x00000000d61ae3d0”鎖玄叠,等待“0x00000000d61ae3a0”的鎖
“mythread-jay"線程堆棧信息分析如下:
mythread-tianluo的線程處于等待(waiting)狀態(tài),持有“0x00000000d61ae3a0”鎖拓提,等待“0x00000000d61ae3d0”的鎖
實(shí)戰(zhàn)案例2:jstack 分析CPU過(guò)高問(wèn)題
來(lái)個(gè)導(dǎo)致CPU過(guò)高的demo程序读恃,一個(gè)死循環(huán),哈哈~
/**
* 有個(gè)導(dǎo)致CPU過(guò)高程序的demo,死循環(huán)
*/
public class JstackCase {
? ? private static ExecutorService executorService = Executors.newFixedThreadPool(5);
? ? public static void main(String[] args) {
? ? ? ? Task task1 = new Task();
? ? ? ? Task task2 = new Task();
? ? ? ? executorService.execute(task1);
? ? ? ? executorService.execute(task2);
? ? }
? ? public static Object lock = new Object();
? ? static class Task implements Runnable{
? ? ? ? public void run() {
? ? ? ? ? ? synchronized (lock){
? ? ? ? ? ? ? ? long sum = 0L;
? ? ? ? ? ? ? ? while (true){
? ? ? ? ? ? ? ? ? ? sum += 1;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
? ? }
}
jstack 分析CPU過(guò)高步驟
top
? ? ?2.top -Hp pid
? ? ?3. jstack pid
? ? ?4. jstack -l [PID] >/tmp/log.txt
? ? ?5. 分析堆棧信息
1.top
在服務(wù)器上寺惫,我們可以通過(guò)top命令查看各個(gè)進(jìn)程的cpu使用情況疹吃,它默認(rèn)是按cpu使用率由高到低排序的
由上圖中,我們可以找出pid為21340的java進(jìn)程西雀,它占用了最高的cpu資源萨驶,兇手就是它,哈哈艇肴!
2. top -Hp pid
通過(guò)top -Hp 21340可以查看該進(jìn)程下腔呜,各個(gè)線程的cpu使用情況,如下:
可以發(fā)現(xiàn)pid為21350的線程再悼,CPU資源占用最高~核畴,嘻嘻,小本本把它記下來(lái)冲九,接下來(lái)拿jstack給它拍片子~
3. jstack pid
通過(guò)top命令定位到cpu占用率較高的線程之后谤草,接著使用jstack pid命令來(lái)查看當(dāng)前java進(jìn)程的堆棧狀態(tài),jstack21350后莺奸,內(nèi)容如下:
4. jstack -l [PID] >/tmp/log.txt
其實(shí)丑孩,前3個(gè)步驟,堆棧信息已經(jīng)出來(lái)啦灭贷。但是一般在生成環(huán)境温学,我們可以把這些堆棧信息打到一個(gè)文件里,再回頭仔細(xì)分析哦~
5. 分析堆棧信息
我們把占用cpu資源較高的線程pid(本例子是21350)甚疟,將該pid轉(zhuǎn)成16進(jìn)制的值
在thread dump中枫浙,每個(gè)線程都有一個(gè)nid,我們找到對(duì)應(yīng)的nid(5366)古拴,發(fā)現(xiàn)一直在跑(24行)
這個(gè)時(shí)候箩帚,可以去檢查代碼是否有問(wèn)題啦~ 當(dāng)然,也建議隔段時(shí)間再執(zhí)行一次stack命令黄痪,再一份獲取thread dump紧帕,畢竟兩次拍片結(jié)果(jstack)對(duì)比,更準(zhǔn)確嘛~
參考與感謝
jvm 性能調(diào)優(yōu)工具之 jstack (http://www.reibang.com/p/025cb069cb69)
如何使用jstack分析線程狀態(tài) (http://www.reibang.com/p/6690f7e92f27)
Java命令學(xué)習(xí)系列(二)—Jstack(http://www.hollischuang.com/archives/110)