JMX笔记整理
JMX的定義
JMX 全稱為 Java Management Extensions,翻譯過來就是 Java 管理擴展,用來管理和監測 Java 程序。最常用到的就是對于 JVM 的監測和管理,比如 JVM 內存、CPU 使用率、線程數、垃圾收集情況等等。另外,還可以用作日志級別的動態修改,比如 log4j 就支持 JMX 方式動態修改線上服務的日志級別。最主要的還是被用來做各種監控工具,比如文章開頭提到的 Spring Boot Actuator、JConsole、VisualVM 等。
JMX 既是 Java 管理系統的一個標準,一個規范,也是一個接口,一個框架。有標準、有規范是為了讓開發者可以定制開發自己的擴展功能,而且作為一個框架來講,JDK 已經幫我們實現了常用的功能,尤其是對 JVM 的監控和管理。
JMX架構
從圖中我們可以看到,JMX的結構一共分為三層:
- 基礎層
- 適配層
- 接入層
基礎層
基礎層主要是MBean,被管理的資源。
JMX 是通過各種 MBean(Managed Bean) 傳遞消息的,MBean 其實就是我們經常說的 Java Bean,只不過由于它比較特殊,所以稱之為 MBean。既然是個 Bean,里面就是一些屬性和方法,外界就可以獲取被管理的資源的狀態和操縱MBean的行為。JMX 中共有四種類型的 MBean,分別是 Standard MBean, Dynamic MBean, Open MBean, Model MBean。JDK 提供的 MBean 主要在 java.lang.management 和 javax.management包里面。
Standard MBean
Standard MBean 就是普通的 Java Bean 沒有區別,它也是 JMX 中最簡單、使用最多的一種。主要在java.lang.management包里面。
它能管理的資源(包括屬性,方法,時間)必須定義在接口中,然后MBean必須實現這個接口。它的命名也必須遵循一定的規范,例如我們的MBean為Hello,則接口必須為HelloMBean。
Dynamic MBean
Dynamic MBean 其實是一種妥協的產物,由于已經存在一些這種 MBean,而將其改造成標準 MBean 比較費力而且不切實際,所以就有了動態 MBean。Dynamic MBean 的接口在 javax.management.DynamicMBean這里,里面定義一些接口方法,比如動態獲取屬性、設置屬性等。
Dynamic MBean 必須實現javax.management.DynamicMBean接口,所有的屬性,方法都在運行時定義。
Open MBean
Open MBean是Dynamic MBean 的一種。
Open MBean 與其它動態 MBean 的唯一區別在于,前者對其公開接口的參數和返回值有所限制 —— 只能是基本類型或者 javax.management.openmbean包內的 ArrayType、CompositeType、TarbularType 等類型。這主要是考慮到管理系統的分布,很可能遠端管理系統甚至 MBServer 層都不具有 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
MBeanServer 主要是提供對資源的注冊和管理。一般一個 JVM 只有一個 MBeanServer,所有的 MBean 都要注冊到 MBeanServer 上,并通過 MBeanServer 對外提供服務。一般用 ManagementFactory.getPlatformMBeanServer()方法獲取當前 JVM 內的 MBeanServer。
接入層
接入層提供遠程訪問的入口。
適配器和連接器
寫好的 MBean 注冊到 MBeanServer 上之后,功能已經具備了。適配器和連接器就是將這些功能開放出來的方式。 比如 HTTP協議適配器,就是將功能以 HTTP 協議開放出去,這樣我們就可以在瀏覽器使用了。但是 JDK 只是提供了適配器的實現標準,并沒有具體的實現,比較常用的是 HtmlAdaptorServer,需要 jmxtools.jar 包的支持。
連接器是各種客戶端最常用的,JDK 提供的默認連接器是 RMI 連接器,JConsole、VisualVM 都是使用它。
實現并使用一個 MBean
定義 MBean 接口 和實體類
// 接口 public interface UserMBean {String getName();String getPassword();String getPhone();void say(); } //接口命名規范:實體類名+MBean public class User implements UserMBean {@Overridepublic String getName() {return "風箏";}@Overridepublic String getPassword() {return "密碼不可見";}@Overridepublic String getPhone() {return "18900000000";}@Overridepublic void say() {System.out.println("Hello JMX");} }將定義好的 MBean 注冊到 MBeanServer
public static void main(String[] args) throws Exception {//獲取MBeanServer。MBeanServer server = ManagementFactory.getPlatformMBeanServer();//定義對象名稱。//ObjectName 是 MBean 的唯一標示,一個 MBeanServer 不能有重復。完整的格式「自定義命名空間:type=自定義類型,name=自定義名稱」。當然可以只聲明 type ,不聲明 name。ObjectName userName = new ObjectName("FengZheng:type=customer,name=customerUserBean");//注冊MBeanserver.registerMBean(new User(), userName);try {//這個步驟很重要,注冊一個端口,綁定url后用于客戶端通過rmi方式連接JMXConnectorServerLocateRegistry.createRegistry(8999);//URL路徑的結尾可以隨意指定,但如果需要用Jconsole來進行連接,則必須使用jmxrmiJMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:8999/jmxrmi");//ConnectorJMXConnectorServer jcs = JMXConnectorServerFactory.newJMXConnectorServer(url, null, server);System.out.println("begin rmi start");//啟動Serverjcs.start();System.out.println("rmi start");} catch (RemoteException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}Thread.sleep(60 * 60 * 1000); }使用JMX
JConsole
使用 RMI 方式連接
RMI 一般是用來連接遠程服務的,當然本地進程也可以。這也是實現連接遠程服務客戶端的第一步。我們在注冊 MBean 的時候,有沒有注意到注冊完成后,還有一大段代碼,那段代碼就是用來開啟 RMI 連接的,開啟 8999 端口作為 RMI 訪問端口,然后客戶端就可以用固定的連接串連接了。
連接串的格式 service:jmx:rmi:///jndi/rmi://host:port/jmxrmi
public class Client {public static void main(String[] args) throws IOException, Exception, NullPointerException {//JMX地址String jmxUrl = "service:jmx:rmi:///jndi/rmi://localhost:8999/jmxrmi";monitor(jmxUrl);}public static void monitor(String url) throws Exception{//獲得一個 JMXServiceURL對象JMXServiceURL jmxServiceURL = new JMXServiceURL(url);//獲取 JMXConnector,連接 Server,JMXConnector jmxc = JMXConnectorFactory.connect(jmxServiceURL, null);//獲取到 MBeanServerConnectionMBeanServerConnection msc = jmxc.getMBeanServerConnection();//獲取所有命名空間String[] domains = msc.getDomains();for (String domain : domains) {System.out.println(domain);}} }Html適配器
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 是 MBean 的唯一標示,一個 MBeanServer 不能有重復。完整的格式「自定義命名空間:type=自定義類型,name=自定義名稱」。當然可以只聲明 type ,不聲明 name。ObjectName userName = new ObjectName("FengZheng:type=customer,name=customerUserBean");//注冊MBeanserver.registerMBean(new User(), userName);//注冊一個AdapterObjectName adapterName = new ObjectName("UserAgent:name=htmladapter,port=8999"); HtmlAdaptorServer adapter = new HtmlAdaptorServer(); server.registerMBean(adapter, adapterName); adapter.start();} }我們訪問地址:[http://localhost:8999,點擊name=customerUserBean:即可查看MBean。
MBean 的獲取
正如各種工具里的 MBean 的樹形展示方式一樣, MBean 本身就是以這種層級關系存在的。
MBean 包含在 Domain 里,Domain 相當于是一套獨立的空間,這個空間里可以定義各種 type,各種 name 的 ObjectName。
通過 ObjectName 可以獲取到 MBean 的各種信息,包括屬性、操作、通知。
有些屬性是簡單數據類型,比如 int、long、double、String 類型,另外有些是比較復雜的,比方說 com.sun.management:type=HotSpotDiagnostic 的屬性 DiagnosticOptions 就是 javax.management.openmbean.CompositeData 類型。還有的屬性的數據類型是 javax.management.openmbean.TabularData。這些都要單獨處理。
Notification
MBean之間的通信是必不可少的,Notification就起到了在MBean之間溝通橋梁的作用。JMX 的通知由四部分組成:
1、Notification這個相當于一個信息包,封裝了需要傳遞的信息
2、Notification broadcaster這個相當于一個廣播器,把消息廣播出。
3、Notification listener 這是一個監聽器,用于監聽廣播出來的通知信息。
4、Notification filiter 這個一個過濾器,過濾掉不需要的通知。這個一般很少使用。
發送一個通用類型的通知,任何一個監聽者都會得到該通知。因此,監聽者需提供過濾器來選擇所需要接受的通知。任何類型的MBean,標準的或動態的,都可以作為一個通知發送者,也可以作為一個通知監聽者,或兩者都是。
場景:日常打招呼的場景:jack與我偶遇,jack說:hi;我禮貌的回答:hello,jack。
package jmx;/** 接口名必須以MBean結尾*/ public interface HelloMBean {public String getName(); public void setName(String name); public void printHello(); public void printHello(String whoName); } 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;public interface JackMBean {public void hi(); } package jmx;import javax.management.Notification; import javax.management.NotificationBroadcasterSupport;/** * Jack不僅實現了MBean接口,還繼承了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);}}Listener:
package jmx;import javax.management.Notification; import javax.management.NotificationListener;/**實現:NotificationListenerhandleNotification */ public class HelloListener implements NotificationListener {/**handback ,通知發起者notification,通知消息*/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();//注冊 Hello,ObjectName helloName = new ObjectName("yunge:name=Hello"); Hello hello=new Hello(); server.registerMBean(hello, helloName); //注冊 JackJack jack = new Jack();server.registerMBean(jack, new ObjectName("jack:name=Jack"));//Jack 添加listenerjack.addNotificationListener(new HelloListener(), null, hello);Thread.sleep(500000);} }接口
public interface NotificationListener extends java.util.EventListener {public void handleNotification(Notification notification, Object handback); } public interface NotificationBroadcaster {public void addNotificationListener(NotificationListener listener,NotificationFilter filter,Object handback)throws java.lang.IllegalArgumentException;public void removeNotificationListener(NotificationListener listener)throws ListenerNotFoundException;public MBeanNotificationInfo[] getNotificationInfo(); }public interface NotificationEmitter extends NotificationBroadcaster {public void removeNotificationListener(NotificationListener listener,NotificationFilter filter,Object handback)throws ListenerNotFoundException; }public class NotificationBroadcasterSupport implements NotificationEmitter {}啟用遠程JMX
-Dcom.sun.management.jmxremote=true # 相關 JMX 代理偵聽開關 -Djava.rmi.server.hostname # 服務器端的IP -Dcom.sun.management.jmxremote.port=8999 # 相關 JMX 代理偵聽請求的端口 -Dcom.sun.management.jmxremote.ssl=false # 指定是否使用 SSL 通訊 -Dcom.sun.management.jmxremote.authenticate=false # 指定是否需要密碼驗證參考
https://zhuanlan.zhihu.com/p/166530442
總結
- 上一篇: Spring Cloud Gateway
- 下一篇: Redis与Lua详解