JVM之jstack命令的使用解讀
一、簡(jiǎn)介
jstack全稱叫Java Stack Trace,Java的堆棧跟蹤工具,用于生成java虛擬機(jī)當(dāng)前時(shí)刻的線程快照。
功能主要有兩個(gè)如下
- 分析死鎖;
- 分析CPU過(guò)高問(wèn)題。
1.1 命令格式
- jstack [ option ] pid 查看當(dāng)前時(shí)間點(diǎn),指定進(jìn)程的dump堆棧信息。
- jstack [ option ] pid > 文件 將當(dāng)前時(shí)間點(diǎn)的指定進(jìn)程的dump堆棧信息,寫(xiě)入到指定文件中。# 注:若該文件不存在,則會(huì)自動(dòng)生成; 若該文件存在,則會(huì)覆蓋源文件。
- jstack [ option ] executable core 查看當(dāng)前時(shí)間點(diǎn),core文件的dump堆棧信息。
- jstack [ option ] [server_id@]<remote server IP or hostname> 查看當(dāng)前時(shí)間點(diǎn),遠(yuǎn)程機(jī)器的dump堆棧信息。
option 參數(shù)如下:
名稱 | 說(shuō)明 |
|---|---|
| -F | 當(dāng)正常輸出的請(qǐng)求不被響應(yīng)時(shí),強(qiáng)制輸出線程堆棧。 |
-m | 打印java和native c/c++框架的所有棧信息??梢源蛴VM的堆棧,以及Native的棧幀,一般應(yīng)用排查不需要使用。 |
| -l | 除堆棧外,顯示關(guān)于鎖的附加信息,在發(fā)生死鎖時(shí)可以用jstack -l pid來(lái)觀察鎖持有情況。 |
1.2 獲取CPU飆高的線程id
1.2.1 找到CPU飆高的進(jìn)程
獲取各個(gè)進(jìn)程的CPU和內(nèi)存情況,并且找到CPU飆高的進(jìn)程ID,比如進(jìn)程ID=10843
top
1.2.2 顯示java進(jìn)程的CPU和內(nèi)存占用情況
top -p 進(jìn)程id
1.2.3 獲取每個(gè)線程的CPU和內(nèi)存占用情況
- 按H
1.2.4 jstack查看線程情況
# 將10進(jìn)制線程id轉(zhuǎn)為16進(jìn)制 printf "%x\n" 線程id # 查看線程情況 jstack 進(jìn)程ID | grep -A 10 十六進(jìn)制的線程ID
二、jstack輸出內(nèi)容解析
其中標(biāo)注daemon字樣的是后臺(tái)線程。
用戶線程中包括:
- 線程的一些基本信息:名稱、優(yōu)先級(jí)及id
- 線程狀態(tài):waiting on condition等
- 線程的調(diào)用棧
- 線程鎖住的資源:locked<0x3f63d600>
2.1 Monitor(監(jiān)視器)
在多線程的java程序中,實(shí)現(xiàn)線程之間的同步,就要說(shuō)說(shuō)Monitor。
Monitor是java中用以實(shí)現(xiàn)線程之間的互斥與協(xié)作的主要手段,它可以看成是對(duì)象或者Class的鎖。每一個(gè)對(duì)象都有,也僅有一個(gè)Monitor。
- 進(jìn)入?yún)^(qū)(Entry Set):表示線程通過(guò)synchronized要求獲取對(duì)象的鎖。如果對(duì)象未被鎖住,則進(jìn)入擁有者;否則在進(jìn)入?yún)^(qū)等待。一旦對(duì)象鎖被其他線程釋放,立即參與競(jìng)爭(zhēng)。
- 擁有者(The Owner):表示某一線程成功競(jìng)爭(zhēng)到對(duì)象鎖。
- 等待區(qū)(Wait Set):表示線程通過(guò)對(duì)象的wait方法釋放對(duì)象的鎖,并在等待區(qū)等待被喚醒。
一個(gè) Monitor在某個(gè)時(shí)刻,只能被一個(gè)線程擁有,該線程就是 “Active Thread”,而其它線程都是 “Waiting Thread”,分別在兩個(gè)隊(duì)列 “ Entry Set”和 “Wait Set”里面等候。
在 “Entry Set”中等待的線程狀態(tài)是 “Waiting for monitor entry”,而在“Wait Set”中等待的線程狀態(tài)是 “in Object.wait()”。
先看 “Entry Set”里面的線程。我們稱被 synchronized保護(hù)起來(lái)的代碼段為臨界區(qū)。當(dāng)一個(gè)線程申請(qǐng)進(jìn)入臨界區(qū)時(shí),它就進(jìn)入了 “Entry Set”隊(duì)列。對(duì)應(yīng)的 code就像:
synchronized(obj){
……
}2.2 調(diào)用修飾
表示線程在方法調(diào)用時(shí)額外的重要操作。線程dump分析的重要信息。修飾上方的方法調(diào)用。
- locked<地址>目標(biāo):使用synchronized申請(qǐng)對(duì)象鎖成功,監(jiān)視器的擁有者;
- waiting to lock<地址>目標(biāo):使用synchronized申請(qǐng)對(duì)象鎖未成功,在進(jìn)入?yún)^(qū)等待;
- waiting on<地址>目標(biāo):使用synchronized申請(qǐng)對(duì)象鎖成功后,調(diào)用了wait方法,進(jìn)入對(duì)象的等待區(qū)等待。在調(diào)用棧頂出線,線程狀態(tài)為WAITING或TIMED_WAITING;
- parking to wait for<地址>目標(biāo):park是基本的線程阻塞原語(yǔ),不通過(guò)監(jiān)視器在對(duì)象上阻塞。隨concurrent包出現(xiàn)的新的機(jī)制,與synchronized體系不同。
2.3 線程狀態(tài)
- 死鎖,Deadlock(重點(diǎn)關(guān)注)
- 等待資源,Waiting on condition(重點(diǎn)關(guān)注)
- 等待獲取管程,Waiting on monitor entry(點(diǎn)關(guān)注)
- 阻塞,Blocked(重點(diǎn)關(guān)注)
- 執(zhí)行中,Runnable
- 暫停,Suspended
- 對(duì)象等待中,Object.wait() 或 TIMED_WAITING
- 停止,Parked
輸出信息例如
"Thread-1" prio=10 tid=0x08223860 nid=0xa waiting on condition [0xef47a000..0xef47ac38]
at java.lang.Thread.sleep(Native Method)
at testthread.MySleepingThread.method2(MySleepingThread.java:53)
- locked <0xef63d600> (a testthread.MySleepingThread)
at testthread.MySleepingThread.run(MySleepingThread.java:35)
at java.lang.Thread.run(Thread.java:595) </span>
我們能看到:
- 線程的狀態(tài): waiting on condition
- 線程的調(diào)用棧
- 線程的當(dāng)前鎖住的資源: <0xef63d600>
Wait on condition
該狀態(tài)出現(xiàn)在線程等待某個(gè)條件的發(fā)生。具體是什么原因,可以結(jié)合 stacktrace來(lái)分析。最常見(jiàn)的情況是線程在等待網(wǎng)絡(luò)的讀寫(xiě),比如當(dāng)網(wǎng)絡(luò)數(shù)據(jù)沒(méi)有準(zhǔn)備好讀時(shí),線程處于這種等待狀態(tài),而一旦有數(shù)據(jù)準(zhǔn)備好讀之后,線程會(huì)重新激活,讀取并處理數(shù)據(jù)。在 Java引入 NewIO之前,對(duì)于每個(gè)網(wǎng)絡(luò)連接,都有一個(gè)對(duì)應(yīng)的線程來(lái)處理網(wǎng)絡(luò)的讀寫(xiě)操作,即使沒(méi)有可讀寫(xiě)的數(shù)據(jù),線程仍然阻塞在讀寫(xiě)操作上,這樣有可能造成資源浪費(fèi),而且給操作系統(tǒng)的線程調(diào)度也帶來(lái)壓力。在 NewIO里采用了新的機(jī)制,編寫(xiě)的服務(wù)器程序的性能和可擴(kuò)展性都得到提高。
如果發(fā)現(xiàn)有大量的線程都在處在 Wait on condition,從線程 stack看, 正等待網(wǎng)絡(luò)讀寫(xiě),這可能是一個(gè)網(wǎng)絡(luò)瓶頸的征兆。因?yàn)榫W(wǎng)絡(luò)阻塞導(dǎo)致線程無(wú)法執(zhí)行。一種情況是網(wǎng)絡(luò)非常忙,幾 乎消耗了所有的帶寬,仍然有大量數(shù)據(jù)等待網(wǎng)絡(luò)讀 寫(xiě);另一種情況也可能是網(wǎng)絡(luò)空閑,但由于路由等問(wèn)題,導(dǎo)致包無(wú)法正常的到達(dá)。所以要結(jié)合系統(tǒng)的一些性能觀察工具來(lái)綜合分析,比如 netstat統(tǒng)計(jì)單位時(shí)間的發(fā)送包的數(shù)目,如果很明顯超過(guò)了所在網(wǎng)絡(luò)帶寬的限制 ; 觀察 cpu的利用率,如果系統(tǒng)態(tài)的 CPU時(shí)間,相對(duì)于用戶態(tài)的 CPU時(shí)間比例較高;如果程序運(yùn)行在 Solaris 10平臺(tái)上,可以用 dtrace工具看系統(tǒng)調(diào)用的情況,如果觀察到 read/write的系統(tǒng)調(diào)用的次數(shù)或者運(yùn)行時(shí)間遙遙領(lǐng)先;這些都指向由于網(wǎng)絡(luò)帶寬所限導(dǎo)致的網(wǎng)絡(luò)瓶頸。另外一種出現(xiàn) Wait on condition的常見(jiàn)情況是該線程在 sleep,等待 sleep的時(shí)間到了時(shí)候,將被喚醒。
Waiting for monitor entry 和 in Object.wait()
在多線程的 JAVA程序中,實(shí)現(xiàn)線程之間的同步,就要說(shuō)說(shuō) Monitor。 Monitor是 Java中用以實(shí)現(xiàn)線程之間的互斥與協(xié)作的主要手段,它可以看成是對(duì)象或者 Class的鎖。每一個(gè)對(duì)象都有,也僅有一個(gè) monitor。每個(gè) Monitor在某個(gè)時(shí)刻,只能被一個(gè)線程擁有,該線程就是 “Active Thread”,而其它線程都是 “Waiting Thread”,分別在兩個(gè)隊(duì)列 “ Entry Set”和 “Wait Set”里面等候。在 “Entry Set”中等待的線程狀態(tài)是 “Waiting for monitor entry”,而在 “Wait Set”中等待的線程狀態(tài)是 “in Object.wait()”。
先看 “Entry Set”里面的線程。我們稱被 synchronized保護(hù)起來(lái)的代碼段為臨界區(qū)。當(dāng)一個(gè)線程申請(qǐng)進(jìn)入臨界區(qū)時(shí),它就進(jìn)入了 “Entry Set”隊(duì)列。
這時(shí)有兩種可能性:
1、該 monitor不被其它線程擁有, Entry Set里面也沒(méi)有其它等待線程。本線程即成為相應(yīng)類或者對(duì)象的 Monitor的 Owner,執(zhí)行臨界區(qū)的代碼
2、該 monitor被其它線程擁有,本線程在 Entry Set隊(duì)列中等待。
在第一種情況下,線程將處于 “Runnable”的狀態(tài),而第二種情況下,線程 DUMP會(huì)顯示處于 “waiting for monitor entry”。
臨界區(qū)的設(shè)置,是為了保證其內(nèi)部的代碼執(zhí)行的原子性和完整性。但是因?yàn)榕R界區(qū)在任何時(shí)間只允許線程串行通過(guò),這 和我們多線程的程序的初衷是相反的。 如果在多線程的程序中,大量使用 synchronized,或者不適當(dāng)?shù)氖褂昧怂?,?huì)造成大量線程在臨界區(qū)的入口等待,造成系統(tǒng)的性能大幅下降。如果在線程 DUMP中發(fā)現(xiàn)了這個(gè)情況,應(yīng)該審查源碼,改進(jìn)程序。
現(xiàn)在我們?cè)賮?lái)看現(xiàn)在線程為什么會(huì)進(jìn)入 “Wait Set”。當(dāng)線程獲得了 Monitor,進(jìn)入了臨界區(qū)之后,如果發(fā)現(xiàn)線程繼續(xù)運(yùn)行的條件沒(méi)有滿足,它則調(diào)用對(duì)象(一般就是被 synchronized 的對(duì)象)的 wait() 方法,放棄了 Monitor,進(jìn)入 “Wait Set”隊(duì)列。只有當(dāng)別的線程在該對(duì)象上調(diào)用了 notify() 或者 notifyAll() , “ Wait Set”隊(duì)列中線程才得到機(jī)會(huì)去競(jìng)爭(zhēng),但是只有一個(gè)線程獲得對(duì)象的 Monitor,恢復(fù)到運(yùn)行態(tài)。
在 “Wait Set”中的線程, DUMP中表現(xiàn)為: in Object.wait(),類似于:仔細(xì)觀察上面的 DUMP信息,你會(huì)發(fā)現(xiàn)它有以下兩行:
- - locked <0xef63beb8> (a java.util.ArrayList)
- - waiting on <0xef63beb8> (a java.util.ArrayList)
線程的執(zhí)行中,先用 synchronized 獲得了這個(gè)對(duì)象的 Monitor(對(duì)應(yīng)于 locked <0xef63beb8> )。當(dāng)執(zhí)行到 obj.wait(), 線程即放棄了 Monitor的所有權(quán),進(jìn)入 “wait set”隊(duì)列(對(duì)應(yīng)于 waiting on <0xef63beb8> )。
往往在你的程序中,會(huì)出現(xiàn)多個(gè)類似的線程,他們都有相似的 DUMP信息。這也可能是正常的。比如,在程序中,有多個(gè)服務(wù)線程,設(shè)計(jì)成從一個(gè)隊(duì)列里面讀取請(qǐng)求數(shù)據(jù)。這個(gè)隊(duì)列就是 lock以及 waiting on的對(duì)象。
當(dāng)隊(duì)列為空的時(shí)候,這些線程都會(huì)在這個(gè)隊(duì)列上等待,直到隊(duì)列有了數(shù)據(jù),這些線程被 Notify,當(dāng)然只有一個(gè)線程獲得了 lock,繼續(xù)執(zhí)行,而其它線程繼續(xù)等待。
2.4 線程動(dòng)作
線程狀態(tài)產(chǎn)生的原因:
runnable:狀態(tài)一般為RUNNABLE,表示線程具備所有運(yùn)行條件,在運(yùn)行隊(duì)列中準(zhǔn)備操作系統(tǒng)的調(diào)度,或者正在運(yùn)行。in Object.wait():等待區(qū)等待,狀態(tài)為WAITING或TIMED_WAITING。waiting for monitor entry:進(jìn)入?yún)^(qū)等待,狀態(tài)為BLOCKED。waiting on condition:等待去等待,被park。sleeping:休眠的線程,調(diào)用了Thread.sleep()。
Wait on condition 該狀態(tài)出現(xiàn)在線程等待某個(gè)條件的發(fā)生。具體是什么原因,可以結(jié)合 stacktrace來(lái)分析。 最常見(jiàn)的情況就是線程處于sleep狀態(tài),等待被喚醒。 常見(jiàn)的情況還有等待網(wǎng)絡(luò)IO:在java引入nio之前,對(duì)于每個(gè)網(wǎng)絡(luò)連接,都有一個(gè)對(duì)應(yīng)的線程來(lái)處理網(wǎng)絡(luò)的讀寫(xiě)操作,即使沒(méi)有可讀寫(xiě)的數(shù)據(jù),線程仍然阻塞在讀寫(xiě)操作上,這樣有可能造成資源浪費(fèi),而且給操作系統(tǒng)的線程調(diào)度也帶來(lái)壓力。
在 NIO里采用了新的機(jī)制,編寫(xiě)的服務(wù)器程序的性能和可擴(kuò)展性都得到提高。 正等待網(wǎng)絡(luò)讀寫(xiě),這可能是一個(gè)網(wǎng)絡(luò)瓶頸的征兆。因?yàn)榫W(wǎng)絡(luò)阻塞導(dǎo)致線程無(wú)法執(zhí)行。一種情況是網(wǎng)絡(luò)非常忙,幾 乎消耗了所有的帶寬,仍然有大量數(shù)據(jù)等待網(wǎng)絡(luò)讀 寫(xiě);另一種情況也可能是網(wǎng)絡(luò)空閑,但由于路由等問(wèn)題,導(dǎo)致包無(wú)法正常的到達(dá)。所以要結(jié)合系統(tǒng)的一些性能觀察工具來(lái)綜合分析,比如 netstat統(tǒng)計(jì)單位時(shí)間的發(fā)送包的數(shù)目,如果很明顯超過(guò)了所在網(wǎng)絡(luò)帶寬的限制 ; 觀察 cpu的利用率,如果系統(tǒng)態(tài)的 CPU時(shí)間,相對(duì)于用戶態(tài)的 CPU時(shí)間比例較高;如果程序運(yùn)行在 Solaris 10平臺(tái)上,可以用 dtrace工具看系統(tǒng)調(diào)用的情況,如果觀察到 read/write的系統(tǒng)調(diào)用的次數(shù)或者運(yùn)行時(shí)間遙遙領(lǐng)先;這些都指向由于網(wǎng)絡(luò)帶寬所限導(dǎo)致的網(wǎng)絡(luò)瓶頸。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Linux上兩種網(wǎng)絡(luò)連接實(shí)現(xiàn)方式
這篇文章主要介紹了Linux上兩種網(wǎng)絡(luò)連接實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-09-09
Linux(Ubuntu 18.04)上安裝Anaconda步驟詳解
Anaconda是最受歡迎的python數(shù)據(jù)科學(xué)和機(jī)器學(xué)習(xí)平臺(tái),用于大規(guī)模數(shù)據(jù)處理,預(yù)測(cè)分析和科學(xué)計(jì)算。這篇文章主要介紹了Linux(Ubuntu 18.04)上安裝Anaconda的方法,需要的朋友可以參考下2018-11-11
在CentOS 7上設(shè)置靜態(tài)IP地址的詳細(xì)指南
配置靜態(tài)IP地址是服務(wù)器或虛擬機(jī)管理的重要步驟之一,特別是在需要穩(wěn)定、可預(yù)測(cè)的網(wǎng)絡(luò)環(huán)境時(shí),本文將詳細(xì)介紹如何在CentOS 7上設(shè)置靜態(tài)IP地址,幫助確保你的系統(tǒng)網(wǎng)絡(luò)配置符合需求,需要的朋友可以參考下2025-09-09
簡(jiǎn)單架設(shè)SSH+Squid代理服務(wù)器的自由上網(wǎng)通道的方法
Squid是一個(gè)高性能的代理緩存服務(wù)器,但缺點(diǎn)是網(wǎng)址是以明文的形式發(fā)送的。在我們的特殊互聯(lián)網(wǎng)環(huán)境中,就算使用這樣的代理服務(wù)器,打開(kāi)網(wǎng)頁(yè)時(shí)也常常會(huì)被“Reset”。2011-03-03
linux環(huán)境下安裝jdk和Tomcat詳細(xì)步驟
大家好,本篇文章主要講的是linux環(huán)境下安裝jdk和Tomcat詳細(xì)步驟,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話記得收一下,方便下次瀏覽2021-12-12
Ubuntu系統(tǒng)中查看網(wǎng)絡(luò)速率的幾種方法
這篇文章主要給大家介紹了在 Ubuntu系統(tǒng)中,可以使用以下幾種方法查看網(wǎng)絡(luò)速率,文中通過(guò)代碼示例講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-11-11
Linux通過(guò)匿名管道進(jìn)行進(jìn)程間通信
這篇文章主要介紹了Linux通過(guò)匿名管道進(jìn)行進(jìn)程間通信,介紹了什么是管道,popen函數(shù),pipe調(diào)用等相關(guān)內(nèi)容,小編覺(jué)得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下2018-01-01

