1. 程式人生 > >Java之JVM監控工具分享

Java之JVM監控工具分享

Java之JVM監控工具分享

JVM的基本知識常用的也就是類載入機制記憶體區域、分配、OOMGCJVM引數調優

幾個連結自己看:

今天結合程式碼講一講常用的java自帶工具講解,這些命令一般都是jdk/lib/tools.jar中。用來監控診斷我們的Java環境。

官方說明: https://docs.oracle.com/en/java/javase/11/tools/

1. jps

顯示當前使用者的所有java程序的PID 以及主類名

jps              : 顯示當前使用者的所有java程序的PID 以及主類名
jps -v           : 列印傳遞給 Java 虛擬機器的引數(如-XX:+UnlockExperimentalVMOptions -XX:+UseZGC)
jps -m           : 列印傳遞給主類的引數
jps -l           : 列印模組名以及包名

預設開啟(UsePerfData),若加上-XX:-UsePerfData 則無法找到程序。

2. jstack

功能 jstack不僅會列印執行緒的棧軌跡、執行緒狀態(BLOCKED)、持有的鎖(locked…)以及正在請求的鎖(waiting to lock …),而且還會分析出具體的死鎖

jstack pid       : 檢視執行緒情況
jstack -F pid    : 正常輸出不被響應時,使用該指令
jstack -l pid    : 除堆疊外,顯示關於鎖的附件資訊

3. jstat

功能 允許使用者檢視目標 Java 程序的類載入、即時編譯以及垃圾回收相關的資訊。它常用於檢測垃圾回收GC

問題以及記憶體洩漏問題。
顯示程序中的類裝載、記憶體、垃圾收集、JIT編譯等執行資料。
常用指令

jstat -class pid              : 列印類裝載、類解除安裝、總空間以及所消耗的時間
jstat -compiler pid           : 列印即時編譯相關的資料
jstat -printcompilation pid   : 列印即時編譯相關的資料
jstat -gc pid 1s 20           : 查詢垃圾收集情況,每1秒查詢一次,一共查詢20次。
jstat -gccause pid            : 額外輸出上次GC原因
...剩下的都是以-gc為字首的子命令,它們將列印垃圾回收相關的資料。
加上 -t引數 每行資料之前列印目標 Java 程序的啟動時間

我們可以看到,這兩個 Survivor 區的容量相等,而且始終有一個 Survivor 區的記憶體使用量為 0。
在這種情況下,Java 虛擬機器會將這塊記憶體區域回收,並標記為可分配的狀態。這樣子做的結果是,堆中可能完全沒有 Survivor 記憶體區域,因而相應的 S1C 和 S1U 將會是 0。

我們可以比較 Java 程序的啟動時間以及總 GC 時間(GCT 列),或者兩次測量的間隔時間以及總GC時間的增量,來得出 GC 時間佔運行時間的比例。
如果該比例超過 20%,則說明目前堆的壓力較大;如果該比例超過 90%,則說明堆里幾乎沒有可用空間,隨時都可能丟擲 OOM 異常。

jstat還可以用來判斷是否出現記憶體洩漏。在長時間運行的 Java 程式中,我們可以運行jstat命令連續獲取多行效能資料,並取這幾行資料中 OU 列(即已佔用的老年代記憶體)的最小值。
然後,我們每隔一段較長的時間重複一次上述操作,來獲得多組 OU 最小值。如果這些值呈上漲趨勢,則說明該 Java 程式的老年代記憶體已使用量在不斷上漲,這意味著無法回收的物件在不斷增加,因此很有可能存在記憶體洩漏。

CGC 和 CGCT,它們分別代表併發 GC Stop-The-World 的次數和時間。

S0C:年輕代中第一個survivor(倖存區)的容量 (kb)
S1C:年輕代中第二個survivor(倖存區)的容量 (kb)
S0U:年輕代中第一個survivor(倖存區)目前已使用空間 (kb)
S1U:年輕代中第二個survivor(倖存區)目前已使用空間 (kb)
EC:年輕代中Eden(伊甸園)的容量 (kb)
EU:年輕代中Eden(伊甸園)目前已使用空間 (kb)
OC:老年代的容量 (kb)
OU:老年代目前已使用空間 (kb)
MC:元空間的容量 (kb)
MU:元空間目前已使用空間 (kb)
CCSC:壓縮類的容量 (kb)
CCSU:壓縮類目前已使用空間 (kb)
YGC:年輕代垃圾回收次數
YGCT:年輕代垃圾回收消耗時間
FGC:老年代垃圾回收次數
FGCT:老年代垃圾回收消耗時間
GCT:垃圾回收消耗總時間

4. jmap

功能 生成堆轉儲快照(heapdump) 使用者統計目標 Java 程序的堆中存放的 Java 物件,並將它們匯出成二進位制檔案。查詢Java堆和永久代的詳細資訊,使用率,使用大小,查詢finalize執行佇列的資訊
常用指令

jmap -heap  pid                     : 列印jvm heap的情況  
jmap -histo pid                     : 列印jvm heap的直方圖。其輸出資訊包括類名,物件數量,物件佔用大小。 並按照記憶體使用量從多至少的順序排列
jmap -histo:live pid                : JVM會先觸發gc,然後再統計資訊,只統計堆中的存活物件的情況  
jmap -dump:format=b,file=map.log pid: 將記憶體使用的詳細情況輸出到檔案,之後一般使用其他工具進行分析。同樣,-dump:live只儲存堆中的存活物件。
jmap -clstats pid                   : 列印被載入類的資訊
jmap -finalizerinfo pid             : 該子命令將列印所有待 finalize 的物件。
jmap -permstat pid                  : 列印permanent generation heap情況

我們通常會利用jmap -dump:live,format=b,file=filename.bin命令,將堆中所有存活物件匯出至一個檔案之中。
這里format=b將使jmap匯出與hprof(在 Java 9 中已被移除)、-XX:+HeapDumpAfterFullGC、-XX:+HeapDumpOnOutOfMemoryError格式一致的檔案。這種格式的檔案可以被其他 GUI 工具檢視。

jmap(以及jinfo、jstack和jcmd)依賴於 Java 虛擬機器的Attach API,因此只能監控本地 Java 程序。
一旦開啟 Java 虛擬機器引數DisableAttachMechanism(即使用引數-XX:+DisableAttachMechanism),基於 Attach API 的命令將無法執行。反過來說,如果你不想被其他程序監控,那麼你需要開啟該引數。

5. jhat

功能 一般與jmap搭配使用,用來分析jmap生成的堆轉儲檔案。
由於有很多視覺化工具(Eclipse Memory Analyzer 、IBM HeapAnalyzer)可以替代,所以很少用。不過在沒有視覺化工具的機器上也是可用的。
常用指令

jmap -dump:format=b,file=map.log pid   : 將記憶體使用的詳細情況輸出到檔案
jhat map.log                           : 解析Java堆轉儲檔案,並啟動一個 web server

演示:https://www.cnblogs.com/baihuitestsoftware/articles/6406271.html

6. jinfo

功能 列印目標 Java 程序的配置引數
實時檢視和調整虛擬機器引數,可以顯示未被顯示指定的引數的預設值(jps -v 則不能)。

jinfo pid :可用來檢視目標 Java 程序的引數,如傳遞給 Java 虛擬機器的-X(即輸出中的 jvm_args)、-XX引數(即輸出中的 VM Flags),以及可在 Java 層面通過System.getProperty獲取的-D引數(即輸出中的 System Properties)。

7. jcmd

可以用來實現前面除了jstat之外所有命令的功能。
詳見 :https://www.jianshu.com/p/388e35d8a09b

8. javap

javap 是一個能夠將 class 檔案反彙編成人類可讀格式的工具。 ASM位元組碼操作:https://blog.csdn.net/ohcezzz/article/details/78416176

預設情況下 javap 會列印所有非私有的欄位和方法,
-p 列印私有的欄位和方法。
-v 列印所有資訊。
-c 查閱方法對應的位元組碼

附:

一隻懂JVM引數的狐狸

程式碼驗證

@Slf4j
public class JvmTest {

    private byte[] memory;

    public JvmTest(byte[] memory) {
        this.memory = memory;
    }

    public static void main(String[] args) throws InterruptedException {

        GC測試();
        //死迴圈();
        //死鎖();
    }

    public static void GC測試() throws InterruptedException {
        for (int i = 1; i < 5; i++) {
            byte[] b = new byte[50 * 1024 * 1024];
            log.info("分配了50M空間給陣列");
            Thread.sleep(10000);
        }
        //方法區中常量引用物件 (虛擬機器棧(棧幀中的區域性變數)中引用的物件 - 方法區中的靜態變數引用的物件 - 本地方法棧中JNI(即一般說的Native方法)中引用的物件)
        JvmTest jvmTest = new JvmTest(new byte[50 * 1024 * 1024]);
        log.info("分配了50M空間給物件");
        log.info("呼叫了System.gc()");
        System.gc();
        jvmTest = null;
        Thread.sleep(10000);
        log.info("呼叫了System.gc()");
        System.gc();
        Thread.sleep(3000000);
    }

    public static void 死迴圈() {
        while (true) {
        }
    }

    public static void 死鎖() {
        String obj1 = "obj1";
        String obj2 = "obj2";
        Runnable r1 = () -> {
            log.info("r1 running");
            while (true) {
                synchronized (obj1) {
                    log.info("r1 lock obj1");
                    try {
                        //獲取obj1後先等一會兒,讓Lock2有足夠的時間鎖住obj2
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (obj2) {
                        log.info("r1 lock obj2");
                    }
                }
            }
        };
        Runnable r2 = () -> {
            log.info("r2 running");
            while (true) {
                synchronized (obj2) {
                    log.info("r2 lock obj2");
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (obj1) {
                        log.info("r2 lock obj1");
                    }
                }
            }
        };
        Thread a = new Thread(r1);
        Thread b = new Thread(r2);
        a.start();
        b.star