JMX的全称为Java Management Extensions. 顾名思义,是管理Java的一种扩展。这种机制可以方便的管理、监控正在运行中的Java程序。常用于管理线程,内存,日志Level,服务重启,系统环境等。
欢迎支持笔者新作:《深入理解Kafka:核心设计与实践原理》和《RabbitMQ实战指南》,同时欢迎关注笔者的微信公众号:朱小厮的博客。
欢迎跳转到本文的原文链接:https://honeypps.com/java/jmx-quick-start-1-standard-mbean/
Adapter 和Connector的区别在于:Adapter是使用某种Internet协议来与JMX Agent获得联系,Agent端会有一个对象 (Adapter)来处理有关协议的细节。比如SNMP Adapter和HTTP Adapter。而Connector则是使用类似RPC的方式来访问Agent,在Agent端和客户端都必须有这样一个对象来处理相应的请求与应答。比如RMI Connector。
JMX Agent可以带有任意多个Adapter,因此可以使用多种不同的方式访问Agent。
JMX分为三层,分别负责处理不同的事务。它们分别是:
如果一个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接口和应用设计模式的过程被称为内省(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.(Unknown Source)
at java.net.Socket.(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 set = mbsc.queryMBeans(null, null);
for(Iterator 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代理通过这些元数据类管理所有管理构件,而不管这些管理构件的类型。部分辅助元类如下:
有关Notication, Dynamic MBean, Model MBean以及Apache Common Modeler由于篇幅限制将后面文章中讲述。
欢迎跳转到本文的原文链接:https://honeypps.com/java/jmx-quick-start-1-standard-mbean/
欢迎支持笔者新作:《深入理解Kafka:核心设计与实践原理》和《RabbitMQ实战指南》,同时欢迎关注笔者的微信公众号:朱小厮的博客。