一、JMX简介
参考:https://honeypps.com/java/jmx-quick-start-1-standard-mbean/
JMX的全称为Java Management Extensions. 顾名思义,是管理Java的一种扩展。这种机制可以方便的管理、监控正在运行中的Java程序。常用于管理线程,内存,日志Level,服务重启,系统环境等。
基本术语
- MBean:是Managed Bean的简称,可以翻译为“管理构件”。在JMX中MBean代表一个被管理的资源实例,通过MBean中暴露的方法和属性,外界可以获取被管理的资源的状态和操纵MBean的行为。事实上,MBean就是一个Java Object,同JavaBean模型一样,外界使用自醒和反射来获取Object的值和调用Object的方法,只是MBean更为复杂和高级一些。MBean通过公共方法以及遵从特定的设计模式封装了属性和操作,以便暴露给管理应用程序。例如,一个只读属性在管理构件中只有Get方法,既有Get又有Set方法表示是一个可读写的属性。一共有四种类型的MBean: Standard MBean, Dynamic MBean, Open MBean, Model MBean。
- MBeanServer:MBean生存在一个MBeanServer中。MBeanServer管理这些MBean,并且代理外界对它们的访问。并且MBeanServer提供了一种注册机制,是的外界可以通过名字来得到相应的MBean实例。
- JMX Agent:Agent只是一个Java进程,它包括这个MBeanServer和一系列附加的MbeanService。当然这些Service也是通过MBean的形式来发布。
- Protocol Adapters and Connectors:MBeanServer依赖于Protocol Adapters和Connectors来和运行该代理的Java虚拟机之外的管理应用程序进行通信。Protocol Adapters通过特定的协议提供了一张注册在MBeanServer的MBean的视图。例如,一个HTML Adapter可以将所有注册过的MBean显示在Web 页面上。不同的协议,提供不同的视图。Connectors还必须提供管理应用一方的接口以使代理和管理应用程序进行通信,即针对不同的协议,Connectors必须提供同样的远程接口来封装通信过程。当远程应用程序使用这个接口时,就可以通过网络透明的和代理进行交互,而忽略协议本身。Adapters和Connectors使MBean服务器与管理应用程序能进行通信。因此,一个代理要被管理,它必须提供至少一个Protocol Adapter或者Connector。面临多种管理应用时,代理可以包含各种不同的Protocol Adapters和Connectors。当前已经实现和将要实现的Protocol Adapters和Connectors包括: RMI Connector, SNMP Adapter, IIOP Adapter, HTML Adapter, HTTP Connector.
Adapter 和Connector的区别在于:Adapter是使用某种Internet协议来与JMX Agent获得联系,Agent端会有一个对象 (Adapter)来处理有关协议的细节。比如SNMP Adapter和HTTP Adapter。而Connector则是使用类似RPC的方式来访问Agent,在Agent端和客户端都必须有这样一个对象来处理相应的请求与应答。比如RMI Connector。
JMX Agent可以带有任意多个Adapter,因此可以使用多种不同的方式访问Agent。
JMX基本构架
JMX分为三层,分别负责处理不同的事务。它们分别是:
- Instrumentation 层
Instrumentation层主要包括了一系列的接口定义和描述如何开发MBean的规范。通常JMX所管理的资源有一个或多个MBean组成,因此这个资源可以是任何由Java语言开发的组件,或是一个JavaWrapper包装的其他语言开发的资源。 - Agent 层
Agent 用来管理相应的资源,并且为远端用户提供访问的接口。Agent层构建在Intrumentation层之上,并且使用并管理 Instrumentation层内部描述的组件。Agent层主要定义了各种服务以及通信模型。该层的核心是一MBeanServer,所有的MBean都要向它注册,才能被管理。注册在MBeanServer上的MBean并不直接和远程应用程序进行通信,他们通过协议适配器(Adapter)和连接器(Connector)进行通信。通常Agent由一个MBeanServer和多个系统服务组成。JMX Agent并不关心它所管理的资源是什么。 - Distributed 层
Distributed层关心Agent如何被远端用户访问的细节。它定义了一系列用来访问Agent的接口和组件,包括Adapter和Connector的描述。
如果一个Java对象可以由一个遵循JMX规范的管理器应用管理,那么这个Java对象就可以由JMX管理资源。要使一个Java对象可管理,则必须创建相应的MBean对象,并通过这些MBean对象管理相应的Java对象。当拥有MBean类后,需要将其实例化并注册到MBeanServer上。
JMX的优点
//优点就是两个字:监控!
参考文章:https://www.zhihu.com/question/36688387/answer/1264761873
这门技术是对Java应用程序和JVM进行监控和管理的,在企业实际开发过程中,所有的程序都是需要进行监控的,没有监控,程序就相当于是裸奔。
在大公司中,没有监控是绝对不可能的。JMX是Java官方提供的一套用于监控Java程序和JVM运行时状态的标准API。通过JMX,我们可以监控的内容包括:
- 服务器中各种资源的使用情况:如CPU、内存等
- JVM内存使用情况
- JVM中的线程情况
- JVM中加载的类
- ...
很多开源软件都是用JMX来实现性能监控的,比如大名鼎鼎的Kafka:
Kafka 的每个监控指标都是以 JMX MBean 的形式定义的,具体来说,Kafka 使用基于 Metrics 的监控指标体系在方法调用的过程中设置埋点来统计 broke 端和 clients 端的各种监控指标,然后将采集的指标通过Metrics的JmxReporter送往JMX中。
JConsole是用来提供运行于Java平台中的应用程序的性能与资源消耗信息的一款工具,随着JDK一同安装,只要你的电脑上安装了JDK,就肯定安装了JConsole。JMX采集到的Kafka性能数据可以通过JConsole访问到:
Kafka 通过JMX提供了几百个性能指标,上面这样展示方式看起来肯定是有点low。优化之后:利用JMX提供的接口可以让我们自己的程序获取到JMX中的数据,比如开源Kafka监控工具Kafka Eagle通过获取Kafka的JMX数据:
二、3种访问JMX的方式
参考文章:https://www.cnblogs.com/dongguacai/p/5900507.html
2.1 通过Jconsole访问
1、 首先定义一个MBean接口,接口的命名规范为以具体的实现类为前缀(这个规范很重要)
package jmx; public interface HelloMBean { public String getName(); public void setName(String name); public String getAge(); public void setAge(String age); public void helloWorld(); public void helloWorld(String str); public void getTelephone(); }
2、定义一个实现类,实现上面的接口:
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 }
3、定义agent层:
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("jmxBean:name=hello"); //create mbean and register mbean server.registerMBean(new Hello(), helloName); Thread.sleep(60*60*1000); } }
1、其中第13行是通过工厂类获取MBeanServer,用来做MBean的容器 。
2、第14行中的ObjectName中的取名是有一定规范的,格式为:“域名:name=MBean名称”,其中域名和MBean的名称可以任意取。这样定义后,就可以唯一标识我们定义的这个MBean的实现类了。
3、第16行是将Hello这个类注入到MBeanServer中,注入需要创建一个ObjectName类
这样,一个简单的JMX的DEMO已经写完了,现在我们通过JDK提供的Jconsole来进行操作。
1、首先在自己的本地路径下:C:\Program Files (x86)\Java\jdk1.6.0_43\bin找到jconsole.exe这个小工具,双击打开:
2、双击打开我们的本地进程:HelloAgent:
3.在这个界面上,我们可以给程序中HelloMBean的属性赋值,也可以调用其中的方法:
4、控制台打印如下:
2.2 通过JMX提供的工具页访问
这里,我们复用上面的接口和实现类,只需要改动适配层,这里需要到导入外部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:
1、在这里创建一个AdaptorServer,这个类将决定MBean的管理界面,这里用最普通的Html型界面。AdaptorServer其实也是一个MBean。
2、我们可以看到这个工具页,其实与我们上一个案例中的Jconsole中的管理界面类似,都可以操作资源中的属性和方法。
2.3 通过客户端进行远程访问JMX
1、这里需要对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进行远程访问:
2、客户端Client程序,用于与agent进行远程连接:
package jmx; import java.io.IOException; import javax.management.Attribute; import javax.management.MBeanServerConnection; import javax.management.MBeanServerInvocationHandler; import javax.management.ObjectName; import javax.management.remote.JMXConnector; import javax.management.remote.JMXConnectorFactory; import javax.management.remote.JMXServiceURL; public class Client { public static void main(String[] args) throws IOException, Exception, NullPointerException { JMXServiceURL url = new JMXServiceURL ("service:jmx:rmi:///jndi/rmi://localhost:9999/jmxrmi"); JMXConnector jmxc = JMXConnectorFactory.connect(url,null); MBeanServerConnection mbsc = jmxc.getMBeanServerConnection(); //ObjectName的名称与前面注册时候的保持一致 ObjectName mbeanName = new ObjectName("jmxBean:name=hello"); System.out.println("Domains ......"); String[] domains = mbsc.getDomains(); for(int i=0;i<domains.length;i++) { System.out.println("doumain[" + i + "]=" + domains[i] ); } System.out.println("MBean count = " + mbsc.getMBeanCount()); //设置指定Mbean的特定属性值 //这里的setAttribute、getAttribute操作只能针对bean的属性 //例如对getName或者setName进行操作,只能使用Name,需要去除方法的前缀 mbsc.setAttribute(mbeanName, new Attribute("Name","杭州")); mbsc.setAttribute(mbeanName, new Attribute("Age","1990")); String age = (String)mbsc.getAttribute(mbeanName, "Age"); String name = (String)mbsc.getAttribute(mbeanName, "Name"); System.out.println("age=" + age + ";name=" + name); HelloMBean proxy = MBeanServerInvocationHandler. newProxyInstance(mbsc, mbeanName, HelloMBean.class, false); proxy.helloWorld(); proxy.helloWorld("migu"); proxy.getTelephone(); //invoke调用bean的方法,只针对非设置属性的方法 //例如invoke不能对getName方法进行调用 mbsc.invoke(mbeanName, "getTelephone", null, null); mbsc.invoke(mbeanName, "helloWorld", new String[]{"I'll connect to JMX Server via client2"}, new String[]{"java.lang.String"}); mbsc.invoke(mbeanName, "helloWorld", null, null); } }
a、在35到41行,是对属性进行赋值和取值,这里我们不能直接调用方法,而是通过setAttribute、getAttrubute方法来进行操作,则属性的首字母要大写。
b、对资源里面的方法进行操作有两种方式:一是通过代理直接调用方法;二是通过JAVA的反射注入的方式进行方法的调用。
下面我们来看看执行结果,先执行agent,再执行客户端:
c、client的控制台打印结果:
d、agent控制台打印结果: