目錄
一、jdk工具之jps(JVM Process Status Tools)命令使用
二、jdk命令之javah命令(C Header and Stub File Generator)
三、jdk工具之jstack(Java Stack Trace)
四、jdk工具之jstat命令(Java Virtual Machine Statistics Monitoring Tool)
四、jdk工具之jstat命令2(Java Virtual Machine Statistics Monitoring Tool)詳解
五、jdk工具之jmap(java memory map)、 mat之四--結合mat對內存泄露的分析
六、jdk工具之jinfo命令(Java Configuration Info)
七、jdk工具之jconsole命令(Java Monitoring and Management Console)
八、jdk工具之JvisualVM、JvisualVM之二--Java程序性能分析工具Java VisualVM
九、jdk工具之jhat命令(Java Heap Analyse Tool)
十、jdk工具之Jdb命令(The Java Debugger)
十一、jdk命令之Jstatd命令(Java Statistics Monitoring Daemon)
十一、jdk命令之Jstatd命令(Java Statistics Monitoring Daemon)
十二、jdk工具之jcmd介紹(堆轉儲、堆分析、獲取系統信息、查看堆外內存)
十三、jdk命令之Java內存之本地內存分析神器:NMT 和 pmap
jstack是java虛擬機自帶的一種堆棧跟蹤工具。
案例
說明
-
不同的 JAVA虛機的線程 DUMP的創建方法和文件格式是不一樣的,不同的 JVM版本, dump信息也有差別。
-
在實際運行中,往往一次 dump的信息,還不足以確認問題。建議產生三次 dump信息,如果每次 dump都指向同一個問題,我們才確定問題的典型性。
案例1-使用
先以一個小場景簡單示范下 jstack 的使用。
場景:Java應用持續占用很高CPU,需要排查一下。
模擬:造個場景簡單模擬下,沒什么實際意義,僅作演示。我啟動了100個線程持續訪問 我的博客,博客部署在Ubuntu 16.04上,是一個簡單的Spring Boot應用,以jar包直接運行的。
top 命令查下系統運行情況,進程31951占用CPU 80.6%。
jps -l
確認一下,31951就是博客的進程ID,或 cat /proc/31951/cmdline
看下進程的啟用命令。
root@iZ94dcq8q6jZ:~# jps -l 28416 sun.tools.jps.Jps 31951 blog.jar
top -Hp 31951
以線程模式查看下進程31951的所有線程情況
假設想看下第二個線程31998的情況,31998是操作系統的線程ID,先轉成16進制。(再例如:21233用計算器轉換為16進制52f1,注意字母是小寫)
printf '%x' 31998 #值為7cfe
獲取該線程的信息(匹配7cf3后取20行差不多)
jstack 31951 | grep 7cfe -A 20
其中部分數據如下:
"Tomcat JDBC Pool Cleaner[11483240:1532362388783]" #31 daemon prio=5 os_prio=0 tid=0x0a29dc00 nid=0x7cfe in Object.wait() [0xa2a69000] java.lang.Thread.State: TIMED_WAITING (on object monitor) at java.lang.Object.wait(Native Method) at java.util.TimerThread.mainLoop(Timer.java:552) - locked <0xaadc5a60> (a java.util.TaskQueue) at java.util.TimerThread.run(Timer.java:505)
注意:nid=0x7cfe中的nid指native id,是OS中線程ID,對應上面31998線程的16進制值7cfe;tid為Java中線程的ID。
至於如何利用jstack的數據分析線程情況,可以看看 如何使用jstack分析線程狀態 和 jstack。
案例2-死鎖
-
"Thread-1"prio=5tid=0x00acc490nid=0xe50waitingformonitorentry[0x02d3f000
-
..0x02d3fd68]
-
atdeadlockthreads.TestThread.run(TestThread.java:31)
-
-waitingtolock<0x22c19f18>(ajava.lang.Object)
-
-locked<0x22c19f20>(ajava.lang.Object)
-
"Thread-0"prio=5tid=0x00accdb0nid=0xdecwaitingformonitorentry[0x02cff000
-
..0x02cff9e8]
-
atdeadlockthreads.TestThread.run(TestThread.java:31)
-
-waitingtolock<0x22c19f20>(ajava.lang.Object)
-
-locked<0x22c19f18>(ajava.lang.Object)
-
FoundoneJava-leveldeadlock:
-
=============================
-
"Thread-1":
-
waitingtolockmonitor0x0003f334(object0x22c19f18,ajava.lang.Object),
-
whichisheldby"Thread-0"
-
"Thread-0":
-
waitingtolockmonitor0x0003f314(object0x22c19f20,ajava.lang.Object),
-
whichisheldby"Thread-1"[2]
生成死鎖(deadlock)的源代碼
/** * 簡單的應用,供測試JDK自帶的jstack使用 本應用會造成deadlock,可能會導致系統崩潰 * 邏輯:一旦兩個線程互相等待的局面出現,死鎖(deadlock)就發生了 */ public class EasyJstack extends Thread { private EasyJstackResource resourceManger;// 資源管理類的私有引用,通過此引用可以通過其相關接口對資源進行讀寫 private int a, b;// 將要寫入資源的數據 public static void main(String[] args) throws Exception { EasyJstackResource resourceManager = new EasyJstackResource(); EasyJstack stack1 = new EasyJstack(resourceManager, 1, 2); EasyJstack stack2 = new EasyJstack(resourceManager, 3, 4); stack1.start(); stack2.start(); } public EasyJstack(EasyJstackResource resourceManager, int a, int b) { this.resourceManger = resourceManager; this.a = a; this.b = b; } public void run() { while (true) { this.resourceManger.read(); this.resourceManger.write(this.a, this.b); } } } public class EasyJstackResource { /** * 管理的兩個資源,如果有多個線程並發,那么就會死鎖 */ private Resource resourceA = new Resource(); private Resource resourceB = new Resource(); public EasyJstackResource() { this.resourceA.setValue(0); this.resourceB.setValue(0); } public int read() { synchronized (this.resourceA) { System.out.println(Thread.currentThread().getName() + "線程拿到了資源 resourceA的對象鎖"); synchronized (resourceB) { System.out.println(Thread.currentThread().getName() + "線程拿到了資源 resourceB的對象鎖"); return this.resourceA.getValue() + this.resourceB.getValue(); } } } public void write(int a, int b) { synchronized (this.resourceB) { System.out.println(Thread.currentThread().getName() + "線程拿到了資源 resourceB的對象鎖"); synchronized (this.resourceA) { System.out.println(Thread.currentThread().getName() + "線程拿到了資源 resourceA的對象鎖"); this.resourceA.setValue(a); this.resourceB.setValue(b); } } } public class Resource { private int value;// 資源的屬性 public int getValue() { return value; } public void setValue(int value) { this.value = value; } } }
Found one Java-level deadlock: ============================= "Thread-1": waiting to lock monitor 0x00000000577c2bc8 (object 0x00000000d7149440, a com.dxz.jstack.EasyJstackResource$Resource), which is held by "Thread-0" "Thread-0": waiting to lock monitor 0x00000000577c4118 (object 0x00000000d7149428, a com.dxz.jstack.EasyJstackResource$Resource), which is held by "Thread-1" Java stack information for the threads listed above: =================================================== "Thread-1": at com.dxz.jstack.EasyJstackResource.read(EasyJstackResource.java:36) - waiting to lock <0x00000000d7149440> (a com.dxz.jstack.EasyJstackResource$Resource) - locked <0x00000000d7149428> (a com.dxz.jstack.EasyJstackResource$Resource) at com.dxz.jstack.EasyJstack.run(EasyJstack.java:41) "Thread-0": at com.dxz.jstack.EasyJstackResource.write(EasyJstackResource.java:46) - waiting to lock <0x00000000d7149428> (a com.dxz.jstack.EasyJstackResource$Resource) - locked <0x00000000d7149440> (a com.dxz.jstack.EasyJstackResource$Resource) at com.dxz.jstack.EasyJstack.run(EasyJstack.java:42) Found 1 deadlock.
jstack用於打印出給定的Java進程ID或core file或遠程調試服務的Java堆棧信息,如果是在64位機器上,需要指定選項"-J-d64",Windows的jstack使用方式只支持以下的這種方式:jstack [-l] pid
如果java程序崩潰生成core文件,jstack工具可以用來獲得core文件的java stack和native stack的信息,從而可以輕松地知道java程序是如何崩潰和在程序何處發生問題。另外,jstack工具還可以附屬到正在運行的java程序中,看到當時運行的java程序的java stack和native stack的信息, 如果現在運行的java程序呈現hung的狀態,jstack是非常有用的。
需要注意的問題:
l 不同的 JAVA虛機的線程 DUMP的創建方法和文件格式是不一樣的,不同的 JVM版本, dump信息也有差別。
l 在實際運行中,往往一次 dump的信息,還不足以確認問題。建議產生三次 dump信息,如果每次 dump都指向同一個問題,我們才確定問題的典型性。
2、命令格式
$jstack [ option ] pid
$jstack [ option ] executable core
$jstack [ option ] [server-id@]remote-hostname-or-IP
參數說明:
pid: java應用程序的進程號,一般可以通過jps來獲得;
executable:產生core dump的java可執行程序;
core:打印出的core文件;
remote-hostname-or-ip:遠程debug服務器的名稱或IP;
server-id: 唯一id,假如一台主機上多個遠程debug服務;
示例:
$jstack –l 23561
線程分析:
一般情況下,通過jstack輸出的線程信息主要包括:jvm自身線程、用戶線程等。其中jvm線程會在jvm啟動時就會存在。對於用戶線程則是在用戶訪問時才會生成。
l jvm線程:
在線程中,有一些 JVM內部的后台線程,來執行譬如垃圾回收,或者低內存的檢測等等任務,這些線程往往在JVM初始化的時候就存在,如下所示:
"Attach Listener" daemon prio=10 tid=0x0000000052fb8000 nid=0xb8f waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers: - None destroyJavaVM" prio=10 tid=0x00002aaac1225800 nid=0x7208 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers: - None |
l 用戶級別的線程
還有一類線程是用戶級別的,它會根據用戶請求的不同而發生變化。該類線程的運行情況往往是我們所關注的重點。而且這一部分也是最容易產生死鎖的地方。
"qtp496432309-42" prio=10 tid=0x00002aaaba2a1800 nid=0x7580 waiting on condition [0x00000000425e9000] java.lang.Thread.State: TIMED_WAITING (parking) at sun.misc.Unsafe.park(Native Method) - parking to wait for <0x0000000788cfb020> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject) at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:198) at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2025) at org.eclipse.jetty.util.BlockingArrayQueue.poll(BlockingArrayQueue.java:320) at org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:479) at java.lang.Thread.run(Thread.java:662)
Locked ownable synchronizers: - None |
從上述的代碼示例中我們可以看到該用戶線程的以下幾類信息:
Ø 線程的狀態:waiting on condition(等待條件發生)
Ø 線程的調用情況;
Ø 線程對資源的鎖定情況;
線程的狀態分析:
正如我們剛看到的那樣,線程的狀態是一個重要的指標,它會顯示在線程每行結尾的地方。那么線程常見的有哪些狀態呢?線程在什么樣的情況下會進入這種狀態呢?我們能從中發現什么線索?
l Runnable
該狀態表示線程具備所有運行條件,在運行隊列中准備操作系統的調度,或者正在運行。
l Waiton condition
該狀態出現在線程等待某個條件的發生。具體是什么原因,可以結合stacktrace來分析。最常見的情況是線程在等待網絡的讀寫,比如當網絡數據沒有准備好讀時,線程處於這種等待狀態,而一旦有數據准備好讀之后,線程會重新激活,讀取並處理數據。在 Java引入 NIO之前,對於每個網絡連接,都有一個對應的線程來處理網絡的讀寫操作,即使沒有可讀寫的數據,線程仍然阻塞在讀寫操作上,這樣有可能造成資源浪費,而且給操作系統的線程調度也帶來壓力。在 NIO里采用了新的機制,編寫的服務器程序的性能和可擴展性都得到提高。
如果發現有大量的線程都在處在 Wait on condition,從線程 stack看, 正等待網絡讀寫,這可能是一個網絡瓶頸的征兆。因為網絡阻塞導致線程無法執行。一種情況是網絡非常忙,幾乎消耗了所有的帶寬,仍然有大量數據等待網絡讀寫;另一種情況也可能是網絡空閑,但由於路由等問題,導致包無法正常的到達。所以要結合系統的一些性能觀察工具來綜合分析,比如 netstat統計單位時間的發送包的數目,如果很明顯超過了所在網絡帶寬的限制 ; 觀察 cpu的利用率,如果系統態的 CPU時間,相對於用戶態的 CPU時間比例較高;如果程序運行在 Solaris 10平台上,可以用 dtrace工具看系統調用的情況,如果觀察到 read/write的系統調用的次數或者運行時間遙遙領先;這些都指向由於網絡帶寬所限導致的網絡瓶頸。
另外一種出現 Wait on condition的常見情況是該線程在 sleep,等待 sleep的時間到了時候,將被喚醒。
l Waitingfor monitor entry 和 in Object.wait()
在多線程的 JAVA程序中,實現線程之間的同步,就要說說Monitor。Monitor是Java中用以實現線程之間的互斥與協作的主要手段,它可以看成是對象或者 Class的鎖。每一個對象都有,也僅有一個 monitor。下面這個圖,描述了線程和 Monitor之間關系,以及線程的狀態轉換圖:
從圖中可以看出,每個 Monitor在某個時刻,只能被一個線程擁有,該線程就是 “Active Thread”,而其它線程都是 “Waiting Thread”,分別在兩個隊列 “ Entry Set”和 “Wait Set”里面等候。在 “Entry Set”中等待的線程狀態是 “Waiting for monitorentry”,而在 “Wait Set”中等待的線程狀態是“in Object.wait()”。
先看 “Entry Set”里面的線程。我們稱被 synchronized保護起來的代碼段為臨界區。當一個線程申請進入臨界區時,它就進入了 “Entry Set”隊列。對應的 code就像:
synchronized(obj){
.........
}
這時有兩種可能性:
該 monitor不被其它線程擁有,Entry Set里面也沒有其它等待線程。本線程即成為相應類或者對象的 Monitor的 Owner,執行臨界區的代碼 。此時線程將處於Runnable狀態;
該 monitor被其它線程擁有,本線程在 Entry Set隊列中等待。此時dump的信息顯示“waiting for monitor entry”。
"Thread-0" prio=10 tid=0x08222eb0 nid=0x9 waiting for monitor entry [0xf927b000..0xf927bdb8] at testthread.WaitThread.run(WaitThread.java:39) |
臨界區的設置,是為了保證其內部的代碼執行的原子性和完整性。但是因為臨界區在任何時間只允許線程串行通過,這和我們多線程的程序的初衷是相反的。如果在多線程的程序中,大量使用 synchronized,或者不適當的使用了它,會造成大量線程在臨界區的入口等待,造成系統的性能大幅下降。如果在線程 DUMP中發現了這個情況,應該審查源碼,改進程序。
現在我們再來看現在線程為什么會進入 “Wait Set”。當線程獲得了 Monitor,進入了臨界區之后,如果發現線程繼續運行的條件沒有滿足,它則調用對象(一般就是被 synchronized 的對象)的 wait() 方法,放棄了 Monitor,進入 “Wait Set”隊列。只有當別的線程在該對象上調用了 notify() 或者 notifyAll() , “ Wait Set”隊列中線程才得到機會去競爭,但是只有一個線程獲得對象的Monitor,恢復到運行態。在 “Wait Set”中的線程, DUMP中表現為: in Object.wait(),類似於:
"Thread-1" prio=10 tid=0x08223250 nid=0xa in Object.wait() [0xef47a000..0xef47aa38] at java.lang.Object.wait(Native Method) - waiting on <0xef63beb8> (a java.util.ArrayList) at java.lang.Object.wait(Object.java:474) at testthread.MyWaitThread.run(MyWaitThread.java:40) - locked <0xef63beb8> (a java.util.ArrayList) at java.lang.Thread.run(Thread.java:595) |
仔細觀察上面的 DUMP信息,你會發現它有以下兩行:
² locked <0xef63beb8> (ajava.util.ArrayList)
² waiting on <0xef63beb8> (ajava.util.ArrayList)
這里需要解釋一下,為什么先 lock了這個對象,然后又 waiting on同一個對象呢?讓我們看看這個線程對應的代碼:
synchronized(obj){
.........
obj.wait();
.........
}
線程的執行中,先用 synchronized 獲得了這個對象的 Monitor(對應於 locked <0xef63beb8> )。當執行到 obj.wait(), 線程即放棄了 Monitor的所有權,進入 “wait set”隊列(對應於 waiting on<0xef63beb8> )。
往在你的程序中,會出現多個類似的線程,他們都有相似的 dump也可能是正常的。比如,在程序中有多個服務線程,設計成從一個隊列里面讀取請求數據。這個隊列就是 lock以及 waiting on的對象。當隊列為空的時候,這些線程都會在這個隊列上等待,直到隊列有了數據,這些線程被notify,當然只有一個線程獲得了 lock,繼續執行,而其它線程繼續等待。
dump:
Dump的本意是"傾卸垃圾"、"把(垃圾桶)倒空"。在計算機技術中使用Dump的主要意思仍然如此,即當電腦運行發現故障后,無法排除而死機,通常要重新啟動。為了找出故障的原因 ,需要分析現場(即死機時整個內存的當前狀況),在重新啟動系統之前要把內存中的一片0、 1(這時它們尤如一堆垃圾)"卸出"保存起來,以便由專家去分析引起死機的原因。技術資料中 把這個"卸出"的過程叫dump;有時把卸出的"內容"也叫dump。國際標准化組織(ISO)把前者定 義為To record,at a particular instant,the contents of all or part of one stora geevice in another storage device.Dumping is usually for the purpose of debuggin。"譯文如下:"在某個特定時刻,把一個存儲設備中的全部或部分的內容轉錄進另一個存儲設備之中。轉儲的目的通常是用於排除故障。"因此,dump作為動詞,宜譯為"轉儲";相應的動名詞,或作為名詞來看 ,則譯為"轉儲(過程、動作…)"。同時,ISO把后者定義為"Data that as been dumped。"譯文如下:"經轉儲而產生的那些數據"。這些數據實際上就是內存中由一片0、1組成的map(映像),因此,這時的dump應譯為"內像"(內存中的映像)。
明白了dump的上述二個基本含義之后,dump的其它用法就不難理解了。比如在IBM主機系統中做dump時,通常是轉儲到磁帶上,所以有人把這盤磁帶也叫dump!為了便於閱讀與分析,把內像按既定的格式打印在紙上,人們便把這一堆打印紙也叫dump!為了實現以上二項工作,必須有相應的程序,人們把這種程序也叫dump,實為dump routine的簡寫。IBM的VSE/SP操作系統中還專門有一條dump宏指令供程序員使用。
當我們把dump譯為"轉儲"時,總是指"把內存中的內容復制到其它存儲設備上",而實際使用dump時,並非一律如此,有時dump就是copy(復制)的意思。IBM的《Dictionary of Compuing》(第十版)就是這樣定義dump的:"To copy data in a readable format from mainr a uxiliary storage onto a external medium such as tape,diskette orprinter(按照可閱讀的格式,把主存或輔存中的數據復制到外部媒體,如磁帶、軟盤或打印機上。)","Tocopy the contents of all or part of virtual storage for the purpose of collectng error information(為了收集出錯信息把部分或全部虛存中的內容復制起來)。"最明顯的例子是VM/SP(IBM的大型操作系統)中有一個DDR(DASD Dump Restore:磁盤轉儲恢復)獨立程序,主用於把可運行的操作系統等軟件從磁盤(DASD)復制到磁帶上(這個過程稱為dump,或反過來,在無需操作系統的控制下 ,可把磁帶上的軟件復制回到磁盤之中,以便恢復可運行的操作系統(這個過程為restore)。這兒的dump過程就不涉及內存,類似的例子還有不少這兒就不一一
列舉了。
在影像系統中,dump被定義為一種方法或過程(process),借此數字節目代碼可以從錄像
盤傳送播放錄像的微處理器上,這時的dump就是"轉錄"的意思。同樣在影像系統中,dump還被
定義為:一次可裝入播放錄像處理器中的"一段節目代碼(a unit of program code)",一張錄
像盤上可以存放多個節目段(program dumps)。
除上述的意思外,dump有時還表示:"切斷[掉](計算機)電源"