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]*
,它包括兩部分:
- 域名:
DomainName
。這個域名通常是和想要註冊到的 MBean Server 的名稱標識相同,以便根據功能模塊區分不同 MBean Server 中的 MBean ; - 鍵值對列表:
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測試
使用客戶端反射和代理測試
修改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);
}
}
反射或代理調用方法,調用的都是同一個對象
4. Notification
MBean之間的通信是必不可少的,Notification就起到了在MBean之間溝通橋樑的作用。JMX 的通知由四部分組成:
- Notification 這個相當於一個信息包,封裝了需要傳遞的信息2
- Notification Broadcaster 這個相當於一個廣播器,把消息廣播出。
- Notification Listener 這是一個監聽器,用於監聽廣播出來的通知信息。
- 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測試
參考:
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簡介