1. 程式人生 > 實用技巧 >jdk視覺化工具系列——檢視閱讀

jdk視覺化工具系列——檢視閱讀

jdk視覺化工具系列——檢視閱讀

參考

java虛擬機器系列

RednaxelaFX知乎問答

RednaxelaFX部落格

JConsole——Java監視與管理控制檯

jconsole介紹

JConsole(java monitoring and management console)是一款基於JMX(Java Management Extensions,即Java管理擴充套件)的視覺化監視和管理工具。

啟動JConsole

  1. 點選JDK/bin 目錄下面的“jconsole.exe”即可啟動
  2. 然後會自動搜尋本機執行的所有虛擬機器程序
  3. 選擇其中一個程序可開始進行監控

JConsole基本介紹

JConsole 基本包括以下基本功能:概述、記憶體、執行緒、類、VM概要、MBean

執行下面的程式、然後使用JConsole進行監控;注意設定虛擬機器引數

/**
 * <p>
 * 設定虛擬機器引數:-Xms100M -Xmx100m -XX:+UseSerialGC -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:/dump1.hprof
 */
public class Demo1 {
    static class OOMObject {
        //public byte[] placeholder = new byte[64 * 1024];
        public byte[] placeholder = new byte[64 * 1024 * 1024]; //create OOM
    }

    public static void fillHeap(int num) throws InterruptedException {
        Thread.sleep(20000); //先執行程式,在執行監控
        List<OOMObject> list = new ArrayList<OOMObject>();
        for (int i = 0; i < num; i++) {
            // 稍作延時,令監視曲線的變化更加明顯
            Thread.sleep(50);
            list.add(new OOMObject());
        }
        System.gc();
    }

    public static void main(String[] args) throws Exception {
        fillHeap(1000);
        while (true) {
            //讓其一直執行著
        }
    }
}

開啟JConsole檢視上面程式

可以切換頂部的選項卡檢視各種指標資訊。

記憶體監控——視覺化的jstat 命令

“記憶體”頁籤相當於視覺化的jstat 命令,用於監視受收集器管理的虛擬機器記憶體的變換趨勢。

還是上面的程式:

程式碼執行,控制檯也會輸出gc日誌:(因為我們添加了引數:-XX:+PrintGCDetails)

、[GC (Allocation Failure) [DefNew: 27273K->3392K(30720K), 0.0120476 secs] 27273K->15632K(99008K), 0.0120955 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [DefNew: 30667K->3392K(30720K), 0.0152203 secs] 42907K->38103K(99008K), 0.0152500 secs] [Times: user=0.02 sys=0.00, real=0.02 secs] 
[GC (Allocation Failure) [DefNew: 30710K->3376K(30720K), 0.0297297 secs] 65422K->63609K(99008K), 0.0297929 secs] [Times: user=0.03 sys=0.00, real=0.03 secs] 
[Full GC (System.gc()) [Tenured: 60232K->66947K(68288K), 0.0107243 secs] 67227K->66947K(99008K), [Metaspace: 9501K->9501K(1058816K)], 0.0107834 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [DefNew: 27328K->27328K(30720K), 0.0000201 secs][Tenured: 66947K->2319K(68288K), 0.0147126 secs] 94275K->2319K(99008K), [Metaspace: 9724K->9724K(1058816K)], 0.0148269 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 

//placeholder加大測試生產dump檔案
[GC (Allocation Failure) [DefNew: 4946K->1232K(30720K), 0.0019894 secs][Tenured: 65536K->66767K(68288K), 0.0027748 secs] 70482K->66767K(99008K), [Metaspace: 3477K->3477K(1056768K)], 0.0048224 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[Full GC (Allocation Failure) [Tenured: 66767K->66746K(68288K), 0.0016820 secs] 66767K->66746K(99008K), [Metaspace: 3477K->3477K(1056768K)], 0.0017023 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
java.lang.OutOfMemoryError: Java heap space
Dumping heap to D:/dump1.hprof ...
Unable to create D:/dump1.hprof: File exists
Heap
 def new generation   total 30720K, used 820K [0x00000000f9c00000, 0x00000000fbd50000, 0x00000000fbd50000)
  eden space 27328K,   3% used [0x00000000f9c00000, 0x00000000f9ccd0e0, 0x00000000fb6b0000)
  from space 3392K,   0% used [0x00000000fba00000, 0x00000000fba00000, 0x00000000fbd50000)
  to   space 3392K,   0% used [0x00000000fb6b0000, 0x00000000fb6b0000, 0x00000000fba00000)
 tenured generation   total 68288K, used 66746K [0x00000000fbd50000, 0x0000000100000000, 0x0000000100000000)
   the space 68288K,  97% used [0x00000000fbd50000, 0x00000000ffe7e980, 0x00000000ffe7ea00, 0x0000000100000000)
 Metaspace       used 3512K, capacity 4500K, committed 4864K, reserved 1056768K
  class space    used 381K, capacity 388K, committed 512K, reserved 1048576K
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at com.self.test.Demo1$OOMObject.<init>(Demo1.java:19)
	at com.self.test.Demo1.fillHeap(Demo1.java:28)
	at com.self.test.Demo1.main(Demo1.java:34)

執行緒監控——視覺化的jstack命令

如果上面的“記憶體”頁籤相當於視覺化的jstat命令的話,“執行緒”頁籤的功能相當於視覺化的jstack命令,遇到執行緒停頓時可以使用這個頁籤進行監控分析。執行緒長時間停頓的主要原因主要有:等待外部資源(資料庫連線、網路資源、裝置資源等)、死迴圈、鎖等待(活鎖和死鎖)

下面三個方法分別等待控制檯輸入、死迴圈演示、執行緒鎖等待演示

第一步:執行如下程式碼:

public class Demo2 {
    public static void main(String[] args) throws IOException {
        waitRerouceConnection();
        createBusyThread();
        createLockThread(new Object());
    }

    /**
     * 等待控制檯輸入
     *
     * @throws IOException
     */
    public static void waitRerouceConnection() throws IOException {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
                try {
                    br.readLine();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }, "waitRerouceConnection");
        thread.start();

    }

    /**
     * 執行緒死迴圈演示
     */
    public static void createBusyThread() {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    ;
                }
            }
        }, "testBusyThread");
        thread.start();
    }

    /**
     * 執行緒鎖等待演示
     */
    public static void createLockThread(final Object lock) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }, "testLockThread");
        thread.start();
    }
}

第二步:開啟jconsole中檢視上面程式執行情況,可以檢視到3個目標執行緒

第三步:檢視目標執行緒資訊

waitRerouceConnection執行緒處於讀取資料狀態,如上圖:

testBusyThread執行緒位於程式碼45行,處於執行狀態,如下圖:

testLockThread處於活鎖等待狀態,如下圖:

只要lock物件的notify()或notifyAll()方法被呼叫,這個執行緒便可能啟用以繼續執行

通過“執行緒”這個視窗可以很方便查詢虛擬機器中的執行緒堆疊資訊,對發現系統中的一些問題非常有幫助。

執行緒死鎖演示

第一步:執行下面程式碼:

package com.jvm.jconsole;

/**
 * <b>description</b>: <br>
 * <b>time</b>:2019/6/2 22:39 <br>
 * <b>author</b>:ready [email protected]
 */
public class Demo3 {
    public static void main(String[] args) {
        User u1 = new User("u1");
        User u2 = new User("u2");
        Thread thread1 = new Thread(new SynAddRunalbe(u1, u2, 1, 2, true));
        thread1.setName("thread1");
        thread1.start();
        Thread thread2 = new Thread(new SynAddRunalbe(u1, u2, 2, 1, false));
        thread2.setName("thread2");
        thread2.start();
    }

    /**
     * 執行緒死鎖等待演示
     */
    public static class SynAddRunalbe implements Runnable {
        User u1, u2;
        int a, b;
        boolean flag;

        public SynAddRunalbe(User u1, User u2, int a, int b, boolean flag) {
            this.u1 = u1;
            this.u2 = u2;
            this.a = a;
            this.b = b;
            this.flag = flag;
        }

        @Override
        public void run() {
            try {
                if (flag) {
                    synchronized (u1) {
                        Thread.sleep(100);
                        synchronized (u2) {
                            System.out.println(a + b);
                        }
                    }
                } else {
                    synchronized (u2) {
                        Thread.sleep(100);
                        synchronized (u1) {
                            System.out.println(a + b);
                        }
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static class User {
        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public User(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    '}';
        }
    }
}

thread1持有u1的鎖,thread2持有u2的鎖,thread1等待獲取u2的鎖,thread2等待獲取u1的鎖,相互需要獲取的鎖都被對方持有者,造成了死鎖。程式中出現了死鎖的情況,我們是比較難以發現的。需要依靠工具解決。剛好jconsole就是這個美妙的工具。

第二步:在jconsole中開啟上面程式的監控資訊:

從上面可以看出程式碼48行和55行處導致了死鎖。

關於程式死鎖的,我們還可以使用命令列工具jstack來檢視java執行緒堆疊資訊,也可以發現死鎖。

jvisualvm——多合一故障處理工具

VisualVM 是一款免費的,集成了多個 JDK 命令列工具的視覺化工具,它能為您提供強大的分析能力,對 Java 應用程式做效能分析和調優。這些功能包括生成和分析海量資料、跟蹤記憶體洩漏、監控垃圾回收器、執行記憶體和 CPU 分析,同時它還支援在 MBeans 上進行瀏覽和操作。本文主要介紹如何使用 VisualVM 進行效能分析及調優。

VisualVM位於{JAVA_HOME}/bin目錄中。

檢視jvm配置資訊

第一步:雙擊左邊視窗顯示正在執行的java程序

第二步:點選右側視窗“概述”,可以檢視各種配置資訊

通過jdk提供的jinfo命令工具也可以檢視上面的資訊。

檢視cpu、記憶體、類、執行緒監控資訊——檢視tab頁

檢視堆的變化

步驟一:執行下面的程式碼

每隔3秒,堆記憶體使用新增100M

public class Demo1 {
    public static final int _1M = 1024 * 1024;

    public static void main(String[] args) throws InterruptedException {
        List<Object> list = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            list.add(new byte[100 * _1M]);
            TimeUnit.SECONDS.sleep(3);
            System.out.println(i);
        }
    }
}

步驟二:在VisualVM可以很清晰的看到堆記憶體變化資訊。

檢視堆快照

步驟一:點選“監視”->"堆(dump)"可以生產堆快照資訊.

生成了以heapdump開頭的一個選項卡,內容如下:

對於“堆 dump”來說,在遠端監控jvm的時候,VisualVM是沒有這個功能的,只有本地監控的時候才有。

匯出堆快照檔案

步驟一:檢視堆快照,此步驟可以參考上面的“檢視堆快照”功能

步驟二:右鍵點選另存為,即可匯出hprof堆快照檔案,可以發給其他同事分析使用

檢視class物件載入資訊

其次來看下永久保留區域PermGen使用情況

步驟一:執行一段類載入的程式,程式碼如下:

public class Demo2 {

    private static List<Object> insList = new ArrayList<Object>();

    public static void main(String[] args) throws Exception {
        permLeak();
    }

    private static void permLeak() throws Exception {
        for (int i = 0; i < 2000; i++) {
            URL[] urls = getURLS();
            URLClassLoader urlClassloader = new URLClassLoader(urls, null);
            Class<?> logfClass = Class.forName("org.apache.commons.logging.LogFactory", true, urlClassloader);
            Method getLog = logfClass.getMethod("getLog", String.class);
            Object result = getLog.invoke(logfClass, "TestPermGen");
            insList.add(result);
            System.out.println(i + ": " + result);
            if (i % 100 == 0) {
                TimeUnit.SECONDS.sleep(1);
            }
        }
    }

    private static URL[] getURLS() throws MalformedURLException {
        File libDir = new File("D:\\installsoft\\maven\\.m2\\repository3.3.9_0\\commons-logging\\commons-logging\\1.1.1");
        File[] subFiles = libDir.listFiles();
        int count = subFiles.length;
        URL[] urls = new URL[count];
        for (int i = 0; i < count; i++) {
            urls[i] = subFiles[i].toURI().toURL();
        }
        return urls;
    }

}

步驟二:開啟visualvm檢視,metaspace

CPU分析:發現cpu使用率最高的方法

CPU 效能分析的主要目的是統計函式的呼叫情況及執行時間,或者更簡單的情況就是統計應用程式的 CPU 使用情況。

下面我們寫一個cpu佔用率比較高的程式。

步驟一:執行下列程式:

public class Demo3 {
    public static void main(String[] args) throws InterruptedException {
        cpuFix();
    }

    /**
     * cpu 執行固定百分比
     *
     * @throws InterruptedException
     */
    public static void cpuFix() throws InterruptedException {
        // 80%的佔有率
        int busyTime = 8;
        // 20%的佔有率
        int idelTime = 2;
        // 開始時間
        long startTime = 0;
        while (true) {
            // 開始時間
            startTime = System.currentTimeMillis();
            /*
             * 執行時間
             */
            while (System.currentTimeMillis() - startTime < busyTime) {
                ;
            }
            // 休息時間
            Thread.sleep(idelTime);
        }
    }
}

步驟二:開啟visualvm檢視cpu使用情況,我的電腦是4核的,如下圖:

過高的 CPU 使用率可能是我們的程式程式碼效能有問題導致的。可以切換到“抽樣器”對cpu進行取樣,可以檢視到哪個方法佔用的cpu最高,然後進行優化。

從圖中可以看出cpuFix方法使用cpu最多,然後就可以進行響應的優化了。

檢視執行緒快照:發現死鎖問題

Java 語言能夠很好的實現多執行緒應用程式。當我們對一個多執行緒應用程式進行除錯或者開發後期做效能調優的時候,往往需要了解當前程式中所有執行緒的執行狀態,是否有死鎖、熱鎖等情況的發生,從而分析系統可能存在的問題。

在 VisualVM 的監視標籤內,我們可以檢視當前應用程式中所有活動執行緒(Live threads)和守護執行緒(Daemon threads)的數量等實時資訊。

可以檢視執行緒快照,發現系統的死鎖問題。

下面我們將通過visualvm來排查一個死鎖問題。

步驟一:執行下面的程式碼:

public class Demo4 {

    public static void main(String[] args) {
        Obj1 obj1 = new Obj1();
        Obj2 obj2 = new Obj2();
        Thread thread1 = new Thread(new SynAddRunalbe(obj1, obj2, 1, 2, true));
        thread1.setName("thread1");
        thread1.start();
        Thread thread2 = new Thread(new SynAddRunalbe(obj1, obj2, 2, 1, false));
        thread2.setName("thread2");
        thread2.start();
    }

    /**
     * 執行緒死鎖等待演示
     */
    public static class SynAddRunalbe implements Runnable {
        Obj1 obj1;
        Obj2 obj2;
        int a, b;
        boolean flag;

        public SynAddRunalbe(Obj1 obj1, Obj2 obj2, int a, int b, boolean flag) {
            this.obj1 = obj1;
            this.obj2 = obj2;
            this.a = a;
            this.b = b;
            this.flag = flag;
        }

        @Override
        public void run() {
            try {
                if (flag) {
                    synchronized (obj1) {
                        Thread.sleep(100);
                        synchronized (obj2) {
                            System.out.println(a + b);
                        }
                    }
                } else {
                    synchronized (obj2) {
                        Thread.sleep(100);
                        synchronized (obj1) {
                            System.out.println(a + b);
                        }
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static class Obj1 {
    }

    public static class Obj2 {
    }
}

程式中:thread1持有obj1的鎖,thread2持有obj2的鎖,thread1等待獲取obj2的鎖,thread2等待獲取obj1的鎖,相互需要獲取的鎖都被對方持有者,造成了死鎖。程式中出現了死鎖的情況,我們是比較難以發現的。需要依靠工具解決。

步驟二:開啟visualvm檢視堆疊資訊:

點選dump,生成執行緒堆疊資訊,還可以右擊threaddump生產執行緒dump分析。

可以看到“Found one Java-level deadlock”,包含了導致死鎖的程式碼。

"thread2":
    at com.jvm.visualvm.Demo4$SynAddRunalbe.run(Demo4.java:50)
    - waiting to lock <0x00000007173d40f0> (a com.jvm.visualvm.Demo4$Obj1)
    - locked <0x00000007173d6310> (a com.jvm.visualvm.Demo4$Obj2)
    at java.lang.Thread.run(Thread.java:745)
"thread1":
    at com.jvm.visualvm.Demo4$SynAddRunalbe.run(Demo4.java:43)
    - waiting to lock <0x00000007173d6310> (a com.jvm.visualvm.Demo4$Obj2)
    - locked <0x00000007173d40f0> (a com.jvm.visualvm.Demo4$Obj1)
    at java.lang.Thread.run(Thread.java:745)

上面這段資訊可以看出,thread1持有Obj1物件的鎖,等待獲取Obj2的鎖,thread2持有Obj2的鎖,等待獲取Obj1的鎖,導致了死鎖。

總結

本文介紹了jdk提供的一款非常強大的分析問題的一個工具VisualVM,通過他,我們可以做一下事情:

  1. 檢視應用jvm配置資訊
  2. 檢視cpu、記憶體、類、執行緒監控資訊
  3. 檢視堆的變化
  4. 檢視堆快照
  5. 匯出堆快照檔案
  6. 檢視class物件載入資訊
  7. CPU分析:發現cpu使用率最高的方法
  8. 分析死鎖問題,找到死鎖的程式碼