程式扎記: [ JVM 應用 ] JVM 性能監控與故障處理工具 - JDK 的視覺化工具

標籤

2012年7月2日 星期一

[ JVM 應用 ] JVM 性能監控與故障處理工具 - JDK 的視覺化工具

前言 : 
除了剛剛介紹 JDK 上豐富的命令列工具外, 還有兩個功能強大的視覺化工具 : JConsole 和 VisualVM. 其中 JConsole 在 JDK 1.5 就已經提供的 JVM 監控工具, 而 VisualVM 是在 JDK 1.6 Update7 中才首次發布, 現在已經成為主力推動的多合一故障處理工具, 並且已經從 JDK 中分離出來成為可以獨立開發的開放原始碼專案. 

JConsole - Java 監視與管理主控台 : 
JConsole (Java Monitoring and Management Console) 是一款基於 JMX 的視覺化監視與管理工具. 

- 啟動 JConsole 
透過 JDK/bin 目錄下的 "jconsole.exe" 啟動 JConsole 後, 將自動搜尋出本機執行的所有 JVM 進程 (透過 jps), 如下圖所示, 你可以選擇其中一個進程開始進行監控 : 
 

選擇一個 LVM 進程後, 雙點擊可以進入管理介面 : 
 

- 記憶體監控 
"Memory" 頁籤相當於視覺化的 jstat 命令, 用於監視收集器管理的 JVM 記憶體 (Java Heap 與 永久代) 的變化趨勢. 我們可以使用下面代碼並執行來體驗一下它的監視功能 : 
  1. package ch04;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.List;  
  5.   
  6. public class Ex4_6 {  
  7.     static class OOMObject{  
  8.         public byte[] placeholder = new byte[64*1024];  
  9.     }  
  10.   
  11.     public static void fillHeap(int num) throws InterruptedException  
  12.     {  
  13.         List list = new ArrayList();  
  14.         for(int i=0; i
  15.         {  
  16.             Thread.sleep(50);  
  17.             list.add(new OOMObject());  
  18.         }  
  19.         System.gc();  
  20.         Thread.sleep(2000);  
  21.     }  
  22.       
  23.     /** 
  24.      * Goal : 以 64KB/50ms 速度往 Java Heap 填充資料, 共填充 1000 次. 
  25.      * VM Args : -Xms100M -Xmx100M -XX:+UseSerialGC 
  26.      * @param args 
  27.      */  
  28.     public static void main(String[] args) throws Exception{  
  29.         fillHeap(1000);       
  30.         Thread.sleep(10000);  
  31.     }  
  32. }  
在下圖可以看到記憶體持續成長而形成一條向上的平滑曲線. 另外在進行 System.gc() 後你會發現記憶體沒有減少, 主要是因為 list 物件還參考著分配的記憶體 (因為 age 夠老, 已經都在老年代), 如果你將 System.gc() 移到 main 方法呼叫 fillHeap() 之後, 就可以看到記憶體被成功回收 : 
 

- Threads 監控 
"Threads" 標籤功能相當於視覺化的 "jstack" 命令, 遇到 Threads 停頓時候可以使用這個功能進行監控分析. 前面講解 jstack 命令有提到過 Thread 長時間停頓原因主要有 : 等待外部資源, 閉環, 鎖等待. 透過下面代碼分別展示一下這幾種狀況 : 
  1. package ch04;  
  2.   
  3. import java.io.BufferedReader;  
  4. import java.io.InputStreamReader;  
  5.   
  6. public class Ex4_7 {  
  7.     public static void createBusyThread()  
  8.     {  
  9.         Thread thread = new Thread(new Runnable(){  
  10.             @Override  
  11.             public void run()  
  12.             {  
  13.                 while(true); // 閉環.  
  14.             }  
  15.         }, "testBusyThread");  
  16.         thread.start();  
  17.     }  
  18.       
  19.     public static void createLockThread(final Object lock)  
  20.     {  
  21.         Thread thread = new Thread(new Runnable(){  
  22.             @Override  
  23.             public void run()  
  24.             {  
  25.                 synchronized(lock)  
  26.                 {  
  27.                     try  
  28.                     {  
  29.                         lock.wait();  // 鎖等待  
  30.                     }  
  31.                     catch(InterruptedException e){e.printStackTrace();}  
  32.                 }  
  33.             }  
  34.         }, "testLockThread");  
  35.         thread.start();  
  36.     }  
  37.   
  38.     /** 
  39.      * Goal : 展示  Thread 停頓原因 - 閉環, 鎖等待. 
  40.      * @param args 
  41.      */  
  42.     public static void main(String[] args) throws Exception{  
  43.         BufferedReader br = new BufferedReader(new InputStreamReader(System.in));  
  44.         br.readLine();  
  45.         createBusyThread();  
  46.         br.readLine();  
  47.         Object obj = new Object();  
  48.         createLockThread(obj);  
  49.     }  
  50. }  
執行上面程式後, 使用 JConsole 監控該程式並選擇 "Threads" 頁籤後, 選擇 "main" Thread. 如下圖所示, 堆疊追蹤顯示 BufferedReader 在 readByte 方法中等待 System.in 的鍵盤輸入, 這時候 Thread 為 Runnable 狀態, Runnable 狀態進程會被分配執行時間, 但 readByte 方法檢查到流沒有更新時會立刻歸還執行權杖, 這種等待只消耗很少的 CPU 資源 : 
 

接著在執行 Console 下任意輸入一鍵讓程式往下走, 接著監控 testBusyThread 線程, 如下圖所示, testBusyThread 一直在執行空循環, 從堆疊追蹤中可以看到該線程一直停留在程式碼第 13 行的位置 (while(true)). 這時候線程為 Runnable 狀態, 而沒有歸還執行權杖的動作, 會在空循環上用盡全部的執行時間直到線程切換, 這種等待會耗費較多的 CPU 資源 : 
 

接著在執行 Console 下任意輸入一鍵讓程式往下走, 接著監控 testLockThread 線程. 下圖顯示線程在等待 lock 物件的 notify 或 notifyAll 方法的出現, 線程此時處在 WAITING 狀態, 在被喚醒前不會被分配執行時間 : 
 

testLockThread 線程處於正常的活鎖狀態, 只要 lock 對象的 notify() 或 notifyAll() 方法被呼叫, 這個 線程 便能啟動並繼續執行. 在 JDK 命令列工具 有一個死鎖範例, 執行後再用 JConsole 監控 : 
 

可以發現線程 Thread-1, Thread-2 處於 Block 狀態, 接著點擊 "Detect Deadlock" 可以檢查已經死鎖的線程 : 
 

VisualVM - 多合一故障處理工具 : 
VisualVM (All-in-One Java Troubleshooting Tool) 是目前為止隨 JDK 發布的功能最強大的執行監控與故障處理工具. "All-in-One" 意味它除了執行監控, 故障處理外, 還提供了很多其他 面的功能. 如性能分析(Profiling). 而且 VisualVM 還有一個很大的優點 : 不需要被監視的程式基於特殊 Agent 執行, 因此它對應用程式的實際性能影響很小, 使得它可以直接執行在應用環境中. 

- 下載執行 
VisualVM 在較新的 JDK 已經不隨之分布, 如果要使用需要自行下載, 下載後解壓縮可以在 bin 目錄下找到 "visualvm.exe", 執行後經過初始設定可以進入到下面的 GUI 介面 (我使用版本為 1.3.4): 
 

- 分析程式性能 
VisualVM 在 JDK 1.6 update7 中才首次出現, 但不意味它只能使用在 JDK 1.6 上的程式, 它具備很強向下相容能力, 這對無數已經處於維護狀態的老專案很有意義. 當然並非所有功能都能完美向下相容, 主要特性的相容性如下表所示 : 
 

在開啟 VisualVM 後, 可以在左邊的 Applications 面板找到要監測的 JVM 進程 : (底下對 LVMID=7640 進行監測
 

接著可以在右方面板點擊 "Sampler" 頁籤, 檢視 CPU 與 記憶體的使用狀況 : 
 
(CPU Usage

 
(Memory Usage)

沒有留言:

張貼留言

網誌存檔

關於我自己

我的相片
Where there is a will, there is a way!