三、jdk工具之jstack(Java Stack Trace)


目錄

一、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虛擬機自帶的一種堆棧跟蹤工具。

基本介紹

編輯
jstack用於生成java虛擬機當前時刻的線程快照。線程快照是當前java虛擬機內每一條線程正在執行的方法堆棧的集合,生成線程快照的主要目的是定位線程出現長時間停頓的原因,如線程間死鎖、死循環、請求外部資源導致的長時間等待等。
線程出現停頓的時候通過jstack來查看各個線程的調用堆棧,就可以知道沒有響應的線程到底在后台做什么事情,或者等待什么資源。

命令格式

編輯
jstack [ option ] pid
基本參數:
-F 當’jstack [-l] pid’沒有響應的時候強制打印棧信息
-l 長列表. 打印關於鎖的附加信息,例如屬於java.util.concurrent的ownable synchronizers列表.
-m 打印java和native c/c++框架的所有棧信息. -h | -help打印幫助信息
pid 需要被打印配置信息的java進程id,可以用jps工具查詢. 

案例

說明

  1. 不同的 JAVA虛機的線程 DUMP的創建方法和文件格式是不一樣的,不同的 JVM版本, dump信息也有差別。
  2. 在實際運行中,往往一次 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-死鎖

在多線程程序的編寫中,如果不適當的運用同步機制,則有可能造成程序的死鎖,經常表現為程序的停頓,或者不再響應用戶的請求。比如在下面這個示例中,是個較為典型的死鎖情況:
dump結果
  1. "Thread-1"prio=5tid=0x00acc490nid=0xe50waitingformonitorentry[0x02d3f000
  2. ..0x02d3fd68]
  3. atdeadlockthreads.TestThread.run(TestThread.java:31)
  4. -waitingtolock<0x22c19f18>(ajava.lang.Object)
  5. -locked<0x22c19f20>(ajava.lang.Object)
  6. "Thread-0"prio=5tid=0x00accdb0nid=0xdecwaitingformonitorentry[0x02cff000
  7. ..0x02cff9e8]
  8. atdeadlockthreads.TestThread.run(TestThread.java:31)
  9. -waitingtolock<0x22c19f20>(ajava.lang.Object)
  10. -locked<0x22c19f18>(ajava.lang.Object)
在 JAVA 5中加強了對死鎖的檢測。線程 Dump中可以直接報告出 Java級別的死鎖,如下所示:
dump結果
  1. FoundoneJava-leveldeadlock:
  2. =============================
  3. "Thread-1":
  4. waitingtolockmonitor0x0003f334(object0x22c19f18,ajava.lang.Object),
  5. whichisheldby"Thread-0"
  6. "Thread-0":
  7. waitingtolockmonitor0x0003f314(object0x22c19f20,ajava.lang.Object),
  8. 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;
        }
    }
}

用jstack進行分析和結果

在dos中輸入命令:jstack 7404
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.
仔細看這一段文字,告訴我們 EasyJstackResource.java:36出了狀況。如果出現了這種情況,我們就要從這里開始順藤摸瓜,解決問題。
 
=========================================================

  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(等待條件發生)

Ø 線程的調用情況;

Ø 線程對資源的鎖定情況;

 

線程的狀態分析:

正如我們剛看到的那樣,線程的狀態是一個重要的指標,它會顯示在線程每行結尾的地方。那么線程常見的有哪些狀態呢?線程在什么樣的情況下會進入這種狀態呢?我們能從中發現什么線索?

Runnable

該狀態表示線程具備所有運行條件,在運行隊列中准備操作系統的調度,或者正在運行。 

Waiton condition 

該狀態出現在線程等待某個條件的發生。具體是什么原因,可以結合stacktrace來分析。最常見的情況是線程在等待網絡的讀寫,比如當網絡數據沒有准備好讀時,線程處於這種等待狀態,而一旦有數據准備好讀之后,線程會重新激活,讀取並處理數據。在 Java引入 NIO之前,對於每個網絡連接,都有一個對應的線程來處理網絡的讀寫操作,即使沒有可讀寫的數據,線程仍然阻塞在讀寫操作上,這樣有可能造成資源浪費,而且給操作系統的線程調度也帶來壓力。在 NIO里采用了新的機制,編寫的服務器程序的性能和可擴展性都得到提高。 

如果發現有大量的線程都在處在 Wait on condition,從線程 stack看, 正等待網絡讀寫,這可能是一個網絡瓶頸的征兆。因為網絡阻塞導致線程無法執行。一種情況是網絡非常忙,幾乎消耗了所有的帶寬,仍然有大量數據等待網絡讀寫;另一種情況也可能是網絡空閑,但由於路由等問題,導致包無法正常的到達。所以要結合系統的一些性能觀察工具來綜合分析,比如 netstat統計單位時間的發送包的數目,如果很明顯超過了所在網絡帶寬的限制 ; 觀察 cpu的利用率,如果系統態的 CPU時間,相對於用戶態的 CPU時間比例較高;如果程序運行在 Solaris 10平台上,可以用 dtrace工具看系統調用的情況,如果觀察到 read/write的系統調用的次數或者運行時間遙遙領先;這些都指向由於網絡帶寬所限導致的網絡瓶頸。 

另外一種出現 Wait on condition的常見情況是該線程在 sleep,等待 sleep的時間到了時候,將被喚醒。 

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) 
- waiting to lock <0xef63bf08> (a java.lang.Object) 
- locked <0xef63beb8> (a java.util.ArrayList) 
at java.lang.Thread.run(Thread.java:595) 

臨界區的設置,是為了保證其內部的代碼執行的原子性和完整性。但是因為臨界區在任何時間只允許線程串行通過,這和我們多線程的程序的初衷是相反的。如果在多線程的程序中,大量使用 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有時還表示:"切斷[掉](計算機)電源"


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM