Java JMX 使用

JMX(Java Management Extensions,Java管理擴展)是一個爲應用程序植入管理功能的框架。JMX是一套標準的代理和服務,實際上,用戶可以在任何Java應用程序中使用這些代理和服務實現管理,JMX讓程序有被管理的功能。常用於管理線程,內存,日誌Level,服務重啓,系統環境等。

1. 基本概念

管理資源(Manageable resource)
能幫助是你的活動和系統正常運轉的都算資源,只要能夠被Java的類描述即可。

管理組件(MBean,managed bean)
從資源的角度來看,它是一個對抽象的資源的一個描述。如果資源是數據庫,管理組件中可以提供數據庫的一些描述信息,比如數據庫服務器的運行地址、端口,類型以及最大連接數等等,但是這個類必須滿足JMX規範中的提出的要求,比如命名規則和實現標準。由於管理組件是資源的抽象,所以管理應用是直接面向MBean,也就說MBean會被暴露給管理應用來操作和訪問,通過MBean中提供的屬性和方法。

MBean就是一種規範的JavaBean,通過集成和實現一套標準的Bean接口,這種叫MBean。Mbean註冊到MBean Server中。之後將被MBeanServer中註冊過的Adapter(比如渲染爲HTML的HtmlAdapter)渲染爲直觀的頁面將MBean的屬性和方法展示給用戶;或者和 JMXConnectorServer 綁定,監聽Client端的請求。

類型 描述
Standard MBean 這種類型的MBean最簡單,它能管理的資源(包括屬性,方法,時間)必須定義在接口中,然後MBean必須實現這個接口。它的命名也必須遵循一定的規範,例如我們的MBean爲User,則接口必須爲UserMBean。
Dynamic MBean 必須實現javax.management.DynamicMBean接口,所有的屬性,方法都在運行時定義。
Model MBean 與標準和動態MBean相比,你可以不用寫MBean類,只需使用 javax.management.modelmbean.RequiredModelMBean 即可。RequiredModelMBean 實現了 ModelMBean 接口,而 ModelMBean 擴展了 DynamicMBean 接口,因此與 DynamicMBean 相似,Model MBean 的管理資源也是在運行時定義的。與 Dynamic MBean 不同的是,Dynamic MBean 管理的資源一般定義在 Dynamic MBean 中(運行時才決定管理那些資源),而 Model MBean 管理的資源並不在 MBean 中,而是在外部(通常是一個類),只有在運行時,才通過set方法將。其加入到 Model MBean 中。
Open MBean Open MBeans被設計爲可以被更大範圍的管理程序訪問。嚴格說,你開發standard,dynamic和model MBean可以使用複雜類型。然而,爲了讓管理程序能夠正確得到這些類型的狀態,這些類的字節碼必須讓這些管理程序訪問到。結果就是導致管理程序和被管理資源的耦合度很高。折衷的做法,就是要降低被管理的資源的可維護性。Open MBean的產生就是爲了去掉這個需求。採用的方法是,Open MBean使用通用的數據類型。管理程序和Open MBeans就可以共享和使用數據和方法,而不需要重新編譯,重新組配或者動態鏈接。所以說,Open MBeans提高了管理系統的靈活性和可擴展性。
MXBean MXBean是一種引用預定義數據類型的MBean。通過這種方式,您可以確保任何客戶機(包括遠程客戶機)都可以使用您的MBean,而不需要客戶機訪問代表MBean類型的特定的類。MXBean提供一種方便的方法來綁定數據,而不需要客戶端進行特殊的綁定操作。MXBeans的主要思想是:MXBean接口java.lang.management.MemoryMXBean 中引用的諸如 java.lang.managementMemoryUsage 類型,該類型映射一組稱之爲開放類型(定義爲javax.management.openbean包中)的類型。映射的具體規則詳見MXBean規範。然而,對於簡單的諸如int、String類型的映射規則保持不變,而對於複雜類型 MemoryUsage 則映射爲基本類型CompositeDataSupport。

管理組件服務器(MBean Server)
它是一個容器,用來盛裝和管理一組MBeans,它是整個JMX管理環境的核心。由於其中有很多的MBean,所以它必須提供一種機制來區分各個MBean,這就是註冊機制,每個添加到 MBean Server 的 MBean 在註冊的時候都要提供一個 ObjectName 來區分彼此,MBean Server 通過這個 ObjectName 來查找每個MBean,在JMX中是通過 ObjectName 類來爲每個 MBean 提供唯一的一個標識。同一個 MBean 類可能會實例化多個 MBean 對象實例,但是不同的對象實例必須有不同的唯一的object name。基本形式:DomainName:property=value[,property2=value2]*,它包括兩部分:

  1. 域名:DomainName。這個域名通常是和想要註冊到的 MBean Server 的名稱標識相同,以便根據功能模塊區分不同 MBean Server 中的 MBean ;
  2. 鍵值對列表:property=value[,property2=value2]*。被用來唯一的標識 MBean,也提供了關於該 MBean 的信息。其中的屬性不一定是真實的 MBean 的屬性,僅僅要求當和其他的 MBean 比較的時候能夠唯一標識,每個 ObjectName 中都要至少有一個屬性。

JMX代理(JMX Agent)
它提供一系列的服務來管理一系列的 MBeans,它是 MBean Server 的容器。JMX代理提供一些服務,包括創建 MBean 之間的關係,動態加載類,簡單監視服務,以及計時器;代理可以有一系列的協議適配器(Protocol adapters )和連接器(connectors ),協議適配器和連接器也是Java類,通常情況下也是MBeans,這些適配器和連接器是提供轉接功能而存在的,以便可以在遠程使用不同的協議,通過客戶端與這個代理連接,它內部可以映射到一個外部的協議或者暴露代理給遠程連接,這就意味着JMX代理可以被一系列不同的管理協議和工具使用,在本質上是插件式架構的一種體現,體現了可插拔的思想。

協議適配器和連接器(Protocol adapters and connectors)
協議適配器和連接器是JMX Agent中的對象,將代理暴露給不同的管理應用和協議。這個和不同的數據庫的驅動程序類似,每個數據庫都有自己的一套協議來聯繫,爲了保持進行連接,就需要在JDBC應用和數據庫服務器之間通過不同的驅動程序關聯。一個JMX Agent可以有任意數量的適配器和連接器,它們也是MBeans。

通知(Notification)
通知是由 MBeans 和 MBean Server 提出的,其中封裝了具體的事件和相應的數據,其他的 MBeans 或者 Java 對象可以註冊作爲監聽器來接收這些通知,這就是觀察者設計模式在JMX中的應用。

2. 架構

在這裏插入圖片描述
分佈層(Distributed layer)
關心Agent如何被遠端用戶訪問的細節,包含可以使管理應用與JMX Agents交互的組件。一旦通過交互組件與JMX Agents建立連接,用戶可以用管理工具來和註冊在Agents中的MBeans進行交互。

代理層(Agent layer )
來管理相應的資源,並且爲遠端用戶提供訪問的接口。Agent層構建在設備層之上,並且使用並管理設備層內部描述的組件。Agent層主要定義了各種服務以及通信模型。該層的核心是 MBeanServer,所有的MBean都要向它註冊,才能被管理。註冊在MBeanServer上的MBean並不直接和遠程應用程序進行通信,他們通過協議適配器(Protocol adapters) 和 連接器(Connectors) 進行通信。通常Agent由一個MBeanServer和多個系統服務組成。JMX Agent並不關心它所管理的資源是什麼。

設備層(Instrumentation layer )
包含代表可管理資源的MBeans。該層是最接近管理資源的,它由註冊在Agents中的MBeans組成,這個MBean允許通過JMX Agent來管理。每個MBean都暴露出來針對底層資源的操作和訪問。

3. Standard MBean 使用

這裏使用 standard MBean。建立一個MBean接口,這個接口的名稱要以 MBean 結束。

public interface UserMBean {
    String getName();
    void setName(String name);
    Integer getAge();
    void setAge(Integer age);
    void sayHello();
    void sayHello(String name);
    void sayHello(String name, Integer age);
    void getToString();
}

實現 UserMBean,名稱必須與實現的接口的前綴保持一致,即MBean前面的名稱

public class User implements UserMBean {
    private String name;
    private Integer age;

    @Override
    public String getName() {
        return name;
    }

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

    @Override
    public Integer getAge() {
        return age;
    }

    @Override
    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public void sayHello() {
        System.out.println("sayHello() hello " + name + " " + age);
    }

    @Override
    public void sayHello(String name) {
        System.out.println("sayHello(String name) hello " + name);
    }

    @Override
    public void sayHello(String name, Integer age) {
        System.out.println("sayHello(String name, Integer age) hello " + name + " " + age);
    }

    @Override
    public void getToString() {
        System.out.println("toString " + this.toString());
    }
}

創建 MBeanServer。

MBean 註冊到 MBeanServe r中。MBeanServer 通過 ObjectName 來對 MBean 進行區分。同一個 MBean 類可能有多個實例對象,但是每個實例對象都有一個唯一的 ObjectName。

public class UserJmxAgent {
    public static void main(String args[]) throws Exception{
        MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
        // 創建並註冊 MBean
        ObjectName objectName = new ObjectName("com.shpun.jmx:name=user");
        mBeanServer.registerMBean(new User(), objectName);
	
		while(true){
		}
    }
}

使用jmc測試
jmc
jmc測試

使用客戶端反射和代理測試

修改UserJmxAgent,創建 JMXConnectorServer 是爲了測試客戶端使用rmi通過url方式來連接JMXConnectorServer。

JMXConnectorServer 和 MBeanServer 綁定,它監聽 Client 端的請求,並處理和 Client 的連接(將Client的請求傳遞(同時負責請求的解碼)給 MBeanServer 並將請求響應返回給 Client。同時,ConnectorServer 也可以傳遞 Notification)。另外,ConnectorServer 可同時處理多個 Client 的請求。

JMXServiceURL 代表 ConnectorServer 的地址,該類實例是不可變對象。可通過 JMXServiceURL 連接到 ConnectorServer 中。
格式:service:jmx:<protocol>://[[[<host>]:<port>]/<path>]
service:jmx::JMX URL的標準前綴,所有的JMX URL都必須以該字符串開頭。
<protocol>:JMX Connector Server 的傳輸協議,可支持rmi,iiop,jmxmp,soap。

在這裏 JMXServiceURL 爲 service:jmx:rmi://XXX.XXX.XXX.XXX:XXXX/jndi/rmi://localhost:10002/jmxUser
rmi:: 使用RMI來進行傳輸的。RMI的核心是一個Registry註冊中心,需要暴露給客戶端的自定義RMI服務接口會提前註冊到這個Registry中,並聲明好訪問的鏈接,之後用戶訪問自定義RMI服務接口時就會鏈接到Registry,並訪問到和鏈接對應的MBeanServer。
XXX.XXX.XXX.XXX:XXXX:JMX Connector Server 的IP和端口,也就是真正提供服務的IP和端口。可以省略,那麼會在運行期間隨意綁定一個端口提供服務。在使用過程中未發現寫與不寫的區別。
jndi/rmi://localhost:10002/jmxUser:JMX Connector Server 的路徑,具體含義取決於前面的傳輸協議。比如該URL中這串字符串就代表着該JMX Connector Server 的 存根(stub)是使用 jndi 的 api 綁定在 rmi://localhost:10002/jmxUser 這個地址上。

// while(true){
// }

// 創建本地主機上的遠程對象註冊表Registry的實例,默認端口1099。
// 客戶端就可以使用rmi通過url方式來連接JMXConnectorServer
LocateRegistry.createRegistry(10002);

JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:10002/jmxUser");
JMXConnectorServer jmxConnectorServer = JMXConnectorServerFactory.newJMXConnectorServer(jmxServiceURL, null, mBeanServer);
jmxConnectorServer.start();

客戶端

JMX提供了一種對外發布MBean的方式,Client可以通過一定的途徑來獲取這些MBean,並使用這些MBean。獲取這些MBean的方式也是在Client端使用Connector。Client使用MBean的方式可以通過Java RMI,或是其他的形式(這要看Agent層的Connector的協議)。

public class UserJmxClient {
    public static void main(String[] args) throws Exception     {
        JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:10002/jmxUser");
        JMXConnector jmxConnector = JMXConnectorFactory.connect(jmxServiceURL,null);
        MBeanServerConnection mBeanServerConnection = jmxConnector.getMBeanServerConnection();

        // ObjectName的名稱與前面註冊時候的保持一致
        ObjectName mbeanName = new ObjectName("com.shpun.jmx:name=user");

        // 設置指定Mbean的特定屬性值,這裏的setAttribute、getAttribute操作只能針對bean的屬性,相當於調用get,set方法,並且屬性的首字母大寫
        mBeanServerConnection.setAttribute(mbeanName, new Attribute("Name","張一"));
        mBeanServerConnection.setAttribute(mbeanName, new Attribute("Age",18));
        String name = (String) mBeanServerConnection.getAttribute(mbeanName, "Name");
        Integer age = (Integer) mBeanServerConnection.getAttribute(mbeanName, "Age");
        System.out.println("UserClient: name=" + name + ";age=" + age);

        // 通過JAVA的反射注入的方式進行方法的調用
        // invoke調用bean的方法,只針對非設置屬性的方法,例如invoke不能對getName方法進行調用
        mBeanServerConnection.invoke(mbeanName, "sayHello", null, null);
        mBeanServerConnection.invoke(mbeanName, "sayHello", new String[]{"張二"}, new String[]{"java.lang.String"});
        mBeanServerConnection.invoke(mbeanName, "sayHello", new Object[]{"張三", 30}, new String[]{"java.lang.String","java.lang.Integer"});
        mBeanServerConnection.invoke(mbeanName, "getToString", null, null);

        // 通過代理直接調用方法
        UserMBean proxy = MBeanServerInvocationHandler.newProxyInstance(mBeanServerConnection, mbeanName, UserMBean.class, false);
        proxy.getToString();
        proxy.setName("張四");
        proxy.setAge(40);
        System.out.println("UserClient: name=" + proxy.getName() + ";age=" + proxy.getAge());

        proxy.sayHello();
        proxy.sayHello("張五");
        proxy.sayHello("張六", 60);
    }
}

反射或代理調用方法,調用的都是同一個對象
userjmcagent
userjmxclient

4. Notification

MBean之間的通信是必不可少的,Notification就起到了在MBean之間溝通橋樑的作用。JMX 的通知由四部分組成:

  1. Notification 這個相當於一個信息包,封裝了需要傳遞的信息2
  2. Notification Broadcaster 這個相當於一個廣播器,把消息廣播出。
  3. Notification Listener 這是一個監聽器,用於監聽廣播出來的通知信息。
  4. Notification Filiter 這個一個過濾器,過濾掉不需要的通知。這個一般很少使用。

下面例子User1事先添加User2的監聽器,當調用noticeUser2後,發送廣播消息,User2監聽器監聽到通知信息,再執行User2的監聽器邏輯;User2邏輯類似。

User1MBean

public interface User1MBean {
    void listenUser2(String message);
    void noticeUser2();
}

User1

public class User1 extends NotificationBroadcasterSupport implements User1MBean {

    private int seq = 0;

    @Override
    public void listenUser2(String message) {
        System.out.println("User1 listenUser1 :" + message);
    }

    @Override
    public void noticeUser2() {
        // 創建一個信息包:通知名稱,誰發起的通知,序列號,發起通知時間,發送的消息
        Notification notify = new Notification("User1.noticeUser2",this, ++seq, System.currentTimeMillis(),"你好,我是User1");
        sendNotification(notify);
    }
}

User1Listener

public class User1Listener implements NotificationListener {
    @Override
    public void handleNotification(Notification notification, Object handback) {
        if (handback instanceof User1) {
            User1 user1 = (User1)handback;
            user1.listenUser2(notification.getMessage());
        }
    }
}

User2MBean

public interface User2MBean {
    void listenUser1(String message);
    void noticeUser1();
}

User2

public class User2 extends NotificationBroadcasterSupport implements User2MBean {

    private int seq = 0;

    @Override
    public void listenUser1(String message) {
        System.out.println("User2 listenUser2 :" + message);
    }

    @Override
    public void noticeUser1() {
        // 創建一個信息包:通知名稱;誰發起的通知;序列號;發起通知時間;發送的消息
        Notification notify = new Notification("User2.noticeUser1",this, ++seq, System.currentTimeMillis(),"你好,我是User2");
        sendNotification(notify);
    }
}

User2Listener

public class User2Listener implements NotificationListener {
    @Override
    public void handleNotification(Notification notification, Object handback) {
        if (handback instanceof User2) {
            User2 user2 = (User2)handback;
            user2.listenUser1(notification.getMessage());
        }
    }
}

User12JmxAgent

public class User12JmxAgent {
    public static void main(String[] args) throws Exception {
        MBeanServer server = ManagementFactory.getPlatformMBeanServer();

        User1 user1 = new User1();
        server.registerMBean(user1, new ObjectName("com.shpun.jmx:name=user1"));

        User2 user2 = new User2();
        server.registerMBean(user2, new ObjectName("com.shpun.jmx:name=user2"));

        user1.addNotificationListener(new User2Listener(), null, user2);
        user2.addNotificationListener(new User1Listener(), null, user1);

        while (true) {

        }
    }
}

使用jmc測試
jmc
jmc測試

參考:
JMX技術主頁
Java JMX 一:初步認識
Java JMX 二:MBean and MBean Server
Java JMX 三:Connector
Java JMX 四:MBeanServerInvocationHandler
JMX詳解詳細介紹及使用
理解JMX之介紹和簡單使用
JMX,RMI與RPC
JMX超詳細解讀
什麼是MBean?什麼是JMX架構?
JMX MBeans之三Open MBean
Java管理擴展指南之MBean簡介

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章