不少Java相关的基础书籍(包括Java核心技术I、II)都没有涉及到JMX的内容,以致于大多数Java学习者对JMX都不甚了解,但这个东西确实是Java SE的一部分,不少性能监控软件都是基于JMX规范开发的,例如jconsole。html
JMX即Java Management Extensions(Java管理扩展),是Java SE的一部分,在Java2的时候加入到Java SE平台中,但Java5才正式发布。JMX提供了一个很是简单的途径去管理应用程序的资源,这里的所说的资源包括内存资源,磁盘资源等,并且由于JMX相关技术是动态的,因此能够在应用程序运行时监控和管理资源。java
JMX主要由三大部分构成:shell
Managed Beans (MBean)表明了某种资源,MBean能够暴露一些接口,用于对MBean所表明的资源进行查看、修改等操做。安全
JMX agents (JMX代理)管理着一个或多个MBean,并将MBean暴露给客户端。JMX代理的核心组件是MBean Server,用于接受客户端的链接,并对客户端的请求作出响应。服务器
Remoete Connectors 能够简单理解成客户端,最贴近用户,用于接受用户的请求,并将请求发送给JMX代理,而后将JMX代理的响应返回给用户。网络
举个例子,假设公司如今有一大堆粮食(有不少种类)须要管理,并设计了一套方案用来管理粮食。他们是这样设计的:用一类特殊的对象来这些粮食而且搭建了一个服务器,该服务器管理着这些粮食,并且还开发了一个对用户友好的程序供用户查看,购买各类各样的粮食。在这个例子中,用来表示粮食的对象就是MBean,而服务器就是JMX Aagent,对用户友好的程序其实就是客户端。架构
例子可能不太恰当,但愿各位能理解其中的意思。oracle
下面是JMX的架构图:app
接下来我将就围绕这三大部分介绍JMX技术。dom
Managed Beans简称MBean(后面就统一使用MBean了),一个MBean是一个被管理的Java对象,它和Java Bean有些类似,但不是彻底等同,一个标准的MBean包含了如下内容:
JMX规范定义了五种类型的MBean,分别是:
本文主要介绍的是Standard MBeans,至于其余类型的MBean,读者若是感兴趣,能够自行查找资料学习。
下面是一个“标准”的MBean:
public interface HelloMBean {
void sayHello();
int add(int x, int y);
String getName();
void setCacheSize(int cacheSize);
int getCacheSize();
}
public class Hello implements HelloMBean {
private final String name = "Yeonon";
private static final int DEFAULT_CACHE_SIZE = 200;
private int cacheSize = DEFAULT_CACHE_SIZE;
@Override
public void sayHello() {
System.out.println("hello, world!");
}
@Override
public int add(int x, int y) {
return x + y;
}
@Override
public String getName() {
return this.name;
}
@Override
public void setCacheSize(int cacheSize) {
int oldValue = this.cacheSize;
this.cacheSize = cacheSize;
System.out.println("Cache size now is : " + this.cacheSize);
}
@Override
public int getCacheSize() {
return this.cacheSize;
}
}
复制代码
标准的MBean一般都包含了一个以MBean为后缀的接口,以及其实现类,例如这里的HelloMBean以及其实现类Hello,在接口里定义了一系列的抽象方法,这些方法都是后面被暴露的,getter和setter方法的最主要做用是来表示某个字段的可读性和可写性,若是某个字段只包含了getter方法而没有包含setter方法,那么该字段就只是可读的,即没法经过JMX技术来修改该属性。在示例代码中,只定义了个操做,即sayHello和add(getter和setter虽然也是方法,但不把他们认为是操做),客户端能够在远程执行这些操做(调用这些方法),后面会看到一个具体的表现,在这里先不说。
JMX还支持事件通知。当MBean的属性被修改、删除或者出现问题的时候,能够生成一个通知,并将其发送出去给,若是客户端包含了通知监听器,客户端就能够接受该通知并将其呈现给用户。MBean要支持通知机制,其实现类必须实现NotificationEmitter接口或者继承NotificationBroadcasterSupport类(推荐继承NotificationBroadcasterSupport类,由于该类已经实现了不少有用的方法),对上面的Hello类进行修改,修改以后的代码以下所示:
public class Hello extends NotificationBroadcasterSupport implements HelloMBean {
private final String name = "Yeonon";
private final AtomicLong sequenceNumber = new AtomicLong(0);
private static final int DEFAULT_CACHE_SIZE = 200;
private int cacheSize = DEFAULT_CACHE_SIZE;
@Override
public void sayHello() {
System.out.println("hello, world!");
}
@Override
public int add(int x, int y) {
return x + y;
}
@Override
public String getName() {
return this.name;
}
@Override
public void setCacheSize(int cacheSize) {
int oldValue = this.cacheSize;
this.cacheSize = cacheSize;
System.out.println("Cache size now is : " + this.cacheSize);
Notification notification = new AttributeChangeNotification(
this, sequenceNumber.getAndIncrement(), System.currentTimeMillis(),
"Cache Size changed", "CacheSize", "int",
oldValue, this.cacheSize);
sendNotification(notification);
}
@Override
public int getCacheSize() {
return this.cacheSize;
}
@Override
public MBeanNotificationInfo[] getNotificationInfo() {
String[] types = new String[] {
AttributeChangeNotification.ATTRIBUTE_CHANGE
};
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};
}
}
复制代码
最主要的修改是在setCacheSize方法里生成了一个Notification对象,该Notification对象就表明一个通知,Notification有不少已经编写好的实现类,例如这里的AttributeChangeNotification,由于这里要发送通知的缘由就是属性被修改,因此就使用了这个实现类,该实现类的构造器签名以下所示:
public AttributeChangeNotification(Object source, long sequenceNumber, long timeStamp, String msg, String attributeName, String attributeType, Object oldValue, Object newValue) {
super(AttributeChangeNotification.ATTRIBUTE_CHANGE, source, sequenceNumber, timeStamp, msg);
this.attributeName = attributeName;
this.attributeType = attributeType;
this.oldValue = oldValue;
this.newValue = newValue;
}
复制代码
一共7个参数,这里我就说一个参数(其余参数根据参数名就能够知道是什么东西了),即sequenceNumber,从参数名上能够知道这是一个序列号,每个通知都有一个序列化,主要是为了防止通知的顺序错误,和网络协议中的那个序列化有殊途同归之妙。例如,若是通知在JMX Agent和远程客户端之间的链路中传输时,发生了顺序错乱,有了序列号,客户端就能够知道应该先处理哪一个通知,后处理哪一个通知,避免乱序处理。
通知生成完毕以后,就调用sendNotification()方法将通知发送出去。你可能会有疑问?这个通知发送到哪去呢?答案是发送到客户端那里(默认是经过RMI机制发送),若是客户端有实现通知监听器,客户端就能够接受并处理通知了。
下面来看看JMX代理。
JMX代理的核心组件就是一个MBean Server,能够将MBean注册到MBean Server中,这就表示MBean由该MBean Server管理了。下面的代码演示了获取MBean Server而且将MBean注册到MBean Server的过程:
public class Main {
public static void main(String[] args) throws MalformedObjectNameException, NotCompliantMBeanException, InstanceAlreadyExistsException, MBeanRegistrationException {
MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
ObjectName name = new ObjectName("top.yeonon.jmx:type=Hello");
Hello mbean = new Hello();
mBeanServer.registerMBean(mbean, name);
System.out.println("waiting forever....");
while (true) {
}
}
}
复制代码
能够经过ManagementFactory.getPlatformMBeanServer()来获取一个MBean Server实例对象,而后调用mBeanServer.registerMBean方法来把一个MBean注册到MBean Server中,该方法有两个参数,一个参数是MBean实例对象,第二个参数是对应MBean的名字,是ObjectName类型的对象,这里的名字其实并不必定就是top.yeonon.jmx:type=Hello,其格式是XXX:type=YYY,XXX和YYY均可以是任意的字符串,并不必定就是包名和类名,不过最好仍是建议使用包名和类名,这样能够避免命名冲突。
启动程序,发现输出waiting forever....,程序没有关闭,其实就是进入无限循环了。这里先不着急,只要程序不包错就好了,下面我会带你来点刺激的!
JMX客户端是最接近用户的,用户也许不知道后面的JMX Agent,MBean,但必定会知道客户端,JMX客户端不会直接和MBean打交道,而是经过JMX Agent来操做其管理的MBean。咱们熟知的Jconsole就是一个JMX客户端,下面先介绍一下如何使用Jconsole来操做MBean,随后再介绍如何自定义一个JMX客户端。
保持JMX Agent处于运行状态,而后打开jconsole软件,以下所示:
选择对应的本地进程(后面会介绍如何使用远程进程来访问),点击以后看到以下界面:
选择MBean选项卡,而后找到top.yeonon.jmx选项,这里会看到有一个名字叫作Hello的MBean(注意到了吗,其实就是咱们在JMX Agent里定义的那个ObjectName),如今选择cacheSize那一栏,在值那里修改能够看到当前cacheSize的值是200,修改为150,并点击刷新试试,以下图所示:
这时候回到控制台,发现多了一行输出:
Cache size now is : 150
复制代码
说明如今cacheSize属性已经被修改为150了!咱们成功的在程序运行时修改了属性。其余的操做,例如sayHello(),add等,各位能够自行尝试,在此就很少说了。
除了属性和操做,还能够看到有一个通知,选择通知那一栏,而后在右下边能够看到有一个“订阅”按钮,点击一下,表示想要接受通知,而后再次尝试修改CacheSize,此时会发现多了一行记录,以下所示:
消息的内容就是咱们以前定义的,Jconsole只是将其展现出来了,并有作过多的处理。
光是用别人开发的客户端没啥意思是否是,没事!JMX也提供了很方便的方式让咱们编写自定义的客户端,下面是一个自定义客户端的代码:
package top.yeonon.jmx;
import javax.management.*;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.Set;
import static java.lang.Thread.sleep;
/** * @Author yeonon * @date 2018/10/16 0016 20:47 **/
public class Client {
//构建一个监听器类,该类须要实现NotificationListener接口并实现handleNotification方法
public static class ClientListener implements NotificationListener {
@Override
public void handleNotification(Notification notification, Object handback) {
echo("\nReceived notification:");
echo("\tClassName: " + notification.getClass().getName());
echo("\tSource: " + notification.getSource());
echo("\tType: " + notification.getType());
echo("\tMessage: " + notification.getMessage());
//若是通知类型是AttributeChangeNotification,那么就获取一些和属性有关的信息
if (notification instanceof AttributeChangeNotification) {
AttributeChangeNotification acn = (AttributeChangeNotification) notification;
echo("\tAttributeName: " + acn.getAttributeName());
echo("\tAttributeType: " + acn.getAttributeType());
echo("\tNewValue: " + acn.getNewValue());
echo("\tOldValue: " + acn.getOldValue());
}
}
public static void main(String[] args) throws IOException, MalformedObjectNameException, InstanceNotFoundException, InterruptedException {
echo("\nCreate an RMI connector client and " +
"connect it to the RMI connector server");
//构造并获取RMI链接
JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://:9999/jmxrmi");
JMXConnector jmxc = JMXConnectorFactory.connect(url, null);
//获取MBeanServer的链接
MBeanServerConnection mbsc = jmxc.getMBeanServerConnection();
waitForEnterPressed();
echo("\nMBeanServer default domain = " + mbsc.getDefaultDomain());
echo("\nMBean count = " + mbsc.getMBeanCount());
echo("\nQuery MBeanServer MBeans:");
Set<ObjectName> objectNames = mbsc.queryNames(null, null);
for (ObjectName objectName : objectNames) {
echo("\tObjectName = " + objectName);
}
waitForEnterPressed();
//建立监听器
NotificationListener listener = new ClientListener();
//管理 Hello MBean
ObjectName mbeanName = new ObjectName("top.yeonon.jmx:type=Hello");
HelloMBean mbeanProxy = JMX.newMBeanProxy(mbsc, mbeanName, HelloMBean.class, true);
echo("\nAdd notification listener...");
mbsc.addNotificationListener(mbeanName, listener, null, null);
echo("\nCacheSize = " + mbeanProxy.getCacheSize());
mbeanProxy.setCacheSize(150);
echo("\nWaiting for notification...");
sleep(2000);
echo("\nCacheSize = " + mbeanProxy.getCacheSize());
echo("\nInvoke sayHello() in Hello MBean...");
mbeanProxy.sayHello();
echo("\nInvoke add(2, 3) in Hello MBean...");
mbeanProxy.add(2,3);
waitForEnterPressed();
//关闭客户端
echo("\nClose the connection to the server");
jmxc.close();
echo("\nBye! Bye!");
}
}
private static void echo(String msg) {
System.out.println(msg);
}
private static void waitForEnterPressed() {
try {
echo("\nPress <Enter> to continue...");
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}
}
复制代码
代码不算少,我将分几步来说解:
先建立一个通知监听器,须要实现NotificationListener并实现handleNotification方法,能够在该方法里对消息进行处理,在演示代码中只是将消息的各项信息列出来而已。
建立一个RMI链接,即以下两行代码:
//构造一个JMXServiceURL,这里使用的是RMI的链接方式,因此要按照RMI的URL格式,端口是9999,
JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://:9999/jmxrmi");
//链接远程的JMX Agent
JMXConnector jmxc = JMXConnectorFactory.connect(url, null);
复制代码
获取MBeanServer链接,使用jmxc.getMBeanServerConnection()方法来获取,下面的操做大多数都是基于该MBeanServer链接。
若是有须要的话,能够MBeanServerConnection里取出各类信息,例如MBean的数量以及具体的MBean的名字(使用MBeanServerConnection.queryNames)。
获取想要管理的MBean代理(这涉及到了代理模式)。使用JMX.newMBeanProxy()方法来生成一个MBean的带来,以后就可使用该MBean代理对具体的MBean进行操做了(例如修改CacheSize等)。
若是须要接收通知,就使用MBeanServerConnection.addNotificationListener()方法为特定的MBean绑定一个监听器。
关闭客户端可使用JMXConnector.close()方法。
在运行程序以前,须要先运行JMX Agent程序,因为这里是使用RMI进行远程链接,而且链接的端口号是9999,若是直接再也不参数启动的话,客户端将没法链接到JMX Agent,须要添加的参数有三个,分别是:
加入参数启动以后,就能够尝试启动客户端了,其输出大体以下:
Create an RMI connector client and connect it to the RMI connector server
Press <Enter> to continue...
#我按个回车
MBeanServer default domain = DefaultDomain
#远端的系统中一共有23个MBean
MBean count = 23
#23个MBean的名字
Query MBeanServer MBeans:
ObjectName = java.lang:type=MemoryPool,name=Metaspace
ObjectName = java.lang:type=MemoryPool,name=PS Old Gen
ObjectName = java.lang:type=GarbageCollector,name=PS Scavenge
ObjectName = java.lang:type=MemoryPool,name=PS Eden Space
ObjectName = JMImplementation:type=MBeanServerDelegate
ObjectName = java.lang:type=Runtime
ObjectName = java.lang:type=Threading
ObjectName = java.lang:type=OperatingSystem
ObjectName = java.lang:type=MemoryPool,name=Code Cache
ObjectName = java.nio:type=BufferPool,name=direct
ObjectName = java.lang:type=Compilation
ObjectName = top.yeonon.jmx:type=Hello
ObjectName = java.lang:type=MemoryManager,name=CodeCacheManager
ObjectName = java.lang:type=MemoryPool,name=Compressed Class Space
ObjectName = java.lang:type=Memory
ObjectName = java.nio:type=BufferPool,name=mapped
ObjectName = java.util.logging:type=Logging
ObjectName = java.lang:type=MemoryPool,name=PS Survivor Space
ObjectName = java.lang:type=ClassLoading
ObjectName = java.lang:type=MemoryManager,name=Metaspace Manager
ObjectName = com.sun.management:type=DiagnosticCommand
ObjectName = java.lang:type=GarbageCollector,name=PS MarkSweep
ObjectName = com.sun.management:type=HotSpotDiagnostic
Press <Enter> to continue...
#我按个回车
Add notification listener...
CacheSize = 100
Waiting for notification...
#监听器收到了通知
Received notification:
ClassName: javax.management.AttributeChangeNotification
Source: top.yeonon.jmx:type=Hello
Type: jmx.attribute.change
Message: Cache Size changed
AttributeName: CacheSize
AttributeType: int
NewValue: 150
OldValue: 100
CacheSize = 150
#调用了sayHello()方法
Invoke sayHello() in Hello MBean...
#调用了add()方法
Invoke add(2, 3) in Hello MBean...
Press <Enter> to continue...
#回车
Close the connection to the server
Bye! Bye!
复制代码
好了,打完收工!
本文简单介绍了JMX的三大组成部分,MBean,JMX Agent,Client,但只使用到了JMX的部分功能,其实JMX远不止如此,还有不少更加高级的功能,例如扩展JMX等,但愿本文能对读者有一些帮助。
若是文章一些地方说的有问题,望指正。由于我也是初学JMX,算是边学边写的。
Ps: 本文的样例代码都是来自Oracle JMX Tutorial,下面的参考资料中给出了连接。