jmx 是什么?应用场景是什么?
jmx 是什么?应用场景是什么?
JMX的简介
JMX(Java Management Extensions)是一个为应用程序植入管理功能的框架 ,从Java5.0开始引入到标准Java技术平台中。JMX是一套标准的代理和服务,实际上,用户可以在任何Java应用程序中使用这些代理和服务实现管理。从官方的文档上来看,他就是一个框架,和JPA、JMS是一样的,和我们平时使用的Spring、Hibernate也没有什么区别。我个人的理解是JMX让程序有被管理的功能,例如你开发一个WEB网站,它是在24小时不间断运行,那么你肯定会对网站进行监控,如每天的UV、PV是多少;又或者在业务高峰的期间,你想对接口进行限流,就必须去修改接口并发的配置值。
它的API在一下两个地方:
java.lang.management:
javax.management.*:包括javax.management.loading、javax.management.modelmbean等;
应用场景:
1、用来管理应用程序的配置项,可以在运行期动态改变配置项的值,而不用妨碍程序的运行,这对与许多可靠性要求较高的应用来说非常方便。
可以通过jconsole等JMX客户端工具动态改变配置项的值。
2、用来对应用程序的运行状态进行监控,比如对一个大型交易处理程序,我们要监控当前有多少交易在排队中,每笔交易的处理时间是多少,平均每处理一笔交易要花多少时间等等。
中间件软件WebLogic的管理页面就是基于JMX开发的,而JBoss则整个系统都基于JMX构架。
对于一些参数的修改,网上有一段描述还是比较形象的:
1、程序初哥一般是写死在程序中,到要改变的时候就去修改代码,然后重新编译发布。
2、程序熟手则配置在文件中(JAVA一般都是properties文件),到要改变的时候只要修改配置文件,但还是必须重启系统,以便读取配置文件里最新的值。
3、程序好手则会写一段代码,把配置值缓存起来,系统在获取的时候,先看看配置文件有没有改动,如有改动则重新从配置里读取,否则从缓存里读取。
4、程序高手则懂得物为我所用,用JMX把需要配置的属性集中在一个类中,然后写一个MBean,再进行相关配置。另外JMX还提供了一个工具页,以方便我们对参数值进行修改。
jmx架构基本概念

从上面的架构图可以看到JMX主要分三层,分别是:
1、设备层(Instrumentation Level)
主要定义了信息模型。在JMX中,各种管理对象以管理构件的形式存在,需要管理时,向MBean服务器进行注册。该层还定义了通知机制以及一些辅助元数据类。
设备层其实就是和被管设备通信的模块,对于上层的管理者来说,Instrumentation 就是设备,具体设备如何通信,是采用SNMP,还是采用ICMP,是MBean的事情。
该层定义了如何实现JMX管理资源的规范。一个JMX管理资源可以是一个Java应用、一个服务或一个设备,它们可以用Java开发,或者至少能用Java进行包装,并且能被置入JMX框架中,从而成为JMX的一个管理构件(Managed Bean),简称MBean。管理构件可以是标准的,也可以是动态的,标准的管理构件遵从JavaBeans构件的设计模式;动态的管理构件遵从特定的接口,提供了更大的灵活性。
在JMX规范中,管理构件定义如下:它是一个能代表管理资源的Java对象,遵从一定的设计模式,还需实现该规范定义的特定的接口。该定义了保证了所有的管理构件以一种标准的方式来表示被管理资源。
管理接口就是被管理资源暴露出的一些信息,通过对这些信息的修改就能控制被管理资源。一个管理构件的管理接口包括:
1) 能被接触的属性值
2) 能够执行的操作
3) 能发出的通知事件
4) 管理构件的构建器
本文着重介绍最基本也是用的最多的Standard Mbean,至于其它的如Dynamic MBean、Model MBean暂时不介绍,
Standard MBean是最简单的MBean,它管理的资源必须定义在接口中,然后MBean必须实现这个接口。它的命名也必须遵循一定的规范,例如我们的MBean为Hello,则接口必须为HelloMBean。
2、代理层(Agent Level)
Agent层 用来管理相应的资源,并且为远端用户提供访问的接口。Agent层构建在设备层之上,并且使用并管理设备层内部描述的组件。Agent层主要定义了各种服务以及通信模型。该层的核心是 MBeanServer,所有的MBean都要向它注册,才能被管理。注册在MBeanServer上的MBean并不直接和远程应用程序进行通信,他们通过 协议适配器(Adapter) 和 连接器(Connector) 进行通信。通常Agent由一个MBeanServer和多个系统服务组成。JMX Agent并不关心它所管理的资源是什么。
3、分布服务层(Distributed Service Level)
分布服务层关心Agent如何被远端用户访问的细节。它定义了一系列用来访问Agent的接口和组件,包括Adapter和Connector的描述。
从零开始玩转JMX(一)——简介和Standard MBean_朱小厮的博客-CSDN博客
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上。
详述
这里采用的是JDK7,JDK7中已经包含了jmx,但是如果用到HtmlAdaptorServer类(后面会看到)还需要用到jmxtools.jar, 可以去这里下载,有两个包:jmx-1_2_1-ri.zip; jmx_remote-1_0_1_03-ri.zip。jmx-1_2_1-ri.zip解压后lib中有jmxri.jar和jmxtools.jar,将jmxtool.jar拷贝出来放入classpath中即可(jmxri.jar在JDK5+已经包被包含了)。
Standard MBean
Standard MBean的设计和实现是最简单的,它们的管理接口通过方法名来描述。Standard MBean的实现依靠一组命名规则,称之为设计模式。这些命名规则定义了属性和操作。
检查Standard MBean接口和应用设计模式的过程被称为内省(Introspection)。JMX代理通过内省来查看每一个注册在MBeanServer上的MBean的方法和超类,看它是否遵从一定设计模式,决定它是否代表了一个MBean,并辨认出它的属性和操作。
Standard MBean是JMX管理构件中最简单的一种,只需要开发一个MBean接口(为了实现Standard MBean,必须遵循一套继承规范。必须每一个MBean定义一个接口,而且这个接口的名字必须是其被管理的资源的对象类的名称后面加上"MBean"),一个实现MBean接口的类,并且把它们注册到MBeanServer中就可以了。
package com.test.jmx;
public interface HelloMBean {
    public String getName();
    public void setName(String name);
    public void printHello();
    public void printHello(String whoName);
}接下来是真正的资源对象,因为命名规范的限制,因此对象名称必须为Hello.
package com.test.jmx;
public class Hello implements HelloMBean {
    private String name;
    @Override
    public String getName() {
        return name;
    }
    @Override
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public void printHello() {
        System.out.println("Hello world, "+ name);
    }
    @Override
    public void printHello(String whoName) {
        System.out.println("Hello, "+whoName);
    }
}接下去创建一个Agent类:
package com.test.jmx;
import com.sun.jdmk.comm.HtmlAdaptorServer;
import javax.management.*;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class HelloAgent {
	public static void main(String[] args) throws MalformedObjectNameException,
			NotCompliantMBeanException, InstanceAlreadyExistsException,
			MBeanRegistrationException, IOException {
        // 下面这种方式不能再JConsole中使用
//    	MBeanServer server = MBeanServerFactory.createMBeanServer();
// 首先建立一个MBeanServer,MBeanServer用来管理我们的MBean,通常是通过MBeanServer来获取我们MBean的信息,间接
// 调用MBean的方法,然后生产我们的资源的一个对象。
        MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
        String domainName = "MyMBean";
        
//为MBean(下面的new Hello())创建ObjectName实例
        ObjectName helloName = new ObjectName(domainName+":name=HelloWorld");
// 将new Hello()这个对象注册到MBeanServer上去
        mbs.registerMBean(new Hello(),helloName);
 
// Distributed Layer, 提供了一个HtmlAdaptor。支持Http访问协议,并且有一个不错的HTML界面,这里的Hello就是用这个作为远端管理的界面
// 事实上HtmlAdaptor是一个简单的HttpServer,它将Http请求转换为JMX Agent的请求
        ObjectName adapterName = new ObjectName(domainName+":name=htmladapter,port=8082");
        HtmlAdaptorServer adapter = new HtmlAdaptorServer();
        adapter.start();
        mbs.registerMBean(adapter,adapterName);
        int rmiPort = 1099;
        Registry registry = LocateRegistry.createRegistry(rmiPort);
        
        JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:"+rmiPort+"/"+domainName);
        JMXConnectorServer jmxConnector = JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbs);
        jmxConnector.start();
    }
}编译运行,在浏览器中输入localhost:8082,这样我们就可以对程序进行管理,如图:

可以看到我们注册的MyMBean域下的"name=HelloWorld",可以点击进去,然后可以修改属性Name和执行2个printHello方法,可以在控制台看到效果。具体不贴图赘述,机智的小伙伴一试就知道怎么玩转了。
上面代码中还通过RMI(JMXServiceURL, JMXConnectorServer )注册URL来提供客户端连接,可以通过JConsole作为客户端来管理MBean. 打开JConsole工具(%JAVA_HOME%/bin/jconsole.exe),如图在远程进程中输入rmi地址“service:jmx:rmi:///jndi/rmi://localhost:1099/MyMBean”:

点击“连接”之后就出现:
这样就可以像HTML一样管理MBean了。
注意上面的代码中:
Registry registry = LocateRegistry.createRegistry(rmiPort);可以在某一特定端口创建名字服务,从而用户无需再手工启动rmiregistry,如果不加入这句代码,就会出现Connection Refused的异常:
Exception in thread "main" java.io.IOException: Cannot bind to URL [rmi://localhost:1099/MyMBean]: javax.naming.ServiceUnavailableException [Root exception is java.rmi.ConnectException: Connection refused to host: localhost; nested exception is: 
	java.net.ConnectException: Connection refused: connect]
	at javax.management.remote.rmi.RMIConnectorServer.newIOException(Unknown Source)
	at javax.management.remote.rmi.RMIConnectorServer.start(Unknown Source)
	at com.test.jmx.HelloAgent.main(HelloAgent.java:44)
Caused by: javax.naming.ServiceUnavailableException [Root exception is java.rmi.ConnectException: Connection refused to host: localhost; nested exception is: 
	java.net.ConnectException: Connection refused: connect]
	at com.sun.jndi.rmi.registry.RegistryContext.bind(Unknown Source)
	at com.sun.jndi.toolkit.url.GenericURLContext.bind(Unknown Source)
	at javax.naming.InitialContext.bind(Unknown Source)
	at javax.management.remote.rmi.RMIConnectorServer.bind(Unknown Source)
	... 2 more
Caused by: java.rmi.ConnectException: Connection refused to host: localhost; nested exception is: 
	java.net.ConnectException: Connection refused: connect
	at sun.rmi.transport.tcp.TCPEndpoint.newSocket(Unknown Source)
	at sun.rmi.transport.tcp.TCPChannel.createConnection(Unknown Source)
	at sun.rmi.transport.tcp.TCPChannel.newConnection(Unknown Source)
	at sun.rmi.server.UnicastRef.newCall(Unknown Source)
	at sun.rmi.registry.RegistryImpl_Stub.bind(Unknown Source)
	... 6 more
Caused by: java.net.ConnectException: Connection refused: connect
	at java.net.DualStackPlainSocketImpl.connect0(Native Method)
	at java.net.DualStackPlainSocketImpl.socketConnect(Unknown Source)
	at java.net.AbstractPlainSocketImpl.doConnect(Unknown Source)
	at java.net.AbstractPlainSocketImpl.connectToAddress(Unknown Source)
	at java.net.AbstractPlainSocketImpl.connect(Unknown Source)
	at java.net.PlainSocketImpl.connect(Unknown Source)
	at java.net.SocksSocketImpl.connect(Unknown Source)
	at java.net.Socket.connect(Unknown Source)
	at java.net.Socket.connect(Unknown Source)
	at java.net.Socket.<init>(Unknown Source)
	at java.net.Socket.<init>(Unknown Source)
	at sun.rmi.transport.proxy.RMIDirectSocketFactory.createSocket(Unknown Source)
	at sun.rmi.transport.proxy.RMIMasterSocketFactory.createSocket(Unknown Source)
	... 11 more当然,这也就其他的解决办法:运行 %JAVA_HOME%/bin/rmiregistry.exe 1099就有和那行代码一样的效果。
我们不仅可以通过JConsole作为客户端采用rmi的方式来进行管理,我们同样可以采用自定义程序作为客户端来连接JMXConnectorServer管理MBean.
package com.test.jmx;
import java.io.IOException;
import java.util.Iterator;
import java.util.Set;
import javax.management.Attribute;
import javax.management.AttributeNotFoundException;
import javax.management.InstanceNotFoundException;
import javax.management.IntrospectionException;
import javax.management.InvalidAttributeValueException;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanServerConnection;
import javax.management.MBeanServerInvocationHandler;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.ReflectionException;
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,
			MalformedObjectNameException, InstanceNotFoundException,
			AttributeNotFoundException, InvalidAttributeValueException,
			MBeanException, ReflectionException, IntrospectionException {
		String domainName = "MyMBean";
		int rmiPort = 1099;
		JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:"+rmiPort+"/"+domainName);
		// 可以类比HelloAgent.java中的那句:
		// JMXConnectorServer jmxConnector = JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbs);
		JMXConnector jmxc = JMXConnectorFactory.connect(url);
		MBeanServerConnection mbsc = jmxc.getMBeanServerConnection();
		
		//print domains
		System.out.println("Domains:------------------");
		String domains[] = mbsc.getDomains();
		for(int i=0;i<domains.length;i++){
			System.out.println("\tDomain["+i+"] = "+domains[i]);
		}
		//MBean count
		System.out.println("MBean count = "+mbsc.getMBeanCount());
		//process attribute
		ObjectName mBeanName = new ObjectName(domainName+":name=HelloWorld");
		mbsc.setAttribute(mBeanName, new Attribute("Name","zzh"));//注意这里是Name而不是name
		System.out.println("Name = "+mbsc.getAttribute(mBeanName, "Name"));
		
		//接下去是执行Hello中的printHello方法,分别通过代理和rmi的方式执行
		//via proxy
		HelloMBean proxy = MBeanServerInvocationHandler.newProxyInstance(mbsc, mBeanName, HelloMBean.class, false);
		proxy.printHello();
		proxy.printHello("jizhi boy");
		//via rmi
		mbsc.invoke(mBeanName, "printHello", null, null);
		mbsc.invoke(mBeanName, "printHello", new String[]{"jizhi gril"}, new String[]{String.class.getName()});
		
		//get mbean information
		MBeanInfo info = mbsc.getMBeanInfo(mBeanName);
		System.out.println("Hello Class: "+info.getClassName());
		for(int i=0;i<info.getAttributes().length;i++){
			System.out.println("Hello Attribute:"+info.getAttributes()[i].getName());
		}
		for(int i=0;i<info.getOperations().length;i++){
			System.out.println("Hello Operation:"+info.getOperations()[i].getName());
		}
		
		//ObjectName of MBean
		System.out.println("all ObjectName:--------------");
		Set<ObjectInstance> set = mbsc.queryMBeans(null, null);
		for(Iterator<ObjectInstance> it = set.iterator();it.hasNext();){
			ObjectInstance oi = it.next();
			System.out.println("\t"+oi.getObjectName());
		}
		jmxc.close();
	}
}运行结果:
Domains:------------------
	Domain[0] = MyMBean
	Domain[1] = java.nio
	Domain[2] = JMImplementation
	Domain[3] = com.sun.management
	Domain[4] = java.lang
	Domain[5] = java.util.logging
MBean count = 21
Name = zzh
Hello Class: com.test.jmx.Hello
Hello Attribute:Name
Hello Operation:printHello
Hello Operation:printHello
all ObjectName:--------------
	java.lang:type=OperatingSystem
	java.lang:type=Compilation
	java.lang:type=MemoryPool,name=PS Old Gen
	java.lang:type=Memory
	JMImplementation:type=MBeanServerDelegate
	java.lang:type=MemoryPool,name=PS Perm Gen
	java.lang:type=Runtime
	MyMBean:name=htmladapter,port=8082
	java.nio:type=BufferPool,name=direct
	java.lang:type=GarbageCollector,name=PS MarkSweep
	java.nio:type=BufferPool,name=mapped
	java.lang:type=Threading
	com.sun.management:type=HotSpotDiagnostic
	java.lang:type=GarbageCollector,name=PS Scavenge
	MyMBean:name=HelloWorld
	java.lang:type=ClassLoading
	java.lang:type=MemoryPool,name=PS Survivor Space
	java.lang:type=MemoryManager,name=CodeCacheManager
	java.lang:type=MemoryPool,name=Code Cache
	java.util.logging:type=Logging
	java.lang:type=MemoryPool,name=PS Eden Space这是客户端的运行结果,由于在客户端调用了服务端的方法,可以在服务端看到打印结果:
Hello world, zzh
Hello, jizhi boy
Hello world, zzh
Hello, jizhi gril上面代码涉及到辅助原数据的概念:辅助元数据类用来描述管理构件。辅助元数据类不仅被用来内省标准管理构件,也被动态管理构件用来进行自我描述。这些类根据属性、操作、构建器和通告描述了管理接口。JMX代理通过这些元数据类管理所有管理构件,而不管这些管理构件的类型。部分辅助元类如下:
- MBeanInfo–包含了属性、操作、构建器和通知的信息。
- MBeanFeatureInfo–为下面类的超类。
- MBeanAttributeInfo–用来描述管理构件中的属性。
- MBeanConstructorInfo–用来描述管理构件中的构建器。
- MBeanOperationInfo–用来描述管理构件中的操作。
- MBeanParameterInfo–用来描述管理构件操作或构建器的参数。
- MBeanNotificationInfo–用来描述管理构件发出的通知。
有关Notication, Dynamic MBean, Model MBean以及Apache Common Modeler由于篇幅限制将后面文章中讲述。
Notification
一个MBean提供的管理接口允许代理对其管理资源进行控制和配置。然而,对管理复杂的分布式系统来说,这些接口知识提供了一部分功能。通常,管理应用程序需要对状态变化或者当特别情况发生变化时作出反映。Notification起到了MBean之间的沟通桥梁的作用。JMX Notification模型和Java Event模型类似,将一些重要的信息,状态的转变,数据的变更传递给Notification Listener,以便资源的管理。
通知模型仅仅涉及了在同一个JMX代理中的管理构件之间的事件传播。JMX通知模型依靠以下几个部分:
- Notification,一个通用的事件类型,该类标识事件的类型,可以被直接使用,也可以根据传递的事件的需要而被扩展。
- NotificationListener接口,接受通知的对象需实现此接口。
- NotificationFilter接口,作为通知过滤器的对象需实现此接口,为通知监听者提供了一个过滤通知的过滤器。
- NotificationBroadcaster接口,通知发送者需实现此接口,该接口允许希望得到通知的监听者注册。
发送一个通用类型的通知,任何一个监听者都会得到该通知。因此,监听者需提供过滤器来选择所需要接受的通知。任何类型的MBean,标准的或动态的,都可以作为一个通知发送者,也可以作为一个通知监听者,或两者都是。
这里采用一个案例来说明,前面的Hello中有一个printHello(String whoName)方法,意思根据碰到的是谁来打招呼,比如:
小厮从对面走来,说:“hi”。我们回之以礼,说:“Hello, Xiaosi”.
首先这里需要小厮先说一个“hi”(相应一个操作方法),然后他说的话封装成了声波(Notification的消息包)传递出去。然后我们要给Hello类装上一个监听器(NotificationListener),这个监听器将捕捉到小厮的Notification消息包,然后回复说:“Hello, Xiaosi”.
引入新的类XiaoSi.java和XiaosiMBean:
package com.test.jmx.notification;
import javax.management.Notification;
import javax.management.NotificationBroadcasterSupport;
public class XiaoSi extends NotificationBroadcasterSupport implements XiaoSiMBean {
    private int seq = 0;
    /*
    * 必需继承NotificationBroadcasterSupport
    * 此类只有一个hi方法,方法只有两句:创建一个Notification消息包,然后将包发出去
    * 如果你还要在消息包上附加其他数据,Notification还有一个setUserData方法可供使用
     */
    @Override
    public void hi() {
        Notification n = new Notification(//创建一个信息包
                "xiaosi.hi",//给这个Notification起个名称
                this,//由谁发出的Notification
                ++seq,//一系列通知中的序列号,可以设置任意数值
                System.currentTimeMillis(),//发出时间
                "Xiaosi"//发出信息的消息文本
        );
        sendNotification(n);
    }
}package com.test.jmx.notification;
public interface XiaoSiMBean {
    public void hi();
}接下去创建Hello类的Listener(HelloListener.java),用于监听Notification的消息包并处理。
package com.test.jmx.notification;
import com.test.jmx.Hello;
import javax.management.Notification;
import javax.management.NotificationListener;
public class HelloListener implements NotificationListener {
    @Override
    public void handleNotification(Notification notification, Object handback) {
    	System.out.println("----------HelloListener-Begin------------");
        System.out.println("\ttype = "+ notification.getType());
        System.out.println("\tsource = "+notification.getSource());
        System.out.println("\tseq = "+notification.getSequenceNumber());
        System.out.println("\tsend time = "+notification.getTimeStamp());
        System.out.println("\tmessage="+notification.getMessage());
        System.out.println("----------HelloListener-End------------");
        if (handback != null) {
            if (handback instanceof Hello) {
                Hello hello = (Hello)handback;
                hello.printHello(notification.getMessage());
            }
        }
    }
}修改HelloAgent的代码:
package com.test.jmx.notification;
import com.sun.jdmk.comm.HtmlAdaptorServer;
import com.test.jmx.Hello;
import javax.management.*;
import java.lang.management.ManagementFactory;
public class HelloAgent {
    public static void main(String[] args) throws MalformedObjectNameException, NotCompliantMBeanException, 
InstanceAlreadyExistsException, MBeanRegistrationException {
        MBeanServer server = ManagementFactory.getPlatformMBeanServer();
        ObjectName helloName = new ObjectName("MyMBean:name=HelloWorld");
        Hello hello = new Hello();
        server.registerMBean(hello,helloName);
        ObjectName adapterName = new ObjectName("MyBean:name=htmladapter,port=8082");
        HtmlAdaptorServer adapter = new HtmlAdaptorServer();
        server.registerMBean(adapter,adapterName);
        XiaoSi xs = new XiaoSi();
        server.registerMBean(xs,new ObjectName("MyMBean:name=xiaosi"));
        xs.addNotificationListener(new HelloListener(),null,hello);
        adapter.start();
    }
}运行HelloAgent,然后在浏览器中输入localhost:8080, 点击“name=xiaosi”:

进入页面:

点击“hi”按钮,此时控制台打印:
----------HelloListener-Begin------------
	type = xiaosi.hi
	source = com.test.jmx.notification.XiaoSi@12f7757c
	seq = 1
	send time = 1476184927551
	message=Xiaosi
----------HelloListener-End------------
Hello, XiaosiModel MBean
相对于Standard MBean,Model MBean更加灵活。如果我们不能修改已有的Java类,那么使用Model MBean是不错的选择。
Model MBean也是一种专门化的动态管理构件。它是预制的、通用的和动态的 MBean 类,已经包含了所有必要缺省行为的实现,并允许在运行时添加或覆盖需要定制的那些实现。JMX规范规定该类必须实现为javax.management.modelmbean.RequiredModelMBean,管理者要做的就是实例化该类,并配置该构件的默认行为并注册到JMX代理中,即可实现对资源的管理。JMX代理通过获得一个ModelMBeanInfo对象来获取管理接口。
模型管理构件具有以下新的特点:
- 持久性。定义了持久机制,可以利用Java的序列化或JDBC来存储模型MBean的状态。 就是要保存到硬盘上。
- 通知和日志功能。能记录每一个发出的通知,并能自动发出属性变化通知。
- 属性值缓存。具有缓存属性值的能力。
还是沿用前面的代码,但是这里就不需要类似HelloMBean这样的接口了。
package com.test.jmx.modelBean;
public class Hello { //注意这里没有implements任何东西
    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);
    }
}但是需要自己编写一个产生Model MBean的工具类。Model MBean使用JDK提供的RequiredModelMBean,指定基本的Bean(Hello),创建好需要的ModelMBeanInfo就可以了。
package com.test.jmx.modelBean;
import javax.management.*;
import javax.management.modelmbean.*;
/**
 * Created by hidden on 2016/10/9.
 */
public class ModelMBeanUtils {
    private static final boolean READABLE = true;
    private static final boolean WRITABLE = true;
    private static final boolean BOOLEAN = true;
    private static final String STRING_CLASS = "java.lang.String";
    public static RequiredModelMBean createModelerMBean() {
        RequiredModelMBean model = null;
        try {
            model = new RequiredModelMBean();
            model.setManagedResource(new Hello(), "ObjectReference");
            ModelMBeanInfo info = createModelMBeanInfo();
            model.setModelMBeanInfo(info);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return model;
    }
    private static ModelMBeanInfo createModelMBeanInfo() {
        //
        //                        属性                                        //
        //
        // 构造name属性信息
        Descriptor portAttrDesc = new DescriptorSupport();
        portAttrDesc.setField("name", "Name");
        portAttrDesc.setField("descriptorType", "attribute");
        portAttrDesc.setField("displayName", "Name");
        portAttrDesc.setField("getMethod", "getName");
        portAttrDesc.setField("setMethod", "setName");
        ModelMBeanAttributeInfo nameAttrInfo = new ModelMBeanAttributeInfo(//
                "Name", // 属性名
                STRING_CLASS, //属性类型
                "people name", // 描述文字
                READABLE, WRITABLE, !BOOLEAN, // 读写
                portAttrDesc // 属性描述
        );
        //
        //                        方法                                        //
        //
        // 构造 getName操作描述符信息
        Descriptor getStateDesc = new DescriptorSupport(new String[] {
                "name=getName",
                "descriptorType=operation",
                "class=com.test.jmx.modelBean.Hello",
                "role=operation"
        });
        ModelMBeanOperationInfo getName = new ModelMBeanOperationInfo(//
                "getName", //
                "get name attribute", //
                null, //
                "java.lang.String", //
                MBeanOperationInfo.ACTION, //
                getStateDesc //
        );
        // 构造 setName操作描述符信息
        Descriptor setStateDesc = new DescriptorSupport(new String[] {
                "name=setName", "descriptorType=operation", "class=com.test.jmx.modelBean.Hello",
                "role=operation" });
        MBeanParameterInfo[] setStateParms = new MBeanParameterInfo[] { (new MBeanParameterInfo(
                "name", "java.lang.String", "new name value")) };
        ModelMBeanOperationInfo setName = new ModelMBeanOperationInfo(//
                "setName", //
                "set name attribute", //
                setStateParms, //
                "void", //
                MBeanOperationInfo.ACTION, //
                setStateDesc //
        );
        //构造 printHello()操作的信息
        ModelMBeanOperationInfo print1Info = new ModelMBeanOperationInfo(//
                "printHello", //
                null, //
                null, //
                "void", //
                MBeanOperationInfo.INFO, //
                null //
        );
        // 构造printHello(String whoName)操作信息
        ModelMBeanOperationInfo print2Info;
        MBeanParameterInfo[] param2 = new MBeanParameterInfo[1];
        param2[0] = new MBeanParameterInfo("whoName", STRING_CLASS, "say hello to who");
        print2Info = new ModelMBeanOperationInfo(//
                "printHello", //
                null,//
                param2,//
                "void", //
                MBeanOperationInfo.INFO, //
                null//
        );
        //
        //                        最后总合                                    //
        //
        // create ModelMBeanInfo
        ModelMBeanInfo mbeanInfo = new ModelMBeanInfoSupport(//
                RequiredModelMBean.class.getName(), // MBean类
                null, // 描述文字
                new ModelMBeanAttributeInfo[] { // 所有的属性信息(数组)
                        nameAttrInfo },//只有一个属性
                null, // 所有的构造函数信息
                new ModelMBeanOperationInfo[] { // 所有的操作信息(数组)
                        getName,
                        setName,
                        print1Info,
                        print2Info },//
                null, // 所有的通知信息(本例无)
                null//MBean描述
        );
        return mbeanInfo;
    }
}这里着重说明下ModelMBeanInfo接口
编写Model MBean的最大挑战是告诉Model MBean对象托管资源的那些熟悉和方法可以暴露给代理。ModelMBeanInfo对象描述了将会暴露给代理的构造函数、属性、操作甚至是监听器。
创建了ModelMBeanInfo对象后,需要将其与ModelMBean对象关联。目前有两种方式可以做到这一点:
- 传入ModelMBeanInfo对象给RequiredModelMBean对象的构造函数。
- 调用RequiredModelMBean对象的setModelMBeanInfo方法。
创建了ModelMBean对象后(RequiredModelMBean implements ModelMBean),需要调用ModelMBean接口的setManagedResource()方法将其与托管资源关联,该方法如下:
public void setManagedResource(Object managedResource, String managedResourceType) ;managedResourceType的值可以为ObjectReference, Handle, IOR, EJBHandle或RMIReference,但当前只支持ObjectReference.
在注册时没有什么特别之处,只是需要注意下通过工具类获得MBean即可
package com.test.jmx.modelBean;
import com.sun.jdmk.comm.HtmlAdaptorServer;
import javax.management.*;
import javax.management.modelmbean.RequiredModelMBean;
import java.lang.management.ManagementFactory;
public class HelloAgent {
    public static void main(String[] args) throws MalformedObjectNameException, NotCompliantMBeanException, InstanceAlreadyExistsException, MBeanRegistrationException {
        MBeanServer server = ManagementFactory.getPlatformMBeanServer();
        ObjectName helloName = new ObjectName("MyMBean:name=HelloWorld");
        //Hello hello = new Hello();
        RequiredModelMBean hello = ModelMBeanUtils.createModelerMBean();
        server.registerMBean(hello, helloName);
        ObjectName adapterName = new ObjectName("MyMBean:name=htmladapter,port=8082");
        HtmlAdaptorServer adapter = new HtmlAdaptorServer();
        server.registerMBean(adapter, adapterName);
        adapter.start();
    }
}运行结果:略。
Model MBean可以动态配置。试想一下这个应用场景:由于安全或其他原因,系统要把某个MBean公开的可管理方法隐藏起来。这时,如果你是用标准MBean,这需要修改接口类,然后重新编译发布;如果用 Apache commons-modeler(如果不想总要维护MBean这个借口,那么可以使用Apache的commons-modeler来辅助开发MBean,所有的MBean都装配在XML文件中)来写的模型MBean,则只需要修改XML文件就行了,不需要重新编译发布(可能要重启一下系统)。这就是Model Mbean 优势之所在了。
Apache Commons Modeler
前面的Model MBean的创建方式看上去特别复杂,一个简单功能的类ModelMBeanUtils 写了很多代码,那有木有简单点的方式呢,答案是肯定的,这里就引出了Apache Commons Modeler(使用这个需要在classpath中导入commons-modeler-2.0.1.jar以及modeler的依赖项目commons-logging-1.1.3.jar,下载地址:http://commons.apache.org/proper/commons-modeler/download_modeler.cgi),使用Apache的Moleler库创建Model MBean最大帮助是,我们不需要再写复杂的代码来创建ModelMBeanInfo对象了。只需要一个MBean描述符(实际上就是一个xml配置文件,Apache Commons Modeler将ModelMBeanUtils 复杂的创建过程转移到xml中来配置,然后自身模块创建对象代替ModelMBeanUtils 的功能,简化用户的操作)来对Model MBean进行描述,就可以轻松的创建Model MBean.
下面来讲前面的Hello.java和HelloAgent.java的例子采用Apache Commons Modele进行改造。
首先还是Hello.java,和Model MBean中的一样,没有implements任何接口。
package com.test.jmx.modeler;
public class Hello{
    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);
    }
}接下去就是最关键的描述文件(mbeans-descriptors.xml)了:
<?xml version="1.0" encoding="UTF-8" ?>
<mbeans-descriptors>
    <mbean name="Hello" description="the hello bean" domain="MyMBean" group="helloGroup" type="com.test.jmx.modeler.Hello">
        <attribute name="name" description="a name attribute" type="java.lang.String" writeable="true"/>
        <operation name="printHello" description="public void printHello()" impact="ACTION" returnType="void"/>
        <operation name="printHello" description="public void printHello(String whoName)" impact="ACTION" returnType="void">
        	<parameter name="whoName" description="method parameter of printHello" type="java.lang.String"></parameter> 
        </operation>
    </mbean>
</mbeans-descriptors>描述文件的名字可以随意,最主要的是要和下面的HelloAgent.java对应起来。
通过这个xml文件的定义就描述了Model MBean所需要的metadata信息和一个基本的ModelMBean实现。
关于这个xml文件有几个需要说明的地方:
<mbean>的属性classname,name,type:
- name属性是每个MBean被Registry对象注册的对象名
- type属性是真正被管理资源的全面(包括包名)
- classname属性是用户扩展的用于实现代理功能的Model MBean的全名,如果不提供Modeler会使用BaseModelMBean;如果提供了代理的ModelMBean对象,在使用时可以使用如下的代码样本访问他所代理的资源对象。
其余的标签就比较好理解了。综述:上面所示代码声明了一个Model MBean, 唯一标示是“Hello”,该MBean负责管理的对象是com.test.jmx.modeler.Hello的实例。域是MyMBean。这个MBean暴露了一个属性name和两个方法printHello()和printHello(String whoName).
下面是新的HelloAgent.java的代码:
package com.test.jmx.modeler;
import com.sun.jdmk.comm.HtmlAdaptorServer;
import org.apache.commons.modeler.ManagedBean;
import org.apache.commons.modeler.Registry;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.modelmbean.ModelMBean;
import java.io.InputStream;
public class HelloAgent  {
    public static void main(String[] args) throws Exception {
// 需要将xml信息读入到Registry对象中
        Registry registry = Registry.getRegistry(null,null);
        InputStream stream = HelloAgent.class.getResourceAsStream("mbeans-descriptors.xml");
        registry.loadMetadata(stream);
        MBeanServer server = registry.getMBeanServer();
// 之前是:MBeanServer server = ManagementFactory.getPlatformMBeanServer();
        ManagedBean managed = registry.findManagedBean("Hello");
        ObjectName helloName = new ObjectName(managed.getDomain()+":name=HelloWorld");
// 以前是Hello hello = new Hello(); 为什么要多个createMBean?因为现在的写法没有写MBean,所以才要动态生成一个,以前就直接
// 把new hello()注册到MBeanServer就可以了,server会自动找到它的HelloMBean接口文件。
        ModelMBean hello = managed.createMBean(new Hello());
        server.registerMBean(hello,helloName);
        ObjectName adapterName = new ObjectName(managed.getDomain()+":name = htmladapter,port=8082");
        HtmlAdaptorServer adapter = new HtmlAdaptorServer();
        server.registerMBean(adapter,adapterName);
        adapter.start();
    }
}注意这里的Registry是指org.apache.commons.modeler.Registry,因为JMX自身也有一个Registry(java.rmi.registry.Registry)。通过Modeler组件提供的Registry对象,可以很方便的完成MBeanServer的创建。
运行效果和之前的一样,这里就不赘述了,有兴趣的小伙伴可以试一下。
Dynamic MBean
四种类型的MBean,前面所讲的都是常用的,现在还剩两种Open MBean就不讲述了,这里简单记录下Dynamic MBean。
Dynamic MBean不需要自定义MBean接口,只需要实现DynamicMBean接口即可,Dynamic MBean没有任何明显些在代码里的属性和方法,所有的属性和方法都是通过反射结合JMX提供的辅助元数据从而动态生成。
下面的代码中首先定义了一个属性name和一个方法print,之后在管理界面(localhost:8082)中点击print之后生成一个print1的方法。
Dynamic MBean的代码如下:
package com.test.jmx.DynamicMBean;
import javax.management.*;
import java.lang.reflect.Constructor;
import java.util.Iterator;
/**
 * Created by hidden on 2016/10/9.
 */
public class HelloDynamic implements DynamicMBean {
    private String name;
    private MBeanInfo mBeanInfo = null;
    private String className;
    private String description;
    private MBeanAttributeInfo[] attributes;
    private MBeanConstructorInfo[] constructors;
    private MBeanOperationInfo[] operations;
    MBeanNotificationInfo[] mBeanNotificationInfoArray;
    private void init(){
        className = this.getClass().getName();
        description = "Simple implementation of a dynamic MBean.";
        attributes = new MBeanAttributeInfo[1];
        constructors = new MBeanConstructorInfo[1];
        operations = new MBeanOperationInfo[1];
        mBeanNotificationInfoArray = new MBeanNotificationInfo[0];
    }
    private void buildDynamicMBean(){
        Constructor[] thisConstructors = this.getClass().getConstructors();
        constructors[0] = new MBeanConstructorInfo("HelloDynamic(): Constructs a HelloDynamic Object",thisConstructors[0]);
        attributes[0] = new MBeanAttributeInfo("name","java.lang.String","Name:name string.",true,true,false);
        MBeanParameterInfo[] params = null;
        operations[0] = new MBeanOperationInfo("print","print():print the name",params,"void",MBeanOperationInfo.INFO);
        mBeanInfo = new MBeanInfo(className,description,attributes,constructors,operations,mBeanNotificationInfoArray);
    }
    public HelloDynamic(){
        init();
        buildDynamicMBean();
    }
    private void dynamicAddOperation(){
        init();
        operations = new MBeanOperationInfo[2];
        buildDynamicMBean();
        operations[1] = new MBeanOperationInfo("print1","print1():print the name",null,"void",MBeanOperationInfo.INFO);
        mBeanInfo = new MBeanInfo(className,description,attributes,constructors,operations,mBeanNotificationInfoArray);
    }
    @Override
    public Object getAttribute(String attribute) throws AttributeNotFoundException, MBeanException, ReflectionException {
        if (attribute == null) {
            return null;
        }
        if (attribute.equals("Name")) {
            return name;
        }
        return null;
    }
    @Override
    public void setAttribute(Attribute attribute) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException {
        if (attribute == null) {
            return;
        }
        String Name = attribute.getName();
        Object value = attribute.getValue();
        try {
            if (Name.equals("Name")) {
                if (value == null) {
                    name=null;
                } else if (Class.forName("java.lang.String").isAssignableFrom(value.getClass())) {
                    name = (String) name;
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    @Override
    public AttributeList getAttributes(String[] attributes) {
        if (attributes == null) {
            return null;
        }
        AttributeList resultList = new AttributeList();
//        if (attributes.length == 0) {
//            return resultList;
//        }
        for(int i=0;i<attributes.length;i++){
            try {
                Object value = getAttribute(attributes[i]);
                resultList.add(new Attribute(attributes[i],value));
            } catch (AttributeNotFoundException e) {
                e.printStackTrace();
            } catch (MBeanException e) {
                e.printStackTrace();
            } catch (ReflectionException e) {
                e.printStackTrace();
            }
        }
        return resultList;
    }
    @Override
    public AttributeList setAttributes(AttributeList attributes) {
        if (attributes == null) {
            return null;
        }
        AttributeList resultList = new AttributeList();
        if(attributes.isEmpty()){
            return resultList;
        }
        for(Iterator i = attributes.iterator();i.hasNext();){
            Attribute attr = (Attribute) i.next();
            try {
                setAttribute(attr);
                String name = attr.getName();
                Object value = getAttribute(name);
                resultList.add(new Attribute(name,value));
            } catch (AttributeNotFoundException e) {
                e.printStackTrace();
            } catch (InvalidAttributeValueException e) {
                e.printStackTrace();
            } catch (MBeanException e) {
                e.printStackTrace();
            } catch (ReflectionException e) {
                e.printStackTrace();
            }
        }
        return resultList;
    }
    @Override
    public Object invoke(String actionName, Object[] params, String[] signature) throws MBeanException, ReflectionException {
        if(actionName.equals("print")){
            System.out.println("Hello, "+name+",this is HelloDynamic!");
            dynamicAddOperation();
            return null;
        }else if(actionName.equals("print1")){
            System.out.println("这是动态增加的一个方法print1");
            return null;
        }else {
            throw new ReflectionException(new NoSuchMethodException(actionName),"Cannot find the operation "+actionName+" in "+className);
        }
    }
    @Override
    public MBeanInfo getMBeanInfo() {
        return mBeanInfo;
    }
}通过Agent调用Dynamic MBean:
package com.test.jmx.DynamicMBean;
import com.sun.jdmk.comm.HtmlAdaptorServer;
import javax.management.*;
import java.lang.management.ManagementFactory;
import java.util.concurrent.TimeUnit;
public class HelloAgent {
    public static void main(String[] args) throws MalformedObjectNameException, NotCompliantMBeanException, InstanceAlreadyExistsException, MBeanRegistrationException {
        
    	MBeanServer server = ManagementFactory.getPlatformMBeanServer();
        ObjectName helloName = new ObjectName("MyMBean:name=helloDynamic");
        HelloDynamic hello = new HelloDynamic();
        server.registerMBean(hello,helloName);
        ObjectName adapterName = new ObjectName("MyMBean:name=htmladapter");
        HtmlAdaptorServer adapter = new HtmlAdaptorServer();
        server.registerMBean(adapter,adapterName);
        adapter.start();
    }
}运行效果图如下:

运行结果:
Hello, null,this is HelloDynamic!
这是动态增加的一个方法print1
面试官问我 JMX 了解不,我说:什么? - 知乎 (zhihu.com)
如果你之前没接触过,一定会出现疑问三连击,"这是个什么玩意儿?干嘛的?有啥用?"。
虽然可能不知道它,但是或多或少你肯定都接触过。
比如你如果用 Spring Boot,那你用过 Spring Boot Actuator 吧,它就用到了 JMX 。比如你用过 JConsole 或者 VisualVM 吧,它们也用到了 JMX。
先上图吧,利用 JMX 你可以做出这样的 JVM 监控出来。


是不是挺有意思的。
上面是我自己实现的界面,下图是 JConsole 和 VisualVM 的界面。

它们是如何用到了 JMX 呢,下面一步一步说。
什么是 JMX
JMX 全称为 Java Management Extensions,翻译过来就是 Java 管理扩展,用来管理和监测 Java 程序。最常用到的就是对于 JVM 的监测和管理,比如 JVM 内存、CPU 使用率、线程数、垃圾收集情况等等。另外,还可以用作日志级别的动态修改,比如 log4j 就支持 JMX 方式动态修改线上服务的日志级别。最主要的还是被用来做各种监控工具,比如文章开头提到的 Spring Boot Actuator、JConsole、VisualVM 等。
JMX 既是 Java 管理系统的一个标准,一个规范,也是一个接口,一个框架。有标准、有规范是为了让开发者可以定制开发自己的扩展功能,而且作为一个框架来讲,JDK 已经帮我们实现了常用的功能,尤其是对 JVM 的监控和管理。

上图是 JMX 架构的简单示意图,简单理解就是管理系统通过 JMX 可以管理各种资源。
管理系统可以理解为管理客户端,比如上面说的 JConsole、VisualVM ,还有 Metrics 这个非常知名的 Java 监控工具包,或者你自己通过 JMX 接口实现的客户端等。 各种资源比如系统配置、JVM 指标等,或者你自己的项目中特定的资源等。
重点来了,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包里面。可以进去看一看,进去就能看到好多似曾相识的身影,比如 Memory 相关的、Thread 相关的,这不就是我们在 VisualVM 上看到的内容吗,没错,数据就是从这里来的。
说实话,并不用太关心这几种 MBean 的区别,但还是简单介绍下。
Standard MBean 就是普通的 Java Bean 没有区别,它也是 JMX 中最简单、使用最多的一种。主要在java.lang.management包里面。
Dynamic MBean 其实是一种妥协的产物,由于已经存在一些这种 MBean,而将其改造成标准 MBean 比较费力而且不切实际,所以就有了动态 MBean。Dynamic MBean 的接口在 javax.management.DynamicMBean这里,里面定义一些接口方法,比如动态获取属性、设置属性等。
另外还有两类 MBean:Open MBean 和 Model MBean,实际上它们也都是动态 MBean。
Open MBean 与其它动态 MBean 的唯一区别在于,前者对其公开接口的参数和返回值有所限制 —— 只能是基本类型或者 javax.management.openmbean包内的 ArrayType、CompositeType、TarbularType 等类型。这主要是考虑到管理系统的分布,很可能远端管理系统甚至 MBServer 层都不具有 MBean 接口中特殊的类。
MBeanServer
MBeanServer 是负责管理 MBean 的,一般一个 JVM 只有一个 MBeanServer,所有的 MBean 都要注册到 MBeanServer 上,并通过 MBeanServer 对外提供服务。一般用 ManagementFactory.getPlatformMBeanServer()方法获取当前 JVM 内的 MBeanServer。
适配器和连接器
写好的 MBean 注册到 MBeanServer 上之后,功能已经具备了。适配器和连接器就是将这些功能开放出来的方式。 比如 HTTP协议适配器,就是将功能以 HTTP 协议开放出去,这样我们就可以在浏览器使用了。但是 JDK 只是提供了适配器的实现标准,并没有具体的实现,比较常用的是 HtmlAdaptorServer,需要 jmxtools.jar 包的支持。
连接器是各种客户端最常用的,JDK 提供的默认连接器是 RMI 连接器,JConsole、VisualVM 都是使用它。
实现并使用一个 MBean
虽然 Java 提供了实现 MBean 的标准和规则,但平时我们几乎不需要开发 MBean。绝大多数的开发者接触到的也仅仅是使用 JDK 或者第三方定义好的 MBean,即便是第三方有实现 MBean,也是非常少的。我们知道的有 Tomcat 和 Spring Boot Actuator。
定义 MBean 接口 和实体类
// 接口
public interface UserMBean {
    String getName();
    String getPassword();
    String getPhone();
    void say();
}
public class User implements UserMBean {
    @Override
    public String getName() {
        return "风筝";
    }
    @Override
    public String getPassword() {
        return "密码不可见";
    }
    @Override
    public String getPhone() {
        return "18900000000";
    }
    @Override
    public void say() {
        System.out.println("Hello JMX");
    }
}实体类需要继承 MBean 接口类,而接口类的命名规则是 「实体类名+MBean」,这是固定的规则。
将定义好的 MBean 注册到 MBeanServer
public static void main(String[] args) throws Exception {
        MBeanServer server = ManagementFactory.getPlatformMBeanServer();
        ObjectName userName = new ObjectName("FengZheng:type=customer,name=customerUserBean");
        server.registerMBean(new User(), userName);
  
        try {
            //这个步骤很重要,注册一个端口,绑定url后用于客户端通过rmi方式连接JMXConnectorServer
            LocateRegistry.createRegistry(8999);
            //URL路径的结尾可以随意指定,但如果需要用Jconsole来进行连接,则必须使用jmxrmi
            JMXServiceURL url = new JMXServiceURL
                    ("service:jmx:rmi:///jndi/rmi://localhost:8999/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();
        }
        Thread.sleep(60 * 60 * 1000);
}ManagementFactory.getPlatformMBeanServer()获取当前 JVM 的 MBeanServer,ObjectName 是 MBean 的唯一标示,一个 MBeanServer 不能有重复。完整的格式「自定义命名空间:type=自定义类型,name=自定义名称」。当然你可以只声明 type ,不声明 name。
使用 JConsole 查看
JConsole 是 JDK 自带的工具,在${JAVA_HOME}的 bin 目录下,启动即可。启动后在本地进程找到上一步启动的 main 方法所在的进程。

在 JConsole 上方有内存、线程、类等选项卡,点击最后一个 MBean,通过这个选项卡可以看到当前 JVM 所有已定义的 MBean。可以看到系统定义的 MBean ,以及刚刚我们定义的这个 MBean。

点击属性可以在右侧查看属性值,并且在操作菜单项下,可以看到我们定义的方法,并且可以调用。


使用 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 {
        String jmxUrl = "service:jmx:rmi:///jndi/rmi://localhost:8999/jmxrmi";
        monitor(jmxUrl);
    }
    public static void monitor(String url) throws Exception{
        JMXServiceURL jmxServiceURL = new JMXServiceURL
                (url);
        JMXConnector jmxc = JMXConnectorFactory.connect(jmxServiceURL, null);
        MBeanServerConnection msc = jmxc.getMBeanServerConnection();
        String[] domains = msc.getDomains();
        for (String domain : domains) {
            System.out.println(domain);
        }
    }
}首先根据连接串获得一个 JMXServiceURL对象,之后通过JMXConnectorFactory.connect方法获取 JMXConnector,再通过 getMBeanServerConnection拿到 MBeanServerConnection。之后就可以获取所有的 MBean 了。其中 getDomains是获取所有命名空间的方法,也就是我们上面定义的 "FengZheng",以及 JMImplementation、java.lang 等,我们在 JConsole 看到的就是这些。
做了一个 web 版的简单监控
本来就是为了更多的了解 JMX,第一步就想把所有的 MBean 和属性都展示出来,开始在控制台输出,但是效果不好,内容太多太长,不够直观,然后就加了个 web 端的树形结构。然后做着做着突然发现,再稍微改改就能当个 web 版的简易监控端用了。
此工具只在 hotspot JVM 8 环境下测试过。可支持查看本地 JVM 和 远程 JVM 实时监控。
前端采用 React 16 + Antd + Yarn ,后端 Spring Boot + Java 1.8。具体使用方式可以到 github 仓库 README 页面查看。并且提供了一个在线预览版本,文末有源码地址和在线版本的使用方式。

支持本地 JVM 和远程 JVM 链接。

所有 MBean 的树形展示以及属性、操作的展示。

JVM 运行情况实时展示
实现的功能有如下几个方面:
- 所有 MBean 的展示;
- 系统信息的展示,包括内存使用、CPU 使用率等等;
- JVM 参数,包括命令行参数和 systemProperties;
- CPU 、Heap、Metaspace、类加载、线程的实时折线图;
- 垃圾收集器的种类和回收次数;
下面结合这几部分,说一下 JMX 的使用方式。
MBean 的获取
正如各种工具里的 MBean 的树形展示方式一样, MBean 本身就是以这种层级关系存在的。
MBean 包含在 Domain 里,Domain 相当于是一套独立的空间,这个空间里可以定义各种 type,各种 name 的 ObjectName。比如前一篇 JMX 文章里自定义的那个。
通过 ObjectName 可以获取到 MBean 的各种信息,包括属性、操作、通知。
有些属性是简单数据类型,比如 int、long、double、String 类型,另外有些是比较复杂的,比方说 com.sun.management:type=HotSpotDiagnostic 的属性 DiagnosticOptions 就是 javax.management.openmbean.CompositeData 类型。还有的属性的数据类型是 javax.management.openmbean.TabularData。这些都要单独处理。

常用的 MBean
有些指标是监控会用到的,比如内存、CPU、堆空间、线程、类加载情况相关的 MBean。
JDK 提供了一个 ManagementFactory,帮助我们方便的获取常用的 MBean。可以到 java.lang.management 包下找到这个类看一下注释和代码。
OperatingSystemMXBean
可以获取操作系统相关的信息,机器名称、内存使用、CPU使用等信息。
可通过 ManagementFactory.getOperatingSystemMXBean() 方式获取。
RuntimeMXBean
可以获取当前 JVM 的信息,包括 JVM 参数和 JVM 相关的系统参数。
可以通过 ManagementFactory.getRuntimeMXBean()方式获取。
MemoryMXBean
可以获取当前 JVM 的内存使用,包括堆内存和非堆内存。
可以通过 ManagementFactory.getMemoryMXBean()获取
ThreadMXBean
获取 JVM 线程使用情况,包括活动线程、守护线程、线程峰值等。
可以通过 ManagementFactory.getThreadMXBean() 获取。
ClassLoadingMXBean
获取 JVM 类加载情况,包括已加载类、未加载类等。
可以通过 ManagementFactory.getClassLoadingMXBean() 获取。
GarbageCollectorMXBean
获取 JVM 垃圾收集器的情况,包括使用的哪种垃圾收集器以及回收次数等等。
可以通过 ManagementFactory.getGarbageCollectorMXBeans() 获取,注意,这里获取到的是一个集合,因为垃圾收集器分为老年代和新生代两个。
除了以上几个常用的 MBean ,还有很多其他的。有些在 ManagementFactory 类里已提供了,另外还有很多需要自己通过 ObjectName 获取。
JMX
JMX的全称为Java Management Extensions. 顾名思义,是管理Java的一种扩展。这种机制可以方便的管理、监控正在运行中的Java程序。常用于管理线程,内存,日志Level,服务重启,系统环境等。The JMX technology provides a simple, standard way of managing resources such as applications, devices, and services. Because the JMX technology is dynamic, you can use it to monitor and manage resources as they are created, installed and implemented. You can also use the JMX technology to monitor and manage the Java Virtual Machine (Java VM).Using the JMX technology, a given resource is instrumented by one or more Java objects known as Managed Beans, or MBeans. These MBeans are registered in a core-managed object server, known as an MBean server. The MBean server acts as a management agent and can run on most devices that have been enabled for the Java programming language.
JMX connectors
The JMX technology defines standard connectors (known as JMX connectors) that enable you to access JMX agents from remote management applications. JMX connectors using different protocols provide the same management interface. Consequently, a management application can manage resources transparently, regardless of the communication protocol used.
JMX Agent
Agent只是一个Java进程,它包括这个MBeanServer和一系列附加的MbeanService。当然这些Service也是通过MBean的形式来发布。JMX Agent是Mbean的
MBean
MBean简介
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。
MBean说明
标准MBean官方定义:A standard MBean is defined by writing a Java interface called SomethingMBean and a Java class called Something that implements that interface. Every method in the interface defines either an attribute or an operation in the MBean. By default, every method defines an operation. Attributes and operations are methods that follow certain design patterns. A standard MBean is composed of an MBean interface and a class. The MBean interface lists the methods for all exposed attributes and operations. The class implements this interface and provides the functionality of the instrumented resource.大致的意思是:标准的MBean是通过定义接口SomethingMBean 和对应接口的实现类Something(注意这两个对应的要一致),在SomethingMBean接口中定义的方法要不是定义属性(对应属性的get/set方法)要不定义是操作,具体如下面代码所示:
public interface HelloMBean {
        // getName和setName方法表示的属性Name
    public String getName();
    public void setName(String name);
    public void printHello();
    public void printHello(String whoName);
}
public class Hello implements HelloMBean {
    private String name; // name属性
    @Override
    public String getName() {
        return name;
    }
    @Override
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public void printHello() {
        System.out.println("Hello world, "+ name);
    }
    @Override
    public void printHello(String whoName) {
        System.out.println("Hello, "+whoName);
    }
}对应的结果如下图所示:

注意:MBean对应的属性是能够读取而且可能允许写入的( an MBean interface consists of named and typed attributes that are readable and possibly writable),如下面代码所示:
public interface HelloMBean { 
    public void sayHello(); 
    public int add(int x, int y); 
    // 属性name只允许读取,不允许写入
    public String getName(); 
     // 属性cacheSize既可以读取也可以写入
    public int getCacheSize(); 
    public void setCacheSize(int size); 
} MBean Notification
To generate notifications, an MBean must implement the interface NotificationEmitter or extend NotificationBroadcasterSupport. To send a notification, you need to construct an instance of the classjavax.management.Notification or a subclass (such as AttributeChangedNotification), and pass the instance to NotificationBroadcasterSupport.sendNotification
例子代码:
// 扩展类NotificationBroadcasterSupport 
public class Hello extends NotificationBroadcasterSupport implements HelloMBean {
    private int cacheSize = DEFAULT_CACHE_SIZE;
    private static final int DEFAULT_CACHE_SIZE = 200;
    private long sequenceNumber = 1;
    public int getCacheSize() {
        return this.cacheSize;
    }
    public synchronized void setCacheSize(int size) {
        int oldSize = this.cacheSize;
        this.cacheSize = size;
        System.out.println("Cache size now " + this.cacheSize);
                // 定义Notification对象
        Notification n = new AttributeChangeNotification(this,
                sequenceNumber++, System.currentTimeMillis(),
                "CacheSize changed", "CacheSize", "int",
                oldSize, this.cacheSize);
        sendNotification(n); // 发送
    }
    @Override
    public MBeanNotificationInfo[] getNotificationInfo() {
        String[] types = new String[]{
    AttributeChangeNotification.ATTRIBUTE_CHANGE
        };
        System.out.println("=========================");
        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};
    }
}
// 监听处理器
public class HelloListener implements NotificationListener {
    public void handleNotification(Notification n, Object handback) {
        System.out.println("type=" + n.getType());
        System.out.println("source=" + n.getSource());
        System.out.println("seq=" + n.getSequenceNumber());
        System.out.println("send time=" + n.getTimeStamp());
        System.out.println("message=" + n.getMessage());
        if (n instanceof AttributeChangeNotification){
            AttributeChangeNotification attributeChangeNotification = (AttributeChangeNotification)n;
            System.out.println("old value="+attributeChangeNotification.getOldValue());
            System.out.println("new value="+attributeChangeNotification.getNewValue());
        }
        if (handback != null) {
            if (handback instanceof Hello) {
                Hello hello = (Hello) handback;
                hello.printHello(n.getMessage());
            }
        }
    }
}
// 关联实体与监控器
public class HelloAgent {
        public static void main (String[]args) throws MalformedObjectNameException,
                NotCompliantMBeanException, InstanceAlreadyExistsException,
                MBeanRegistrationException, IOException {
            MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
            String domainName = "MyMBean";
            ObjectName helloName = new ObjectName(domainName + ":name=HelloWorld");
            Hello hello = new Hello();
            mbs.registerMBean(hello, helloName);
                       // 管理实体与监听器
            hello.addNotificationListener(new HelloListener(),null,hello);
        }
}
在Jconsole中对应的结果如下图所示:

| TimeStamp | 类型 | UserData | SeqNum | 消息 | 事件 | 源 | 
|---|---|---|---|---|---|---|
| 16:56:15:525 | jmx.attribute. change | 4 | CacheSize changed | javax.management. AttributeChangeNotification [source=MyMBean:name=HelloWorld] [type=jmx.attribute.change][message=CacheSize changed] | MyMBean:name =HelloWorld | 
Remote management
A JMX connector consists of a connector client and a connector server. A connector server is attached to an MBean server and listens for connection requests from clients. A connector client is responsible for establishing a connection with the connector server. A connector client is usually in a different Java Virtual Machine (Java VM) from the connector server and is often running on a different machine. The JMX API defines a standard connection protocol based on Remote Method Invocation (RMI). This protocol enables you to connect a JMX client to an MBean in an MBean server from a remote location and perform operations on the MBean, exactly as if the operations were being performed locally.
 
                    
                     
                    
                 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号