最近因為工作的關係,需要使用到 JMX ,上網 Google 了一下,發現資料並不多,特別是中文的資料很少,要不就是有謬誤,就想說自己寫一篇,這樣之後的人也會比較輕鬆。也當作是這個部落格的第一篇,假如有錯誤的的話,還望大家不吝指正。
因為這篇有實作,交代一下環境
OS: Window 7 Professional SP1 64bit
JAVA: 1.6.0_34
IDE: IntellijIDEA 13
什麼是JMX
JMX 是 Java Management Extensions 的簡寫,它的主要目的為了管理在 JVM 上運行的應用程式
試想今天你需要管理在不同機器執行的5個 Services,監控它的執行情況,還要監控機器上的記憶體,CPU,執行緒數量...等,你該怎麼做?
你可能會一台台登入下指令去看 Linux 的 memory,cpu 情況。那應用程式執行情況呢?寫程式將資訊透過網路傳出來?那假如程式是別人寫的呢?那你就需要知道它的溝通方式, 不同 application 就像說著不同語言的人,你需要為每一個 application 寫一個translator,轉成共同的格式,然後透過網路傳給你的 Client 端
不然你的 Client 就需要自己做這個翻譯的工作,它需要懂 N 種不同的溝通方式,哪天需要多監控一個新的application 的時候,Client 就必須修改...
想到就累不是嗎 ? JMX 就是為了解決這個難題而被發明的
JMX 基於一個管理系統架構來設計
你可能會問為什麼要分為兩層的架構,SubAgent 主要是與 Resource 溝通的角色,Agent 則是統整訊息,
並負責網路傳遞的角色
這樣分工明確,也不用在 SubAgent 內負責網路的連線與傳遞,且 SubAgent 一般都與 Resource 處於同
一台機器,Agent 則可以位於另一台機器
對應到 JMX 是
javax.management.MBeanServer 實現了 Agent 的功能
javax.management.MBean 實現了 SubAgent 的功能
在 package 上的分類為
java.lang.management 包含了基本的 VM 監控功能
javax.management 讓用戶可以對 application 實現監控功能
簡單 MBean範例
簡單的說就是針對特定的 Application 客製化 MBean,然後使用 MBeanSever 去呼叫它
MBean 必須要用特定的規則去撰寫,並定義接口(interface),然後 MBeanServer 才可以呼叫這些接口,從 Applicaiton 取得資料或呼叫 Application 的方法
假設有一個應用程式想要管理快取,OK ! 那首先我們先定義一個擁有快取功能的 Application,就叫ApplicationCache
public class ApplicationCache {
private int maxCacheSize = 100;
private java.util.List cache = new java.util.ArrayList();
public synchronized void clearCache() {
cache.clear();
}
public synchronized int getCachedObjects() {
return cache.size();
}
public synchronized int getMaxCacheSize() {
return maxCacheSize;
}
public synchronized void setMaxCacheSize(int value) {
if(value < 1) {
throw new IllegalArgumentException("Value must be >= 1");
}
maxCacheSize = value;
}
public synchronized void cacheObject(Object o) {
while( cache.size() >= maxCacheSize ) {
cache.remove(0);
}
cache.add(o);
}
}
很簡單吧 ! 它可以取得與設定最大快取大小,取得目前快取物件的數量,清除快取,將物件設定到快取內
接下來我們要將這個 ApplicationCache 想要透漏給 MBeanServer 使用的 methods 定義成一個 interface
public interface ApplicationCacheMBean {
int getMaxCacheSize();
void setMaxCacheSize(int value);
int getCachedObjects();
void clearCache();
}
這個類別要遵守幾個規則
- 1.要是 interface
- 2.類別名稱要用 MBean 做結尾
- 3.要符合 java bean 的規則,比如說有一個 getter 叫做 getCachedObjects( ),那假如也有 setter 的話,就要叫做 setCachedObjects()
接著修改 ApplicationCache 的 signature, 讓它實作此介面
public class ApplicationCache implements ApplicationCacheMBean{
/* 略 */
}
之所以這樣寫的原因是,這是假定此 Application 已經存在了,需要將它的某些功能"公開"出去給 MBeanServer 來呼叫使用
假如是重頭開發 Application 的話,那大可以先寫好 interface,再寫實作的 class
OK !! MBean 已經寫好了,接下來就是去 MBean Server 裡面去註冊它,這樣當我們連到 MBeanServer 的時候才看得到這些定義的介面
這包含幾個步驟
- 1.取得 MBeanServer
- 2.定義一個 unique ObjectName
- 3.使用這個unique ObjectName 將 Application 註冊到 MBeanServer
想像 Application 是一個 Resource,你今天要透過 MBeanServer去呼叫它的方法,當然先給它一個名字
回想前面講過的,Agent 對應到數個SubAgent,你當然要先給每一個 SubAgent 取一個唯一的名字,這樣之後你才可以跟Agent說『ㄟ! 叫那個 xyzSubAgent出來』,不然Agent 哪知道你要取得哪一個subAgent的資訊
public class JMXServer {
private static void imitateActivity(ApplicationCache cache) {
//
// add new Object to cache every sec
//
while(true) {
try {
cache.cacheObject(new Object());
Thread.sleep(1000);
}
catch(InterruptedException e) { /*do nothing*/ }
}
}
public static void runMBeanServer() throws Exception {
ApplicationCache cache = new ApplicationCache();
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); // step 1
ObjectName name = new ObjectName("com.example.jmx:type=ApplicationCacheMBean"); // step 2
mbs.registerMBean(cache, name); // step 3
imitateActivity(cache);
}
public static void main(String[] args) throws Exception {
JMXServer.runMBeanServer();
}
}
要注意的是,使用的是 ApplicationCache,而不是 ApplicationCacheMBean,
因為 ApplicationCacheMBean 只是一個 interface 而已,它無法實體化,將來你無法透過 MBeanServer取得實體,呼叫它的方法
然後為了避免大家取名字的時候取到相同的名字,在 ObjectName取名字一般都是使用 {packageName}:type:{MBeanClassName} 這個
規則來取名字,來避免衝突
啟動的時候要在 java 指令加入參數給 JVM,這樣JVM才會知道『喔 ! 這個Applicaton 要啟動 JMX』
參數一般使用
-Dcom.sun.management.jmxremote
假如要從遠端連到這個 MBeanServer 的話,要給另外的參數,指定 port,要不要ssl,要不要認證...等
-Dcom.sun.management.jmxremote.port=8888 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote
啟動之後,開啟 jconsole,jconsole是一個執行檔案,位於 java 安裝目錄的 bin 資料夾下
剛剛啟動的 Application 會出現在 Local Process 內, 點選後按 [Connect]
假如要連去遠端的 MBean Server 的話( MBeanServer啟動時要加入port, ssl 等參數),要點選Remote Process, 並輸入
service:jmx:rmi:///jndi/rmi://{ip}:{port}/jmxrmi
比如 service:jmx:rmi:///jndi/rmi://127.0.0.1:8888/jmxrmi
為了簡單起見,以 Local Process 作例子
你可以取得 MBean 的 attributes 與 operations
遵照 java bean 設定的 getter 與 setter 會變成 attributes,比如說有getMaxCacheSize() 與 setMaxCacheSize(int value)
那就會有一個 MaxCacheSize 的 attribute,可以讀取也可以修改其數值
CacheObjects 因為只有 getter,所以你只能讀取它的數值,無法修改
另外我們有定義了 clearCache() 這個方法,就會出現在 operations 內,可以點選按鈕來呼叫
以上是使用 jconsole 當作 client 連到 MBeanServer 的範例
那要怎麼自己撰寫 Client 程式連到 MBeanServer 呢?這也很簡單
public class JMXClient {
private static void testMBServer(){
try{
JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:8888/jmxrmi");
JMXConnector jmxc = JMXConnectorFactory.connect(url,null);
MBeanServerConnection mbsc = jmxc.getMBeanServerConnection();
final ObjectName objectName = new ObjectName("com.example.jmx:type=ApplicationCacheMBean");
final ApplicationCacheMBean appProxy =
JMX.newMBeanProxy( mbsc,objectName,ApplicationCacheMBean.class );
// 列出所有Domain
System.out.println("Domains:---------------");
String domains[] = mbsc.getDomains();
for (int i = 0; i < domains.length; i++) {
System.out.println("\tDomain[" + i + "] = " + domains[i]);
}
// MBean的總數
System.out.println("MBean count = " + mbsc.getMBeanCount());
//
// 透過 Attribute 的操作來存取 MBean
//
System.out.println("====== set by attribute operations, set MaxCacheSize to 5 ");
mbsc.setAttribute(objectName, new Attribute("MaxCacheSize", 5));
System.out.println("MaxCacheSize = " + mbsc.getAttribute(objectName, "MaxCacheSize"));
//
// 也可以直接透過 Proxy 物件來操作
//
System.out.println("====== set by Proxy, set MaxCacheSize to 100 ");
appProxy.setMaxCacheSize(100);
System.out.println("MaxCacheSize = " + appProxy.getMaxCacheSize() );
jmxc.close();
System.out.println("going to exit.....");
}
catch(Exception e){
e.printStackTrace();
}
}
public static void main(String[] argc)
{
JMXClient.testMBeanServer();
}
}
要特別注意的是這邊使用遠端的連線的方式連線到 MBeanServer
所以前面的 JMXServer 啟動時要使用參數
-Dcom.sun.management.jmxremote.port=8888 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote
JMXClient 執行結果如下

再談 ObjectName
前面有提到,在 ObjectName取名字一般都是使用 {packageName}:type:{MBeanClassName} 這個規則來取名字,來避免衝突
其實 ObjectName 真的規則是 [domain]:[key]=[value],[key2]=[value2],[key3]=[value3]...
domain不允許冒號在裡面,domain 冒號右邊的 key 一定是 "type",剩下的隨你命名,只要符合 key=value 的格式就好
比如說你寫 "my.mbeans:type=Cache,realm=UI,vv=ImageHandles,ooo=box"
那開啟 jconsole 的時候會看到如下

