之前本來打算結合自己寫的小程序來介紹JConsole和VirtualVM的使用的,但是發現很難通過一個程序把所有的場景都體現出來,所以還是決定用書中的典型小例子來講更加清晰。
一、JConsole的基本功能
JConsole是一個機遇JMX(Java Management Extensions,即Java管理擴展)的JVM監控與管理工具,監控主要體現在:堆棧內存、線程、CPU、類、VM信息這幾個方面,而管理主要是對JMX MBean(managed beans,被管理的beans,是一系列資源,包含對象、接口、設備等)的管理,不僅能查看bean的屬性和方法信息,還能夠在運行時修改屬性或調用方法。
首先我們看下JConsole的啟動,JConsole在jdk/bin/下,其啟動需要圖形界面的支持(廢話,都說了圖形界面),可能不少人一聽到這個就覺得有點low:平時服務器跑的linux都沒圖形界面,那豈不是用不了。其實不用擔心,JConsole支持遠程進程監測。下邊是連接界面,其實相當於jps命令:
再來看下連接后的界面,我們打開DeadLock(一個測試死鎖的示例):
可以看到上邊的選項卡正好對應各個功能。
1、概述
這個不介紹了,就是上圖,相信大家都看的懂。
2、內存
在內存頁我們可以看到程序運行期間JVM各個部分的內存狀況,右下角是對應各個分區的內存使用柱狀圖,點擊對應柱可查看詳情,看圖: 
3、線程
4、類
該頁面其實和線程頁有些相似,不過顯示的是JVM加載類的信息,見圖:
5、VM概述

6、MBean管理
下邊來看兩個小示例,分別分析內存和死鎖的。
二、兩個示例
1、內存分析
這里我們來通過一個小程序進行一下內存分析,代碼如下:
package com.gj.jconsole; import java.util.ArrayList; import java.util.List; public class DataInsert { //一個OOMObject實例大概64k+ static class OOMObject{ public byte[] placeholder= new byte[64*1024]; } public static void fillHeap() throws InterruptedException { List<OOMObject> list =new ArrayList<OOMObject>(); for(int i=0;i<1000;i++){ Thread.sleep(100); list.add(new OOMObject()); } System.gc(); } public static void main(String[] args) throws Exception{ fillHeap(); } }
可以看到程序向list中插入了1000個OOMObject對象,每個OOMObject大概64k,那么堆內存的峰值應該在64k*1000=64m左右,我們運行程序,並使用JConsole打開DataInsert進程,當程序結束時堆內存如下:

可以看到對內存峰值在60-70m之間(下方已用內存為63631kb,大約63m),與我們預計的相符。下邊我們來看下下邊這段代碼:
package com.gj.jconsole; import java.util.ArrayList; import java.util.List; import com.gj.jconsole.DataInsert.OOMObject; public class GCTest { // 一個OOMObject實例大概640k+ static class OOMObject { public byte[] placeholder = new byte[64 * 1024*10]; } public static void fillHeap() throws InterruptedException { for(int i=0;i<100;i++){ OOMObject oOmObject =new OOMObject(); Thread.sleep(1000); oOmObject=null; } } public static void main(String[] args) throws InterruptedException { fillHeap(); } }
這段代碼每次新建一個OOMObject對象,在暫停1s后將其置null,我們來看下運行時內存圖:
會發現堆內存呈規律的折線,我們來分析下:當每個對象實例化后,然后置null,這時候對象並不會被回收(因為沒有gc),因此內存會一直上升,但是當堆內存不夠用時,會觸發gc,因此內存會降低。查看VM概況可知,一共進行了18次gc,回收算法為“復制”。
2、線程死鎖
通過Jconsole不僅可以查看線程信息,而且能夠檢測死鎖,先來看下代碼:
package com.gj.jconsole; public class DeadLock { static class SynAddRunable implements Runnable{ int a,b; public SynAddRunable(int a,int b){ this.a= a; this.b= b; } @Override public void run() { synchronized (Integer.valueOf(a)) { synchronized (Integer.valueOf(b)){ System.out.println(a+b); } } } } /** * @param args */ public static void main(String[] args) { for(int i=0;i<100;i++){ new Thread(new SynAddRunable(1, 2)).start(); new Thread(new SynAddRunable(2, 1)).start(); } } }
可以看到代碼中啟動200個子線程,進行1+2或2+1的計算,但是這種情況為什么會出現死鎖呢?我們看到在run中出現雙重sychronized,這是典型的死鎖特征,但是這種情況要出現死鎖前提是多線程中sychronized同步的兩個對象分別都是同一個,才會造成互鎖,但是Integer.valueOf(a)和Integer.valueOf(b)每次返回的不都是一個新對象嗎?這里需要注意一個問題,為了節省內存,對於[-128,127]以內的轉換,Integer.valueOf會將這些值從緩存直接返回,所以相同的值返回的都是同一個對象(記得看java源碼的時候見過很多這種處理方法)。好了,來看下如何檢查死鎖。
等程序運行一段時間之后(這種情況下形成死鎖是隨機的,並不能確定那兩個會互鎖,但是對於200個線程概率還是非常大的),我們在線程頁點擊“檢測死鎖”,則會多出來一個死鎖頁,打開可以看到如下信息:
可以看到線程0和線程11互鎖,同時線程199由於等待線程11釋放鎖,也被阻塞。
以上就是JConsole的基本用法,還是比較簡單的。但這些只是小道,工具畢竟只是輔助,關鍵的還是要懂得原理,學會分析,真正的能在實踐中活用才好。下次將介紹更為強大的VirtualVM,敬請期待嘍^_^。
