一些JDK自帶的性能分析利器

有時候碰到服務器CPU飆升或者程序卡死之類的問題,一般都不太好定位。這類bug一般都隱藏的比較深並且還可能是偶發性的,比較棘手。

對於此類問題,一般我們都有固定的分析流程。藉助於JDK自帶的一些分析工具,比如jstack、jmap、jstat一類的命令行工具,除此之外,還有jconsole、mat、jvisualvm這些圖形界面分析工具。

這篇文章基於JDK8,操作系統是macOS 12.0.1

1、一些命令行分析工具

這些命令行分析工具都在jdk/bin目錄下

解壓jdk/lib/tool.jar可以得到上述工具的class文件

1.1 jps - JVM Process Status Tool

作用:列出正在運行的虛擬機進程,並顯示虛擬機執行主類名稱以及這些進程的本地虛擬機唯一ID。

第一個參數說明:

  • -q:默認攜帶的參數,顯示進程ID。

  • -m:顯示進程ID,主類名稱,以及傳入main方法的參數。

  • -l:顯示進程ID,主類全名。

  • -v:顯示進程ID,主類名稱,以及傳入JVM的參數。

  • -V:顯示進程ID,主類名稱。

[-mlvV]可以任意組合使用。

第二個參數說明:

  • hostid:服務器的ip地址。不指定的情況下,默認爲當前服務器。如果要查看其他機器上的JVM進程,需要在待查看機器上啓動jstatd。

1.2 jstat - JVM Statistics Monitoring Tool

作用:監視虛擬機各種運行狀態信息,可以顯示本地或者遠程虛擬機進程中的類裝載、內存、垃圾收集、JIT編譯等運行數據。

命令格式:jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]

參數說明

第一個參數:option,代表用戶希望查詢的虛擬機信息,主要分爲3類:類裝載、垃圾收集和運行期編譯情況。具體選項如下:

  • -class:顯示有關類加載器行爲的統計信息。

  • -compiler:顯示有關java hotspot vm即時編譯器行爲的統計信息。

  • -gc:顯示有關垃圾收集堆行爲的統計信息。

  • -gccapacity:顯示有關各個垃圾回收代容量及其相應容量的統計信息。

  • -gccause:顯示有關垃圾收集統計信息(同-gcutil),以及上一次和當前垃圾收集事件的原因。

  • -gcnew:顯示新生代行爲的統計信息。

  • -gcnewcapacity:顯示有關新生代大小及其相應空間的統計信息。

  • -gcold:顯示老年代行爲的統計信息和元空間統計信息。

  • -gcoldcapacity:顯示有關老年代大小的統計信息。

  • -gcmetacapacity:顯示有關元空間大小的統計信息。

  • -gcutil:顯示有關垃圾收集統計信息。

  • -printcompilation:顯示java hotspot vm編譯方法統計信息。

第二個參數:vmid。如果是本地虛擬機進程,那麼vmid和本地虛擬機唯一ID是一致的。如果是遠程虛擬機進程,那麼vmid的格式是:protocol:lvmid[@hostname[:port]/servername]

第三個參數:interval。採樣間隔,單位爲s或ms,默認單位是ms,必須爲整數。指定該參數,jstat命令將在每個間隔產生輸出。

第四個參數:count。要顯示的樣本數。

import java.io.IOException;

/**
 * -class:
 *  Loaded:已加載的類數量。
 *  Bytes:已加載的kb數。
 *  Unloaded:卸載的類數量。
 *  Bytes:卸載的kb數。
 *  Time:執行類加載和卸載的耗時。
 *
 * -compiler:
 *  Compiled:執行的編譯任務數。
 *  Failed:編譯失敗的任務數。
 *  Invalid:無效的編譯任務數。
 *  Time:執行編譯任務所花費的時間。
 *  FailedType:失敗的編譯類型。
 *  FailedMethod:失敗的編譯類名和方法。
 */
public class Demo1_jstat {
    public static void main(String[] args) throws IOException {
        System.out.println("jstat");
        System.in.read();
    }
}

啓動上面的demo後

import java.io.IOException;

/**
 * -gc: 垃圾收集的堆統計信息
 *  S0C:s0區的容量(kb)
 *  S1C:s1區的容量(kb)
 *  S0U:s0區的使用大小(kb)
 *  S1U:s1區的使用大學(kb)
 *  EC:eden區的容量(kb)
 *  EU:eden區的使用大小(kb)
 *  OC:老年代容量(kb)
 *  OU:老年代的使用大小(kb)
 *  MC:元空間的容量(kb)
 *  MU:元空間的使用大小(kb)
 *  CCSC:壓縮的類空間容量(kb)
 *  CCSU:使用的壓縮類空間大小(kb)
 *  YGC:新生代垃圾收集次數
 *  YGCT:新生代垃圾回收時間
 *  FGC:full gc收集次數
 *  FGCT:full gc垃圾回收時間
 *  GCT:總垃圾收集時間
 *
 * -gcutil:垃圾收集統計信息
 *  S0:s0區的使用率
 *  S1:s1區的使用率
 *  E:eden區使用率
 *  O:老年代使用率
 *  M:元空間使用率
 *  CCS:壓縮類空間使用率
 *  YGC:新生代gc次數
 *  YGCT:新生代gc耗費時間
 *  FGC:full gc次數
 *  FGCT:full gc耗費時間
 *  GCT:總垃圾收集時間
 */
public class Demo2_jstat {
    // -Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -verbose:gc
    public static void main(String[] args) throws IOException {
        final int _1MB = 1024 * 1024;
        // 申請2M的空間
        byte[] b1 = new byte[2 * _1MB];
        System.out.println("1111");
        System.in.read();

        // 申請2M的空間
        byte[] b2 = new byte[2 * _1MB];
        System.out.println("2222");
        System.in.read();

        // 申請2M的空間
        byte[] b3 = new byte[2 * _1MB];
        System.out.println("3333");
        System.in.read();
    }
}

啓動上面的程序之前,先指定一些jvm參數

-Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -verbose:gc

然後啓動demo

剛啓動時,程序會申請2M的數組,可以看到eden區的使用率是62.37%,ygc的值爲0。

讓程序申請第二個2M的數組,之後再查看堆內存信息

可以發現eden區的使用率達到了87.37%。

讓程序申請第三個2M的數組,發現控制檯輸出提示

再查看堆內存信息

發現eden區的使用率降到了26.71,s1區使用率時70.80%,老年代的使用率是40%即4096kb也就是4M空間大小。同時ygc的次數是1,說明進行了一次ygc,並且把大對象放進了老年代中。

1.3 jinfo - Configuration Info For Java

作用:實時的查看和調整虛擬機各項參數。

命令格式:jinfo [option] <pid>

參數說明

第一個參數:option。

  • no options:輸出全部的參數和系統屬性。

  • -flag name:輸出對應名稱的參數。

  • -flag [+ | -] name:開啓或者關閉對應名稱的參數。

  • -flag name=value:設置對於名稱參數的參數值。

  • -flags:輸出全部的參數。

  • -sysprops:輸出全部的系統屬性。

命令演示

  • jinfo pid

  • jinfo -flag PrintGCDetails pid (輸出PrintGCDetails參數的值)

  • jinfo -flag +PrintGCDetails pid (開啓PrintGCDetails參數)

  • jinfo -flag HeapDumpPath=/ pid (設置HeapDumpPath參數的值)

在我電腦上使用這個命令會報錯,據說是macOS的一個bug,需要升級到jdk9,懶得升了。bug鏈接

1.4 jmap - Memory Map For Java

作用:一個多功能的命令,它可以生成Java程序的dump文件,也可以查看堆內對象的信息、classloader的信息和finalizer隊列。

命令格式:jmap [option] <pid>

參數解釋

第一個參數:option

  • no option:查看進程的內存鏡像信息。

  • -heap:顯示Java堆詳細信息。

  • -histo[:live]:顯示堆中對象的統計信息。

  • -clstats:打印類加載器信息。

  • -finalizerinfo:顯示在F-Queue隊列等待Finalizer線程執行finaizer方法的對象。

  • -dump:<dump-options>:生成堆轉儲快照。

命令演示

  • jamp pid。打印出虛擬機加載的每個共享對象的起始地址、映射大小以及共享對象文件的路徑全稱。

  • jamp -heap pid。打印一個堆的摘要信息,包括使用的GC算法、堆配置信息和各內存區域內存使用信息。

  • jmap -histo:live pid。顯示堆中對象的統計信息,包括每個Java類型,對象數量,內存大小(單位字節),完全限定的類名。打印的虛擬機內部的類名稱將會帶一個‘*’前綴。如果指定了live子選項,則只計算活動的對象。

  • jmap -clstats pid。打印Java堆內存的永久保存區域的類加載器的智能統計信息。對於每個類加載器而言,它的名稱、活躍度、地址、父類加載器、它所加載的類的數量和大小都會打印。

  • jmap -finalizerinfo pid。打印等待終結的對象信息。Number of objects pending for finalization: 0說明沒有等待終結的對象。

  • jamp -dump:live,format=b,file=./jmap.bin pid。以二進制格式轉儲java堆到指定路徑下的filename文件中。指定了live子選項,則只會轉儲活動的對象。

在macOS上使用這個命令同樣也會報錯。但某些命令還是可以的,比如dump二進制文件。

1.5 jhat - JVM Heap Dump Browser

作用:與jmap搭配使用,用來分析jmap生成的堆轉儲文件。jhat內置了一個微型的http/html服務器,生成dump文件的分析結果後,可以在瀏覽器中查看。

不過這個工具一般比較少使用,一是因爲功能比較簡陋,VisualVM和MAT等工具完全能夠替代它。二是因爲我們一般不會在生產服務器上直接去dump二進制文件,並且分析二進制文件是一個比較耗時的工作,所以就沒必要使用命令行工具了。

命令格式:jhat [options] 堆轉儲文件

參數解釋

第一個參數:option

  • [-stack <bool>]:開關對象分配調用棧跟蹤,如果分配位置信息在堆轉儲中不可用,則必須將此標誌設置爲false,默認爲true。

  • [-refs <bool>]:開關對象引用跟蹤,默認爲true。默認情況下,返回的指針是指向其他特定對象的對象。如果爲false則會統計堆中所有對象。

  • [-port <port>]:設置jhat http server的端口號,默認爲7000。

  • [-exclude <file>]:指定對象查詢時需要排除的數據成員列表文件。例如,如果文件列出了java.lang.String.value,那麼當從某個對象Object o計算可達的對象列表時,引用路徑涉及java.lang.String.value的都會排除。

  • [-baseline <file>]:指定一個基準堆轉儲。在兩個heap dumps中有相同object ID的對象會被標記爲不是新的,其他對象會被標記爲新的(new),在比較兩個不同的堆轉儲時有用。

  • [-debug <int>]:設置debug級別,0表示不輸出調試信息,值越大表示輸出的調試信息越詳細。[0, 1, 2]

  • [-version]:啓動後只顯示版本信息就退出。

第二個參數:堆轉儲文件。

命令演示

我們可以先生成一個二進制文件。

然後使用jhat命令進行分析

瀏覽器訪問8888端口

1.6 jstack - Stack Trace For Java

作用:查看或導出Java應用程序中線程堆棧信息。

線程快照是當前Java虛擬機內每一條線程正在執行的方法堆棧的集合。生成線程快照的主要目的是定位線程出現長時間停頓的原因,比如線程死鎖、死循環、長時間等待外部資源等。線程出現停頓的時候通過jstack來查看各個線程的調用堆棧,就可以知道沒有響應的線程到底在後臺做什麼事情,或者等待什麼資源。如果Java程序崩潰生成core文件,jstack工具可以用來獲得core文件的java stack和native stack的信息,從而輕鬆地知道Java程序是如何崩潰和在程序何處發生問題。另外jstack工具還可以附屬到正在運行的java程序中,看到當時運行的java程序的java stack和native stack信息。

命令格式:jstack [options] <pid>

參數說明

第一個參數:options

  • -F:當線程掛起時,使用jstack -l pid請求不被響應時,強制輸出線程堆棧。

  • -l:除堆棧外,顯示關於鎖的附加信息,比如ownable synchronizers。

  • -m:可以同時輸出java以及C/C++的堆棧信息。

命令演示:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Demo5_jstack {
    public static void main(String[] args) {
        System.out.println("start");
        test1();
        // test2();
        System.out.println("end");
    }

    // 測試死循環
    private static void test1() {
        while (true) {}
    }

    // 測試死鎖
    private static void test2() {
        Lock lock1 = new ReentrantLock();
        Lock lock2 = new ReentrantLock();

        new Thread(() -> {
            try {
                lock1.lock();
                Thread.sleep(100);
                lock2.lock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "thread1").start();

        new Thread(() -> {
            try {
                lock2.lock();
                Thread.sleep(100);
                lock1.lock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "thread2").start();
    }
}

測試死循環

啓動程序後,top命令查看進程狀態

Linux系統中可以使用top -Hp 50242命令查看進程下的線程信息,但在macOS上不支持這個命令。我也沒找到怎麼查看macOS裏進程下所有線程的方式==。

一般在Linux上的步驟就是下面這幾步:

(1)top查看哪個進程cpu最高。

(2)top -Hp pid查看進程下面哪個線程cpu最高。

(3)jstack -l pid打印出進程的堆棧信息,然後將佔有cpu最高的線程id轉換爲16進制,將這個16進制在堆棧信息中查詢它的位置,一般都能定位到具體的代碼位置。

測試死鎖

調用test2方法,然後啓動程序,使用jstack -l pid命令能夠打印出死鎖信息。

2、一些可視化分析工具

2.1 jConsole

使用jConsole可以查看程序的堆內存使用量、線程信息、CPU使用信息等。

在控制檯輸入jconsole命令,選擇我們本地的程序

進入後就能看到一些基本信息了

在內存模塊,我們可以查看新生代、老年代、元空間等區域的使用率

在線程模塊,我們能看到該進程下的所有線程,同時還能檢測死鎖

在VM概要模塊,則可以看到本機的一些JVM信息。

2.2 Visual VM

作用:是到目前爲止隨JDK發佈的功能最強大的運行監視和故障處理程序。官方在VisualVM的軟件說明中寫上了“All-in-One”的描述,說明它除了運行監視、故障處理外,還提供了很多其他方面的功能。如性能分析,VisualVM的性能分析甚至比很多專業的收費工具都好用,而且VisualVM不需要被監視的程序基於特殊的運行,因此它對應用程序的實際性能的影響很小,使得它可以直接應用在生產環境中。

VisualVM基於NetBeans平臺開發,因此它一開始就具備了插件擴展功能的特性,通過插件擴展支持,VisualVM可以做到:

  • 顯示虛擬機進程以及進程的配置、環境信息(jps、jinfo)

  • 監視應用程序的CPU、GC、堆、方法區以及線程的信息(jstat、jstack)

  • dump以及分析堆轉儲快照(jmap、jhat)

  • 方法級的程序運行性能分析,找到被調用最多、運行時間最長的方法。

  • 離線程序快照:收集程序的運行時配置、線程dump、內存dump等信息建立一個快照。

  • 動態的安裝plugins。

使用:在控制檯輸入jvisualvm執行即可。進入後主頁還會有一些文檔,十分貼心。

在工具欄可以進行插件的安裝

可以看到左側可以選擇本地進程或者遠程的進程,選擇我們的目標程序,頂部有概述、監視、線程、抽樣器、Profiler幾個選項。

監視頁面和jconsole的也有點像,不過在visualvm中可以直接進行堆dump文件分析

在線程頁面,還可以檢測程序的死鎖,進行線程dump的分析

還有很多的功能大家可以一一去看看。以前還不知道JDK自帶了這麼多性能分析利器啊,以後遇到一些性能問題可以嘗試使用一下上面的工具,也不需要額外安裝。

文章首發於我的公衆號【禿頭哥編程】,歡迎大家關注。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章