What and Why JMX
JMX的全稱為Java Management Extensions. 顧名思義,是管理Java的一種擴展。這種機制可以方便的管理正在運行中的Java程序。常用于管理線程,內存,日志Level,服務重啟,系統環境等。
試想,一個正在運行中的程序,我們如果想改變程序中的一些屬性,可以通過什么方法呢?可能有這么幾個方法:
-
對于服務器式的程序,可以制作管理頁面,通過HTTP post與servlet來更改服務器端程序的屬性。
-
對于服務器式的程序,還可以通過SOAP方式。但這需要程序開啟了SOAP端的服務。
-
可以使用RMI遠程調用。但這需要設計開啟RMI服務。
-
如果是SWT或Swing的程序,則可以通過設計UI管理界面,使用戶可以和程序內部交互。
-
還有一種方式,是將可改變的屬性放入配置文件XML,properties或數據庫,程序輪詢配置文件,以求獲取最新的配置。
上面幾個方法都是常見,但卻無法通用的。所謂通用,是指解決方案符合一個標準,使得任何符合此標準的工具都能解析針對此標準的方案實現。這樣A公司設計的方案,B公司可以根據標準來解析。JMX就是Java管理標準。
JMX的構成
JMX由三部分組成:
-
程序端的Instrumentation, 我把它翻譯成可操作的儀器。這部分就是指的MBean. MBean類似于JavaBean。最常用的MBean則是Standard MBean和MXBean.
-
程序端的JMX agent. 這部分指的是MBean Server. MBean Server則是啟動與JVM內的基于各種協議的適配器。用于接收客戶端的調遣,然后調用相應的MBeans.
-
客戶端的Remote Management. 這部分則是面向用戶的程序。此程序則是MBeans在用戶前投影,用戶操作這些投影,可以反映到程序端的MBean中去。這內部的原理則是client通過某種協議調用agent操控MBeans.
JMX agent與Remote Management之間是通過協議鏈接的,這協議可能包含:
-
HTTP
-
SNMP
-
RMI
-
IIOP
JMX agent中有針對上面協議的各種適配器。可以解析通過相應協議傳輸過來的數據。Remote Management client則可以用現成的工具,如JConsole, 也可以自己書寫java code。
接下來,我們看是一步一步,通過代碼示例來熟悉JMX各種特性。
受監管的程序
JMX是用于管理java程序的,為了試驗,我們首先需要寫一個小程序Echo。然后加入JMX對此程序進行監管。這個程序就是每隔10秒鐘,輸出一個預先定義好的Message。
首先定義Message類。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
public class Message { private String title, body, by; public Message() { title= "none" ; body= "none" ; by= "none" ; } public String getTitle() { return title; } public void setTitle(String title) { this .title = title; } public String getBody() { return body; } public void setBody(String body) { this .body = body; } public String getBy() { return by; } public void setBy(String by) { this .by = by; } public void echo() { System.out.println( "<" +title+ ">" ); System.out.println(body); System.out.println( "by " + by); } } |
定義Echo類
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public class Echo { public static Message msg = new Message(); public static boolean running= true ; public static boolean pause= false ; public static void main(String[] args) { // 開啟JMX Agent。如果不需要JMX,只是單獨運行程序,請屏蔽掉下面這行代碼。 new MessageEngineAgent().start(); while (running) { try { Thread.sleep( 10000 ); } catch (InterruptedException e) { e.printStackTrace(); } if (!pause) msg.echo(); } } } |
執行Echo,得到每過10秒鐘,則會輸出一個消息:
<none>
none
by none
MBean
接下來,開始設計管理程序的MBean. 在設計MBean之前,必須要先了解MBean都包括哪幾種。MBean包含:
-
Standard MBean
-
Dynamic MBean
-
Open MBean
-
Model MBean
-
MXBean
最常用最簡單的兩個就是Standard MBean與MXBean. 首先搞清楚,MBean和MXBean的區別是什么。
Standard MBean與MXBean的區別
這里有一些細節,列出了兩只的區別http://docs.oracle.com/javase/7/docs/api/javax/management/MXBean.html 。它 們最根本的區別是,MXBean在Agent與Client之間會將自定義的Java類型轉化為Java Open Type. 這樣的好處是Client無需獲取MXBean的接口程序,便可訪問和操作MXBean的投影。如果使用MBean, client則必須先將MBean的接口程序放到classpath中,否則無法解析MBean中自定義類型。
基于上述原因,我將使用MXBean做為例子。實際上,JVM自帶的幾乎全是MXBean。
實現
定義MXBean的接口,注意命名規則,必須以MXBean結尾。
1
2
3
4
5
6
7
8
9
10
11
|
public interface MessageEngineMXBean { //結束程序。 public void stop(); //查看程序是否暫停。 public boolean isPaused(); //暫停程序或者繼續程序。 public void pause( boolean pause); public Message getMessage(); //修改message public void changeMessage(Message m); } |
實現部分。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
public class MessageEngine implements MessageEngineMXBean { private final Message message = Echo.msg; @Override public void stop() { Echo.running = false ; } @Override public boolean isPaused() { return Echo.pause; } @Override public void pause( boolean pause) { Echo.pause = pause; } @Override public Message getMessage() { return this .message; } @Override public void changeMessage(Message m) { this .message.setBody(m.getBody()); this .message.setTitle(m.getTitle()); this .message.setBy(m.getBy()); } } |
Notification
在JMX中,還有一個重要的概念是Notification。構成Notification的幾個接口是:
-
NotificationEmitter, 只要實現此接口,就可以發出Notification和訂閱Notification. 類NotificationBroadcasterSupport則實現了NotificationEmitter.
-
NotificationListener, 實現此接口的可以訂閱JMX的Notification。
-
Notification, 消息本身。
修改MessageEngine, 使它在pause的時候發送通知給訂閱者。我把修改的部分貼上。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
public class MessageEngine extends NotificationBroadcasterSupport implements MessageEngineMXBean { private long sequenceNumber = 1 ; ... ... ... ... public MessageEngine() { addNotificationListener( new NotificationListener() { @Override public void handleNotification(Notification notification, Object handback) { System.out.println( "*** Handling new notification ***" ); System.out.println( "Message: " + notification.getMessage()); System.out.println( "Seq: " + notification.getSequenceNumber()); System.out.println( "*********************************" ); } }, null , null ); } ... ... ... ... @Override public void pause( boolean pause) { Notification n = new AttributeChangeNotification( this , sequenceNumber++, System.currentTimeMillis(), "Pause changed" , "Paused" , "boolean" , Echo.pause, pause); Echo.pause = pause; this .sendNotification(n); } ... ... ... ... // 規定可以發送的Notification Type,不在Type list中的Notification不會被發送。 @Override public MBeanNotificationInfo[] getNotificationInfo() { String[] types = new String[]{ AttributeChangeNotification.ATTRIBUTE_CHANGE }; String name = AttributeChangeNotification. class .getName(); String description = "An attribute of this MBean has changed" ; MBeanNotificationInfo info = new MBeanNotificationInfo(types, name, description); return new MBeanNotificationInfo[]{info}; } } |
Client端如何使用Notification,可以查看后面的Client一節。
JMX Agent
如果說Agent只是被Local使用,比如本地的JConsole,只需要開啟MBeanServer,并注冊MBean即可。不需要配置協議適配器。但如果需要遠程管理,比如遠程的JConsole或者自定義的管理器,則還需要配置兩者相互打交道的協議適配器。
1
2
3
4
5
6
7
8
9
10
11
12
|
public class MessageEngineAgent { public void start() { MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); try { ObjectName mxbeanName = new ObjectName( "com.example:type=MessageEngine" ); MessageEngineMXBean mxbean = new MessageEngine(); mbs.registerMBean(mxbean, mxbeanName); } catch (Exception e) { e.printStackTrace(); } } } |
因為java默認自帶的了JMX RMI的連接器。所以,只需要在啟動java程序的時候帶上運行參數,就可以開啟Agent的RMI協議的連接器。
1
2
3
4
|
java -Dcom.sun.management.jmxremote.port = 9999 \ -Dcom.sun.management.jmxremote.authenticate = false \ -Dcom.sun.management.jmxremote.ssl = false \ jmx.Echo |
認證與授權
JMX的認證與授權是非常必要的,我們不可能允許任何client都能連接我們的Server。JMX的認證和授權可以復雜的使用LDAP, SSL。也可以使用最簡單的文件存儲用戶信息方式。本文作為啟蒙,只給出最簡單的認證方式。
在java啟動的時候,添加運行參數:
1
2
3
4
5
6
|
java -Dcom.sun.management.jmxremote.port = 9999 \ -Dcom.sun.management.jmxremote.authenticate = true \ -Dcom.sun.management.jmxremote.password.file = pathTo/my.password \ -Dcom.sun.management.jmxremote.access.file = pathTo/my.access \ -Dcom.sun.management.jmxremote.ssl = false \ jmx.Echo |
my.password里面定義了用戶名和密碼:
1
2
|
user1 password1 user2 password2 |
my.access里面定義了用戶授權信息:
1
2
3
4
|
user1 readOnly user2 readWrite \ create jmx.*,javax.management.timer.* \ unregister |
更詳細的內容可以從這里找到: http://docs.oracle.com/javase/7/docs/technotes/guides/management/agent.html 。
現在可以啟動程序了。啟動以后,我們使用下面的Client來連接我們寫的JMX Agent.
JMX Client
JConsole
JDK提供了一個工具在jdk/bin目錄下面,這就是JConsole。使用JConsole可以遠程或本地連接JMX agent。如下圖所以:
無論是遠程還是本地,連接進去所看到的都一樣。進去MBeans面板以后,找到MessageEngine。MessageEngine下面有 Attributes, Operations和Notification。可以瀏覽MessageEngine中的Attributes并更改那些可寫的屬性。也可以執行 Operations下面的stop, pause方法。此外,必須訂閱Notifications才能收到消息。
JConsole有缺點,它只能對MXBean中的主要基本類型做修改,但不能修改復雜類型。
Custom Client
我們也可以用java寫client調用Agent。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
public class Client implements NotificationListener { public static void main(String[] args) { try { new Client().start(); } catch (Exception e) { e.printStackTrace(); } } public void start() throws Exception { // 如果agent不做配置的話,默認jndi path為jmxrmi JMXServiceURL url = new JMXServiceURL( "service:jmx:rmi://localhost/jndi/rmi://localhost:9999/jmxrmi" ); JMXConnector jmxc = JMXConnectorFactory.connect(url, null ); MBeanServerConnection server = jmxc.getMBeanServerConnection(); ObjectName mbeanName = new ObjectName( "com.example:type=MessageEngine" ); // 訂閱Notification server.addNotificationListener(mbeanName, this , null , null ); // 訪問paused屬性。 boolean paused = (Boolean)server.getAttribute(mbeanName, "Paused" ); System.out.println(paused); if (!paused) { server.invoke(mbeanName, "pause" , new Object[]{ true }, new String[]{ "boolean" }); } // 構建一個jmx.Message類型實例。 CompositeType msgType = new CompositeType( "jmx.Message" , "Message Class Name" , new String[]{ "title" , "body" , "by" }, new String[]{ "title" , "body" , "by" }, new OpenType[]{SimpleType.STRING,SimpleType.STRING,SimpleType.STRING}); CompositeData msgData = new CompositeDataSupport(msgType, new String[]{ "title" , "body" , "by" }, new Object[]{ "Hello" , "This is a new message." , "xpbug" }); // 調用changeMessage方法 server.invoke(mbeanName, "changeMessage" , new Object[]{msgData}, new String[]{CompositeData. class .getName()}); server.invoke(mbeanName, "pause" , new Object[]{ false }, new String[]{ "boolean" }); // 訪問修改后的Message屬性。 msgData = (CompositeData)server.getAttribute(mbeanName, "Message" ); System.out.println( "The message changes to:" ); System.out.println(msgData.get( "title" )); System.out.println(msgData.get( "body" )); System.out.println(msgData.get( "by" )); Thread.sleep( 1000 * 10 ); } @Override public void handleNotification(Notification notification, Object handback) { System.out.println( "*** Handling new notification ***" ); System.out.println( "Message: " + notification.getMessage()); System.out.println( "Seq: " + notification.getSequenceNumber()); System.out.println( "*********************************" ); } } |
運行一下client,看看都發生了什么。
源碼下載
http://pan.baidu.com/s/1sjLKewX
來自:http://my.oschina.net/xpbug/blog/221547
文章列表