目錄
- 一、簡介
- 二、內存分析
- 1、Heap堆
- 三、CPU分析
- 四、線程分析
一、簡介
VisualVM
是一款免費的,集成了多個JDK命令行工具的可視化工具,它能為您提供強大的分析能力,對Java應用程序做 性能分析和調優
。這些功能包括 生成和分析海量數據
、跟蹤內存泄漏
、監控垃圾回收器
、執行內存和CPU分析
。本文主要介紹如何使用VisualVM進行性能分析及調優。
自從JDK 6 Update 7 以后已經作為Oracle JDK的一部分,位于JDK根目錄的bin文件夾下,無需安裝,直接運行即可。
JDK路徑查看:
-
MAC查找JDK的路徑
-
Windows查找JDK的路徑
二、內存分析
VisualVM通過檢測JVM中加載的類和對象信息等幫助我們分析內存使用情況,我們可以通過VisualVM的監視標簽對應用程序進行內存分析。
1、Heap堆
首先寫一個內存堆占用較大的例子,代碼如下:
public class Main {public final static int OUTOFMEMORY = 200000000;public static String oom;public static StringBuffer tempOOM = new StringBuffer();public static void main(String[] args){threadHeap(OUTOFMEMORY);}public static void threadHeap(final int len){Thread t = new Thread(new Runnable() {@Overridepublic void run() {int i = 0;while(i < len){i++;try{tempOOM.append("abcdefghijabcdefghijabcdefghijabcdefghijabcdefghij");if(i%1000 == 1){Thread.sleep(10);}//Thread.sleep(1);} catch (Exception e){e.printStackTrace();break;} catch (Error e){e.printStackTrace();break;}}oom = tempOOM.toString();System.out.println("Thread Heap Length : " + oom.length());}});t.setName("ThreadHeap_1");t.start();}
}
運行該段代碼,然后查看VisualVM Monitor(監視), 堆內存會慢慢變大。
在程序運行結束之前, 點擊 堆Dump
按鈕, 等待一會兒,得到dump結果,可以看到一些摘要信息。
點擊類, 發現char[]所占用的內存是最大的。
雙擊 char[]
,得到如下實例數結果。
StringBuffer類型的全局變量 tempOOM 占用內存特別大, 注意局部變量是無法通過堆dump來得到分析結果的
。
另外,對于 堆 dump
來說,在遠程監控jvm的時候,VisualVM是沒有這個功能的,只有本地監控的時候才有
。
三、CPU分析
CPU 性能分析的主要目的是統計函數的調用情況及執行時間,或者更簡單的情況就是統計應用程序的 CPU 使用情況。
沒有程序運行時的 CPU 使用情況如下圖:
運行一段 占用CPU 的小程序,代碼如下:
public class Main {public static void main(String[] args){cpuFix();}public static void cpuFix(){try{// 80%的占有率int busyTime = 8;// 20%的占有率int idelTime = 2;// 開始時間long startTime = 0;while (true) {// 開始時間startTime = System.currentTimeMillis();/** 運行時間*/while (System.currentTimeMillis() - startTime < busyTime) {;}// 休息時間Thread.sleep(idelTime);}}catch(Exception ex){ex.printStackTrace();}}
}
結果如下:
過高的 CPU 使用率可能是由于我們的項目中存在低效的代碼;
在我們對程序施壓的時候,過低的 CPU 使用率也有可能是程序的問題。
點擊【抽樣器】, 點擊【CPU】按鈕, 啟動CPU性能分析會話,VisualVM 會檢測應用程序所有的被調用的方法,在【CPU 樣例】下可以看到我們的方法cpuFix() 的自用時間最長, 如下圖:
切換到【線程 CPU 時間】頁面下,我們的 main 函數這個進程占用CPU時間最長, 如下圖:
四、線程分析
Java 語言能夠很好的實現多線程應用程序。當我們對一個多線程應用程序進行調試或者開發后期做性能調優的時候,往往需要了解當前程序中所有線程的運行狀態,是否有死鎖、熱鎖等情況的發生,從而分析系統可能存在的問題。
在 VisualVM 的監視標簽內,我們可以查看當前應用程序中所有實時線程(Live threads)和守護線程(Daemon threads)的數量等實時信息。
運行一段小程序,代碼如下:
public class Main {public static void main(String[] args){startThread("Thread_1");startThread("Thread_2");}public static void startThread(String threadName){Thread thread = new Thread(new Runnable() {@Overridepublic void run() {while (true){}}});thread.setName(threadName);thread.start();}
}
VisualVM 的線程標簽提供了三種視圖,默認會以時間線的方式展現, 如下圖:
可以看到兩個我們run的程序里啟的線程:Thread_1 和 Thread_2。
再來一段死鎖的程序,看VisualVM 能否分析出來:
public class Main {public static void main(String[] args){diethread();}public static void diethread(){DieThread d1=new DieThread(true);DieThread d2=new DieThread(false);final Thread t1 = new Thread(d1);final Thread t2 = new Thread(d2);t1.setName("DieThread_1");t2.setName("DieThread_2");t1.start();t2.start();}
}package com.javaagent.thread;public class DieThread implements Runnable {public static Object obj1=new Object();public static Object obj2=new Object();private boolean flag;public DieThread(boolean bl){flag = bl;}@Overridepublic void run() {if(flag) {while(true) {synchronized(obj1) {try {Thread.sleep(1000);}catch (InterruptedException e){e.printStackTrace();}System.out.println("線程" + Thread.currentThread().getName() + "獲取obj1鎖對象,等待獲取obj2鎖對象...");synchronized(obj2) {System.out.println(Thread.currentThread().getName() + " ---- obj2.");}}}}else {while(true){synchronized(obj2) {System.out.println("線程" + Thread.currentThread().getName() + "獲取obj2鎖對象,等待獲取obj1鎖對象...");synchronized(obj1) {System.out.println(Thread.currentThread().getName() + " ---- obj1.");}}}}}
}
打開VisualVM檢測到的JVM進程,我們可以看到這個tab在閃,VisualVM已經檢測到死鎖。
另外可以點擊【線程 Dump】線程轉儲,進一步分析。