JMX(Java Management Extensions)是一個爲應用程序植入管理功能的框架。JMX是一套標準的代理和服務,實際上,用戶能夠在任何Java應用程序中使用這些代理和服務實現管理。這是官方文檔上的定義,我看過不少次也沒法很好的理解。我我的的理解是JMX讓程序有被管理的功能,例如你開發一個WEB網站,它是在24小時不間斷運行,那麼你確定會對網站進行監控,如天天的UV、PV是多少;又或者在業務高峯的期間,你想對接口進行限流,就必須去修改接口併發的配置值。html
應用場景:中間件軟件WebLogic的管理頁面就是基於JMX開發的,而JBoss則整個系統都基於JMX構架。java
對於一些參數的修改,網上有一段描述仍是比較形象的:linux
一、程序初哥通常是寫死在程序中,到要改變的時候就去修改代碼,而後從新編譯發佈。緩存
二、程序熟手則配置在文件中(JAVA通常都是properties文件),到要改變的時候只要修改配置文件,但仍是必須重啓系統,以便讀取配置文件裏最新的值。tomcat
三、程序好手則會寫一段代碼,把配置值緩存起來,系統在獲取的時候,先看看配置文件有沒有改動,若有改動則從新從配置裏讀取,不然從緩存裏讀取。服務器
四、程序高手則懂得物爲我所用,用JMX把須要配置的屬性集中在一個類中,而後寫一個MBean,再進行相關配置。另外JMX還提供了一個工具頁,以方便咱們對參數值進行修改。架構
從圖中咱們能夠看到,JMX的結構一共分爲三層:併發
一、基礎層:主要是MBean,被管理的資源。框架
MBean分爲以下四種,我接下來主要介紹standard MBeandom
類型 | 描述 |
standard MBean | 這種類型的MBean最簡單,它能管理的資源(包括屬性,方法,時間)必須定義在接口中,而後MBean必須實現這個接口。它的命名也必須遵循必定的規範,例如咱們的MBean爲Hello,則接口必須爲HelloMBean。 |
dynamic MBean | 必須實現javax.management.DynamicMBean接口,全部的屬性,方法都在運行時定義 |
open MBean | 此MBean的規範還不完善,正在改進中 |
model MBean | 與標準和動態MBean相比,你能夠不用寫MBean類,只需使用javax.management.modelmbean.RequiredModelMBean便可。RequiredModelMBean實現了ModelMBean接口,而ModelMBean擴展了DynamicMBean接口,所以與DynamicMBean類似,Model MBean的管理資源也是在運行時定義的。與DynamicMBean不一樣的是,DynamicMBean管理的資源通常定義在DynamicMBean中(運行時才決定管理那些資源),而model MBean管理的資源並不在MBean中,而是在外部(一般是一個類),只有在運行時,才經過set方法將其加入到model MBean中。後面的例子會有詳細介紹 |
二、適配層:MBeanServer,主要是提供對資源的註冊和管理。
三、接入層:提供遠程訪問的入口。
接下來我這裏會用程序來介紹三種訪問JMX的方式:
一、 首先定義一個MBean接口,接口的命名規範爲以具體的實現類爲前綴(這個規範很重要)
1 package jmx; 2 3 public interface HelloMBean 4 { 5 public String getName(); 6 7 public void setName(String name); 8 9 public String getAge(); 10 11 public void setAge(String age); 12 13 public void helloWorld(); 14 15 public void helloWorld(String str); 16 17 public void getTelephone(); 18 }
二、定義一個實現類,實現上面的接口:
1 package jmx; 2 3 /* 4 * 該類名稱必須與實現的接口的前綴保持一致(即MBean前面的名稱 5 */ 6 public class Hello implements HelloMBean 7 { 8 private String name; 9 10 private String age; 11 12 public void getTelephone() 13 { 14 System.out.println("get Telephone"); 15 } 16 17 public void helloWorld() 18 { 19 System.out.println("hello world"); 20 } 21 22 public void helloWorld(String str) 23 { 24 System.out.println("helloWorld:" + str); 25 } 26 27 public String getName() 28 { 29 System.out.println("get name 123"); 30 return name; 31 } 32 33 public void setName(String name) 34 { 35 System.out.println("set name 123"); 36 this.name = name; 37 } 38 39 public String getAge() 40 { 41 System.out.println("get age 123"); 42 return age; 43 } 44 45 public void setAge(String age) 46 { 47 System.out.println("set age 123"); 48 this.age = age; 49 } 53 }
三、定義agent層:
1 package jmx; 2 3 import java.lang.management.ManagementFactory; 4 5 import javax.management.JMException; 6 import javax.management.MBeanServer; 7 import javax.management.ObjectName; 8 9 public class HelloAgent 10 { 11 public static void main(String[] args) throws JMException, Exception 12 { 13 MBeanServer server = ManagementFactory.getPlatformMBeanServer(); 14 ObjectName helloName = new ObjectName("jmxBean:name=hello"); 15 //create mbean and register mbean 16 server.registerMBean(new Hello(), helloName); 17 Thread.sleep(60*60*1000); 18 } 19 }
一、其中第13行是經過工廠類獲取MBeanServer,用來作MBean的容器 。
二、第14行中的ObjectName中的取名是有必定規範的,格式爲:「域名:name=MBean名稱」,其中域名和MBean的名稱能夠任意取。這樣定義後,就能夠惟一標識咱們定義的這個MBean的實現類了。
三、第16行是將Hello這個類注入到MBeanServer中,注入須要建立一個ObjectName類
這樣,一個簡單的JMX的DEMO已經寫完了,如今咱們經過JDK提供的Jconsole來進行操做。
一、首先在本身的本地路徑下:C:\Program Files (x86)\Java\jdk1.6.0_43\bin找到jconsole.exe這個小工具,雙擊打開:
二、雙擊打開咱們的本地進程:HelloAgent:
3.在這個界面上,咱們能夠給程序中HelloMBean的屬性賦值,也能夠調用其中的方法:
四、控制檯打印以下:
這裏,咱們複用上面的接口和實現類,只須要改動適配層,這裏須要到導入外部jar包jdmk
package jmx; import java.lang.management.ManagementFactory; import javax.management.JMException; import javax.management.MBeanServer; import javax.management.ObjectName; import com.sun.jdmk.comm.HtmlAdaptorServer; public class HelloAgent { public static void main(String[] args) throws JMException, Exception { MBeanServer server = ManagementFactory.getPlatformMBeanServer(); ObjectName helloName = new ObjectName("jmxBean:name=hello"); //create mbean and register mbean server.registerMBean(new Hello(), helloName); ObjectName adapterName = new ObjectName("HelloAgent:name=htmladapter,port=8082"); HtmlAdaptorServer adapter = new HtmlAdaptorServer(); server.registerMBean(adapter, adapterName); adapter.start(); } }
咱們訪問地址:http://localhost:8082,點擊name=hello:
一、在這裏建立一個AdaptorServer,這個類將決定MBean的管理界面,這裏用最普通的Html型界面。AdaptorServer其實也是一個MBean。
二、咱們能夠看到這個工具頁,其實與咱們上一個案例中的Jconsole中的管理界面相似,均可以操做資源中的屬性和方法。
一、這裏須要對agent進行修改,增長ip和porta綁定部分的邏輯
package jmxTest; import java.io.IOException; import java.lang.management.ManagementFactory; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import javax.management.JMException; import javax.management.MBeanServer; import javax.management.ObjectName; import javax.management.remote.JMXConnectorServer; import javax.management.remote.JMXConnectorServerFactory; import javax.management.remote.JMXServiceURL; public class HelloAgent { public static void main(String[] args) throws JMException, NullPointerException { MBeanServer server = ManagementFactory.getPlatformMBeanServer(); ObjectName helloName = new ObjectName("jmxBean:name=hello"); //create mbean and register mbean server.registerMBean(new Hello(), helloName); try { //這個步驟很重要,註冊一個端口,綁定url後用於客戶端經過rmi方式鏈接JMXConnectorServer LocateRegistry.createRegistry(9999); //URL路徑的結尾能夠隨意指定,但若是須要用Jconsole來進行鏈接,則必須使用jmxrmi JMXServiceURL url = new JMXServiceURL ("service:jmx:rmi:///jndi/rmi://localhost:9999/jmxrmi"); JMXConnectorServer jcs = JMXConnectorServerFactory.newJMXConnectorServer(url, null, server); System.out.println("begin rmi start"); jcs.start(); System.out.println("rmi start"); } catch (RemoteException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }
}
}
寫到這裏,若是沒有client進行遠程鏈接,可使用Jconsole進行遠程訪問:
二、客戶端Client程序,用於與agent進行遠程鏈接:
1 package jmx; 2 3 import java.io.IOException; 4 5 import javax.management.Attribute; 6 import javax.management.MBeanServerConnection; 7 import javax.management.MBeanServerInvocationHandler; 8 import javax.management.ObjectName; 9 import javax.management.remote.JMXConnector; 10 import javax.management.remote.JMXConnectorFactory; 11 import javax.management.remote.JMXServiceURL; 12 13 14 public class Client 15 { 16 public static void main(String[] args) throws IOException, Exception, NullPointerException 17 { 18 JMXServiceURL url = new JMXServiceURL 19 ("service:jmx:rmi:///jndi/rmi://localhost:9999/jmxrmi"); 20 JMXConnector jmxc = JMXConnectorFactory.connect(url,null); 21 22 MBeanServerConnection mbsc = jmxc.getMBeanServerConnection(); 23 //ObjectName的名稱與前面註冊時候的保持一致 24 ObjectName mbeanName = new ObjectName("jmxBean:name=hello"); 25 26 System.out.println("Domains ......"); 27 String[] domains = mbsc.getDomains(); 28 29 for(int i=0;i<domains.length;i++) 30 { 31 System.out.println("doumain[" + i + "]=" + domains[i] ); 32 } 33 34 System.out.println("MBean count = " + mbsc.getMBeanCount()); 35 //設置指定Mbean的特定屬性值 36 //這裏的setAttribute、getAttribute操做只能針對bean的屬性 37 //例如對getName或者setName進行操做,只能使用Name,須要去除方法的前綴 38 mbsc.setAttribute(mbeanName, new Attribute("Name","杭州")); 39 mbsc.setAttribute(mbeanName, new Attribute("Age","1990")); 40 String age = (String)mbsc.getAttribute(mbeanName, "Age"); 41 String name = (String)mbsc.getAttribute(mbeanName, "Name"); 42 System.out.println("age=" + age + ";name=" + name); 43 44 HelloMBean proxy = MBeanServerInvocationHandler. 45 newProxyInstance(mbsc, mbeanName, HelloMBean.class, false); 46 proxy.helloWorld(); 47 proxy.helloWorld("migu"); 48 proxy.getTelephone(); 49 //invoke調用bean的方法,只針對非設置屬性的方法 50 //例如invoke不能對getName方法進行調用 51 mbsc.invoke(mbeanName, "getTelephone", null, null); 52 mbsc.invoke(mbeanName, "helloWorld", 53 new String[]{"I'll connect to JMX Server via client2"}, new String[]{"java.lang.String"}); 54 mbsc.invoke(mbeanName, "helloWorld", null, null); 55 } 56 }
a、在35到41行,是對屬性進行賦值和取值,這裏咱們不能直接調用方法,而是經過setAttribute、getAttrubute方法來進行操做,則屬性的首字母要大寫。
b、對資源裏面的方法進行操做有兩種方式:一是經過代理直接調用方法;二是經過JAVA的反射注入的方式進行方法的調用。
下面咱們來看看執行結果,先執行agent,再執行客戶端:
c、client的控制檯打印結果:
d、agent控制檯打印結果:
MBean之間的通訊是必不可少的,Notification就起到了在MBean之間溝通橋樑的做用。JMX 的通知由四部分組成:
一、Notification這個至關於一個信息包,封裝了須要傳遞的信息
二、Notification broadcaster這個至關於一個廣播器,把消息廣播出。
三、Notification listener 這是一個監聽器,用於監聽廣播出來的通知信息。
四、Notification filiter 這個一個過濾器,過濾掉不須要的通知。這個通常不多使用。
這裏咱們使用平常打招呼的場景:jack與我偶遇,jack說:hi;我禮貌的回答:hello,jack。
這裏咱們先分別建立兩個資源:
package jmx; /* * 該類名稱必須與實現的接口的前綴保持一致(即MBean前面的名稱 */ public class Hello implements HelloMBean { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public void printHello() { System.out.println("Hello World, " + name); } public void printHello(String whoName) { System.out.println("Hello , " + whoName); } }
package jmx; /* * 接口名必須以MBean結尾 */ public interface HelloMBean { public String getName(); public void setName(String name); public void printHello(); public void printHello(String whoName); }
package jmx; import javax.management.Notification; import javax.management.NotificationBroadcasterSupport; public class Jack extends NotificationBroadcasterSupport implements JackMBean { private int seq = 0; public void hi() { //建立一個信息包 Notification notify = //通知名稱;誰發起的通知;序列號;發起通知時間;發送的消息 new Notification("jack.hi",this,++seq,System.currentTimeMillis(),"jack"); sendNotification(notify); } }
package jmx; public interface JackMBean { public void hi(); }
這裏的類Jack不只實現了MBean接口,還繼承了NotificationBroadcasterSupport。jack在這裏建立併發送了一個消息包。
package jmx; import javax.management.Notification; import javax.management.NotificationListener; public class HelloListener implements NotificationListener { public void handleNotification(Notification notification, Object handback) { if(handback instanceof Hello) { Hello hello = (Hello)handback; hello.printHello(notification.getMessage()); } } }
package jmx; import java.lang.management.ManagementFactory; import javax.management.JMException; import javax.management.MBeanServer; import javax.management.ObjectName; public class HelloAgent { public static void main(String[] args) throws JMException, Exception { MBeanServer server = ManagementFactory.getPlatformMBeanServer(); ObjectName helloName = new ObjectName("yunge:name=Hello"); Hello hello=new Hello(); server.registerMBean(hello, helloName); Jack jack = new Jack(); server.registerMBean(jack, new ObjectName("jack:name=Jack")); jack.addNotificationListener(new HelloListener(), null, hello); Thread.sleep(500000); } }
咱們用Jconsole來進行訪問:
這裏咱們能夠看到有兩個MBean,一個是yunge,一個是jack。咱們執行jack的hi方法後,去看下控制檯上的打印信息;
利用JMX監控Tomcat,就是至關於部署在tomcat上的應用做爲服務端,也就是被管理資源的對象。而後經過程序或者jconsole遠程鏈接到該應用上來。遠程鏈接須要服務器端提供ip和port。若是須要加密訪問的話,還須要配置用戶名、密碼等參數。
主要是在tomcat下的文件catalina.sh中進行一些環境變量的配置配置:
-Dcom.sun.management.jmxremote=true 相關 JMX 代理偵聽開關
-Djava.rmi.server.hostname 服務器端的IP
-Dcom.sun.management.jmxremote.port=29094 相關 JMX 代理偵聽請求的端口
-Dcom.sun.management.jmxremote.ssl=false 指定是否使用 SSL 通信
-Dcom.sun.management.jmxremote.authenticate=false 指定是否須要密碼驗證這樣就能夠經過客戶端或者jconsole對tomcat進行監控。