备注:JDK版本:1.8.0,系统环境:Ubuntu16.04 LTS
JMX(Java Management Extensions,即Java管理扩展)是一个为应用程序、设备、系统等嵌入管理功能的框架。借助JMX可以方便的对应用进行监控,小到对属性的修改实时知悉,大到对应用程序接口访问的情况分析,以实时控制接口的并发量,了解应用的健康状态。
结合JMX规范(JSR003),可以知道JMX可以为我们的Java应用提供以下益处:
不需要大的投入就可以对应用程序进行监控;
提供一个可以伸缩的架构,无缝接入已经存在的监控系统;
开发简单,只需要借助于标准的Java代码;
参考:Java Management Extensions Specification, version 1.4
https://download.oracle.com/otn-pub/jcp/jmx-1_4-mrel4-eval-spec/jsr3-jmx-1_4-mrel4-spec-FINAL-v1_0.pdf
1、JMX架构

从上图中我们知道JMX分为四个层次:
设备层(Instrumentation Level):
定义了信息模型。在JMX中,各种管理对象以管理构件的形式存在,需要管理时,向MBean服务器进行注册,该层还定义了通知机制以及一些辅助元数据类。
代理层(Agent Level):
定义各种服务以及通信模型。该层的核心是一个MBean服务器,所有的管理构件都需要向它注册,才能被管理。注册在MBean服务器上管理构件并不直接和远程应用程序进行通信,它们通过协议适配器和连接器进行通信。而协议适配器和连接器也以管理构件的形式向MBean服务器注册才能提供相应的服务。
分布服务层(Distributed Service Level):
定义能对代理层进行操作的管理接口和构件,这样管理者就可以操作代理。
附加管理协议API
定义的API主要用来支持当前已经存在的网络管理协议,如SNMP、TMN、CIM/WBEM等。
参考:Java Management Extensions Specification, version 1.4
https://download.oracle.com/otn-pub/jcp/jmx-1_4-mrel4-eval-spec/jsr3-jmx-1_4-mrel4-spec-FINAL-v1_0.pdf
2、JMX管理Bean(MBean)
JMX管理的Bean分为以下四种类型:
| 类型 | 描述 |
| StandMBean | 最简单MBean,它能管理的资源(包括属性,操作)必须定义在接口中,然后MBean必须实现这个接口,命名也必须遵循一定的规范,例如我们的MBean为User,则接口必须为UserMBean |
| DynamicBean | 必须实现javax.management.DynamicMBean接口,所有的属性,操作都在运行时定义 |
| ModelMBean | 与标准和动态MBean相比,你可以不用写MBean类,只需使用javax.management.modelmbean.RequiredModelMBean即可。RequiredModelMBean实现了ModelMBean接口,而ModelMBean扩展了DynamicMBean接口,因此与DynamicMBean相似,ModelMBean管理的资源也是在运行时定义的。与DynamicMBean不同的是,DynamicMBean管理的资源一般定义在DynamicMBean中(运行时才决定管理那些资源),而ModelMBean管理的资源并不在MBean中,而是在外部(通常是一个类),在运行时,通过set方法将其加入到ModelMBean中 |
| OpenMBean | OpenMBean能够在运行时共享和使用管理数据和操作,而无需重新编译,开放的MBean有助于管理系统的灵活性和可扩展性 |
javax.management.DynamicMBean的接口方法如下图所示:

因此使用javax.management.DynamicMBean的时候,必须复写这些方法(下文说明用法)
本文将详细讲述如何借助StandMBean、DynamicMBean和ModelMBean来实现对应用的监控操作。
三、StandMBean演示
3.1 MBean接口定义
根据Standard MBean的要求,需要一个MBean接口,接口的命名规范以具体的实现类为前缀,MBean为后缀
package com.zhru.wechat.jdk.jmx;/*** 定义的MBean* @Author zhru* @Date 2019-10-30**/public interface UserMBean {String getName();void setName(String name);}
3.2 MBean接口实现
package com.zhru.wechat.jdk.jmx;/*** UserMBean接口实现* @Author zhru* @Date 2019-10-30**/public class User implements UserMBean {private String name;@Overridepublic String getName() {return this.name;}@Overridepublic void setName(String name) {this.name = name;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +'}';}}
3.3 定义Agent层
package com.zhru.wechat.jdk.jmx;import javax.management.MBeanServer;import javax.management.ObjectName;import java.lang.management.ManagementFactory;/*** 1.定义MBeanServer* 2.定义{@link javax.management.ObjectName}* 3.注册MBean* @Author zhru* @Date 2019-10-30**/public class JmxAgent {public static void main(String[] args) throws Exception {// 定义MBeanServerMBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();//定义ObjectNameObjectName userObjectName = new ObjectName("jmxBean:name=jmx");User user = new User();mBeanServer.registerMBean(user, userObjectName);// 30s后观察user对象的值Thread.sleep(30 * 1000);System.out.println(user);}}
上面的代码流程分为3个步骤:
1、 通过工厂类获取MbeanServer,用来做MBean的容器
2、 定义ObjectName,取名规范为 域名:name=Mbean名称
,其中域名和MBean的名称可以任取。
3、最后将User这个类注册到MBeanServer中
3.4 借助Jconsole操作
通过Java安装目录/bin/jconsole程序可以看到我们暴露出来的UserMBean,对它的name属性进行操作

30秒后输出的user结果:
/usr/lib/jdk1.8.0_171/bin/javaUser{name='zhru'}Process finished with exit code 0
4、DynamicMBean演示
4.1 定义DynamicMBean
从JMX MBean的定义中,我们知道动态MBean必须实现javax.management.DynamicMBean接口,并复写接口中的方法
package com.zhru.wechat.jdk.jmx;import ......;/*** DynamicMBean* @Author zhru* @Date 2019-10-30**/public class DomainBean implements DynamicMBean {//属性Nameprivate String name;//属性Ageprivate int age;public int getAge() {return age;}public void setAge(int age) {this.age = age;}public void setName(String name) {this.name = name;}public String getName() {return name;}public int add(int x, int y) {return x + y;}/*** 获取属性值* @param attribute* @return* @throws AttributeNotFoundException* @throws MBeanException* @throws ReflectionException*/@Overridepublic Object getAttribute(String attribute) throws AttributeNotFoundException, MBeanException, ReflectionException {if (attribute == null) {return null;}if ("name".equals(attribute)) {return name;}if ("age".equals(attribute)) {return age;}return null;}/*** 设置属性值* @param attribute* @throws AttributeNotFoundException* @throws InvalidAttributeValueException* @throws MBeanException* @throws ReflectionException*/@Overridepublic void setAttribute(Attribute attribute) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException {if (attribute == null) {return;}if ("name".equals(attribute.getName())) {this.name = (String) attribute.getValue();}if ("age".equals(attribute.getName())) {this.age = (int) attribute.getValue();}}/*** 获取Attribute集合* @param attributes* @return*/@Overridepublic AttributeList getAttributes(String[] attributes) {List<Attribute> collect = Stream.of(attributes).map(attribute -> {Attribute bute = null;try {Object o = getAttribute(attribute);bute = new Attribute(attribute, o);} catch (Exception e) {throw new RuntimeException("getAttributes()方法异常:" + e);}return bute;}).collect(Collectors.toList());return new AttributeList(collect);}/*** 批量设置属性* @param attributes* @return*/@Overridepublic AttributeList setAttributes(AttributeList attributes) {List<Attribute> collect = attributes.asList().stream().map(attribute -> {try {setAttribute(attribute);return attribute;} catch (Exception e) {throw new RuntimeException("setAttributes()方法异常:" + e);}}).collect(Collectors.toList());return new AttributeList(collect);}/*** 设置属性时调用的方法* @param actionName 方法名* @param params 方法参数* @param signature 签名数组* @return* @throws MBeanException* @throws ReflectionException*/@Overridepublic Object invoke(String actionName, Object[] params, String[] signature) throws MBeanException, ReflectionException {return invokeMethod(actionName, params);}/*** MBeanInfo信息,MBeanInfo中的信息会被暴露到MBeanServer中* 包括属性,操作等* @return*/@Overridepublic MBeanInfo getMBeanInfo() {MBeanInfo mBeanInfo = null;try {mBeanInfo = new MBeanInfo(DomainBean.class.getName(),"DynamicMbeanInfo使用",new MBeanAttributeInfo[]{new MBeanAttributeInfo("name", String.class.getName(), "Name属性", true, true, false),new MBeanAttributeInfo("age", int.class.getName(), "age属性", true, true, false)},new MBeanConstructorInfo[]{new MBeanConstructorInfo("SpringUser构造器", DomainBean.class.getConstructor())},new MBeanOperationInfo[]{new MBeanOperationInfo("setName操作", this.getClass().getMethod("setName", String.class)),new MBeanOperationInfo("getName操作", DomainBean.class.getMethod("getName")),new MBeanOperationInfo("setAge操作", DomainBean.class.getMethod("setAge", int.class)),new MBeanOperationInfo("getAge操作", DomainBean.class.getMethod("getAge")),new MBeanOperationInfo("add操作", this.getClass().getMethod("add", int.class, int.class))},new MBeanNotificationInfo[]{});} catch (Exception e) {throw new RuntimeException("getMBeanInfo()方法异常:" + e);}return mBeanInfo;}/*** 公共方法* @param actionName 方法名* @param params 方法参数* @return*/private Object invokeMethod(String actionName, Object[] params) {AtomicReference<Object> obj = new AtomicReference<>();Method[] methods = this.getClass().getMethods();Arrays.stream(methods).forEach(method -> {if (method.getName().equals(actionName)) {try {obj.set(method.invoke(DomainBean.this, params));} catch (Exception e) {e.printStackTrace();}}});return obj.get();}}
4.2 定义Agent层
/*** 1.定义MBeanServer* 2.定义{@link javax.management.ObjectName}* 3.注册MBean* @Author zhru* @Date 2019-10-30**/public class JmxAgent {public static void main(String[] args) throws Exception {// 定义MBeanServerMBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();//定义ObjectNameObjectName domainObjName = new ObjectName("jmxBean:name=dynamicMBean");// 注册MBeanServermBeanServer.registerMBean(new DomainBean(), domainObjName);Thread.sleep(30 * 60 * 1000);}}
4.3 借助Jconsole操作

从Jsoncole的图中我们可以看出,我们定义MBeanInfo时暴露的属性和操作,都会在Jconsole看到,并能手动的对属性进行设置,执行暴露的操作。
5、ModelMBean演示
StandMBean和DynamicMBean虽然可以将MBean暴露给MBeanServer,完成Bean的控制工作,但是两者都有直接的劣势:
StandMBean强制必须定义一个以MBean结尾的接口,并加以实现;
DynamicMBean必须实现javax.management.DynamicMBean接口,并复写接口的方法;
也就是说,都需要对类进行定制化的修改。倘若是接入第三方的代码,本身无法对第三方的代码进行更改,这个时候,StandMBean和DynamicMBean的劣势就会显现出来,而使用ModelMBean可以解决这个问题,实现不修改类完成对Bean的管理工作。
Model MBean也是一种专门化的动态管理构件,允许在运行时添加或覆盖需要定制的实现。JMX规范规定该类必须实现javax.management.modelmbean.RequiredModelMBean,管理者要做的就是实例化该类,将需要管理的Bean和它需要暴露的属性和方法组成的ModelMBeanInfo信息注入到这个对象,并注册到JMX代理中,即可实现对资源的管理。
ModelMBeanInfo对象描述了将会暴露给代理的构造函数、属性、操作和监听器。
下面将讲述ModelMBean类型的实现流程
5.1 定义ModelMBean
package com.zhru.wechat.jdk.jmx;/*** ModelMBean* 不需要实现任何接口* @Author zhru* @Date 2019-10-30**/public class ModelBean {private String beanName;private String beanDesc;public String getBeanName() {return beanName;}public void setBeanName(String beanName) {this.beanName = beanName;}public String getBeanDesc() {return beanDesc;}public void setBeanDesc(String beanDesc) {this.beanDesc = beanDesc;}@Overridepublic String toString() {return "ModelBean{" +"beanDesc='" + beanDesc + '\'' +", beanName='" + beanName + '\'' +'}';}}
5.2 封装RequiredModelMBean
javax.management.modelmbean.RequiredModelMBean完成Bean的暴露,必须至少设置需要暴露的Bean对象,以及该Bean对象的ModelInfo信息(描述了将会暴露给代理的构造函数、属性、操作和监听器)
RequiredModelMBean的类结构图:

上图中,将该类常用的方法标注了出来,通过这些方法可以设置需要暴露的Bean对象,设置BeanInfo信息和监听器(NotificationListener),发布通知(下面将详解)等行为。
主要看下面两个方法:javax.management.modelmbean.RequiredModelMBean#setManagedResource(java.lang.Object, java.lang.String)和javax.management.modelmbean.RequiredModelMBean#setModelMBeanInfo(javax.management.modelmbean.ModelMBeanInfo)
/*** 基于ModelBeanInfo创建ModelBean.** 一旦ModelBeanInfo设置给了ModelBean,* 这个ModelBean就可以在 MBeanServer注册** @param mbi The ModelMBeanInfo object to be used* by the ModelMBean.**/public void setModelMBeanInfo(ModelMBeanInfo mbi)throws MBeanException, RuntimeOperationsException {.......}/*** 设置需要暴露属性和方法的对象** @param mr 需要被管理的对象* @param mr_type 资源管理类型.* 只能是"ObjectReference", "Handle", "IOR", "EJBHandle",* or "RMIReference".* 目前只支持"ObjectReference"**/public void setManagedResource(Object mr, String mr_type) {.......}
为了便于RequiredModelMBean对象的创建,设计了一个工具类:
/*** 创建RequireModelMBean工具类** @Author zhru* @Date 2019-10-30*/public class ModelMBeanUtils {/*** 创建{@link RequiredModelMBean}** @param obj* @return*/public static RequiredModelMBean createModelMBean(Object obj) {RequiredModelMBean mBean = null;try {mBean = new RequiredModelMBean();/*** RequiredModelMBean#setManagedResource(String mr, String mr_type)* @param mr Object that is the managed resource* @param mr_type The type of reference for the managed resource.* Can be: "ObjectReference", "Handle", "IOR", "EJBHandle",* or "RMIReference".* In this implementation only "ObjectReference" is supported.*/mBean.setManagedResource(obj, "ObjectReference");// 创建ModelMBeanInfoModelMBeanInfo info = createModelMBeanInfo(obj.getClass());mBean.setModelMBeanInfo(info);} catch (Exception e) {throw new RuntimeException(String.format("创建对象[%s]的RequireModelBean异常:" + e,obj.getClass().getName()));}return mBean;}/*** 创建{@link ModelMBeanInfo}* 也就是对外暴露的方法集合** @param clazz 需要暴露成MBean的类* @return*/private static ModelMBeanInfo createModelMBeanInfo(Class clazz) {ModelMBeanOperationInfo[] modelMBeanOperationInfos = null;ModelMBeanAttributeInfo[] modelMBeanAttributeInfos = null;ModelMBeanConstructorInfo[] modelMBeanConstructorInfos = null;if (clazz == null) {throw new RuntimeException("clazz为空");}// Methods生产ModelMBeanOperationInfoMethod[] methods = clazz.getMethods();List<Method> methodList = Arrays.stream(methods).filter(method ->!Object.class.equals(method.getDeclaringClass())).collect(Collectors.toList());if (methodList != null && methodList.size() > 0) {modelMBeanOperationInfos = new ModelMBeanOperationInfo[methodList.size()];for (int i = 0; i < methodList.size(); i++) {modelMBeanOperationInfos[i] =new ModelMBeanOperationInfo(methodList.get(i).getName() + "方法",methodList.get(i));}}// Fields生成ModelMBeanAttributeInfoField[] fields = clazz.getFields();if (fields != null && fields.length > 0) {modelMBeanAttributeInfos = new ModelMBeanAttributeInfo[fields.length];for (int i = 0; i < fields.length; i++) {modelMBeanAttributeInfos[i] =new ModelMBeanAttributeInfo(fields[i].getName(),fields[i].getType().getName(),fields[i].getName() + "属性",true,true,false);}}// 构造函数生产ModelMBeanConstructorInfoConstructor[] constructors = clazz.getConstructors();if (constructors != null && constructors.length > 0) {modelMBeanConstructorInfos = new ModelMBeanConstructorInfo[constructors.length];for (int i = 0; i < constructors.length; i++) {modelMBeanConstructorInfos[i] =new ModelMBeanConstructorInfo(constructors[i].getName() + "构造方法",constructors[i]);}}// 实例化ModelMBeanInfoModelMBeanInfo info = new ModelMBeanInfoSupport(clazz.getName(),"创建ModelMBeanInfo对象",modelMBeanAttributeInfos,modelMBeanConstructorInfos,modelMBeanOperationInfos,null);return info;}}
5.3 定义Agent层
public static void main(String[] args) throws Exception {//定义MBean ServerMBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();ModelBean bean = new ModelBean();ObjectName objName = new ObjectName("jmxBean:name=modelBean");//创建ModelMBeanRequiredModelMBean mBean = ModelMBeanUtils.createModelMBean(bean);// 注册ModelMBeanmBeanServer.registerMBean(mBean, objName);Thread.sleep(30 * 60 * 1000);}
在Jconsole对属性进行设置和调用,结果如下:

6、JMX MBean通知(Notification)
当MBean的属性改变了的时候,是否能够被外界监听到呢?带着这个疑问,我们去查阅JMX规范,发现规范中提到了通知机制(Notification),JMX的通知机制相当于事件监听机制。当有事件发生时动态的通知监听的社备执行操作指令。
JMX 的通知(Notification)由四部分组成:
1、Notification:继承于java.util.EventObject,相当于一个信息包,封装了需要传递的信息
2、Notification broadcaster:通知广播器,把消息广播出。
3、Notification listener:通知监听器,继承于java.util.EventListener,用于监听广播出来的通知信息。
4、Notification filiter:通知过滤器,只有能够通知的消息才会被发送给Notification listener处理
以StandMBean为例,模拟UserMBean的属性(name)值发生变化时的通知。
修改UserMBean的setName()方法
/*** 继承{@link javax.management.NotificationBroadcasterSupport}* 实现通知的发送操作*/public class User extends NotificationBroadcasterSupport implements UserMBean {private AtomicInteger seq = new AtomicInteger();......@Overridepublic void setName(String name) {String oldName = this.name;this.name = name;// 定义属性变化通知Notification notification = new AttributeChangeNotification(this,seq.getAndIncrement(),System.currentTimeMillis(),String.format("User的[name]属性从%s, 更新到%s", oldName, name),"name",name.getClass().getName(),oldName,name);// 发送通知sendNotification(notification);}}
既然有通知,基于事件监听模式,必然也有对应的通知监听器
/*** 通知监听器* @Author zhru* @Date 2019-10-30**/public class UserNotifyListener implements NotificationListener {/*** 通知处理方法* @param notification 通知* @param handback 得到这个通知后处理回调的对象*/@Overridepublic void handleNotification(Notification notification, Object handback) {System.out.println("得到的通知:" + notification);if (handback instanceof User) {User user = (User) handback;// 事件操作......System.out.println("属性变化后的user对象:" + user);}}
修改MBean Agent层
public class JmxAgent {public static void main(String[] args) throws Exception {//定义MBean ServerMBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();User user = new User();ObjectName userObjectName = new ObjectName("jmxBean:name=jmx");// 设置通知过滤器,只监听Attribuate_Change的类型NotificationFilterSupport notificationFilter =new NotificationFilterSupport();notificationFilter.enableType(ATTRIBUTE_CHANGE);//注册MBeanmBeanServer.registerMBean(user, userObjectName);// 设置监听器mBeanServer.addNotificationListener(userObjectName,new UserNotifyListener(),notificationFilter, user);Thread.sleep(30 * 60 * 1000);}}
在Jconsole中对属性name进行设值,控制台输出:
/usr/lib/jdk1.8.0_171/bin/java得到的通知:javax.management.AttributeChangeNotification[source=jmxBean:name=jmx][type=jmx.attribute.change][message=User的[name]属性从null, 更新到zh]属性变化后的user对象:User{name='zh'}得到的通知:javax.management.AttributeChangeNotification[source=jmxBean:name=jmx][type=jmx.attribute.change][message=User的[name]属性从zh, 更新到zhru]属性变化后的user对象:User{name='zhru'}





