本篇文章内容主要来源于官方文档《Trail: Java Management Extensions (JMX)》,目的是体验一波JMX。
什么是JMX?
JMX(Java Management Extensions)是一个为监控应用资源而制定的一套规范。JMX规范使用Java语言定义了一套体系、提供了接口和服务。JMX的体系结构如下所示:
总共分为了三层:
- 设备层(Instrumentation),由许多提供属性、操作的MBean组成,这些MBean就是对外暴露资源用的对象。
- 代理层(Agent),设备层的MBean需要注册到代理层,用户可以在代理层对MBean进行查找、注册、删除等操作
- 适配层(Adapter),允许管理工具层使用不同的网络协议来操作代理层
- 管理工具层(Client),直接面向用户的GUI工具
设备层
设备层由众多MBean组成。MBean是代表某些资源的Java对象,遵循一定的规范,能够将属性、操作暴露出去。通常一个MBean需要支持以下功能:
- 能被访问的属性
- 能够做一些更新操作
- 能够发出通知事件
比如要定义一个标准的Bean(还有其他种类的 MBean ),需要做以下两个步骤:
- 创建接口SomethingMBean
- 实现上述接口的类必须叫做Something
比如现在创建一个接口, WebServerConfigMBean
public interface WebServerConfigMBean {String getApplicationName();void setCache(int size);int getCache();}
实现WebServerConfigMBean的类名必须叫做 WebServerConfig ,否则会报一个javax.management.NotCompliantMBeanException 异常,说我们这个类既不是 XMBean 、 Standard MBean 、 也不是Dynamic MBean
public class WebServerConfig implements WebServerConfigMBean {private String applicationName;private int cache;public WebServerConfig(String applicationName, int cache) {this.applicationName = applicationName;this.cache = cache;}@Overridepublic String getApplicationName() {return this.applicationName;}@Overridepublic void setCache(int size) {this.cache = size;}@Overridepublic int getCache() {return this.cache;}}
代理层
代理层总的来说就是用来管理 MBean 的,并让外部的应用程序可以控制这些MBean。
主要包含 MBean Server 、管理 MBean 的服务、一个连接器或协议适配器。
远程管理层
远程管理需要使用协议获取到所有MBean的资源信息,而连接器主要用来控制资源。
实战1
定义MBean
MBean的种类很多,后面会逐一介绍,这里主要先讲最简单的 Standard MBean 。 Standard Bean 需要定义一个接口。注意,只要是 **MBean ,每个接口都要带有 MBean** 的后缀! sayHello() 和 add() 是暴露给外部的可调用的方法, getName() 是必要的返回 MBean 名称的方法, get/setCacheSize() 是暴露给外部查看/修改资源的方法。
public interface HelloMBean {public void sayHello();public int add(int x, int y);public String getName();public int getCacheSize();public void setCacheSize(int size);}
这里给出 HelloMBean 的实现,注意这里不需要带 MBean 后缀了:
public class Hello implements HelloMBean {
private static final String name = "Reginald";
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 name;
}
@Override
public int getCacheSize() {
return this.cacheSize;
}
@Override
public void setCacheSize(int size) {
this.cacheSize = size;
System.out.println("Cache size now " + this.cacheSize);
}
}
创建JmxAgent管理资源
一开始是通过 ManagementFactory 获取 MBeanServer ,如果这样获取不到就会尝试通过 MBeanServerFactory#createMBeanServer() 来获取 MBeanServer :
public static void main( String[] args ) throws Exception {
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
ObjectName name = new ObjectName("com.example:type=Hello");
Hello mbean = new Hello();
mbs.registerMBean(mbean, name);
System.out.println("Waiting forever...");
Thread.sleep(Long.MAX_VALUE);
}
此外这里还要注意,每个被注册的 MBean 必须带有名字,即 ObjectName ,且名字必须遵守 域名:键值对1[,键值对2...] 这样的规范来。之后通过 registerMBean 来注册对应的 MBean 。最后,外部的资源管理器就能查看 cacheSize 、修改 cacheSize 、执行一些方法了~
通过远程管理器管理资源
打开 jconsole (或者装了 MBean插件 的 JVisual ),进入我们启动的这个这个程序,选择最后一栏 MBean
Name属性
CacheSize属性

如果把 CacheSize 从“200” 改为 “300”,那么控制台会输出 “Cache size now 300 ”
add方法
相当于在 Hello 这个对象上调用 add() 方法,值是外部传入的!输出结果会返回回来,如果有返回值会以弹窗的形式返回;没有返回值则近提示调用成功。
sayHello方法
因为没有参数,所以直接点击按钮调用。因为无返回值,所以仅提示调用成功。
实战2:增加MBean的消息通知
在实战1的基础上,增加通知机制,即有变化通知订阅者~
首先对 MBean 进行改造:
public class Hello extends NotificationBroadcasterSupport implements HelloMBean {
// ...省略
// 当修改cacheSize就触发通知,告诉所有订阅者
public synchronized void setCacheSize(int size) {
int oldSize = this.cacheSize;
this.cacheSize = size;
System.out.println("Cache size now " + this.cacheSize);
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
};
String name = AttributeChangeNotification.class.getName();
String description = "An attribute of this MBean has changed";
Descriptor descriptor = new ImmutableDescriptor(new String[]{"size"}, new Object[]{this.cacheSize});
MBeanNotificationInfo info =
new MBeanNotificationInfo(types, name, description, descriptor);
return new MBeanNotificationInfo[]{info};
}
// 新增消息序列
private long sequenceNumber = 1;
}
当在通知栏目点击订阅后,再修改 CacheSize 就会触发消息通知:
实战3:自定义JMX Client
之前都是通过 JConsole 来管理 MBean 对象,这回要自定义一个 Client 来管理 MBean 对象。
第一步,创建一个 JMXServiceURL ,这货用来创建一个URL,这个URL的结构拆分如下所示:
- 该URL必须要以
service:jmx开头, - 使用RMI注册器?
:rmi:///jndi/rmi://:9999/jmxrmi(总之这部分是要用到RMI技术的)
第二步,使用上面的JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://:9999/jmxrmi");url创建JMX连接,即JMXConnector。这一步看文档说是和ConnectorServer连接,ConnectorServer是啥呢?:
第三步,通过JMXConnector jmxc = JMXConnectorFactory.connect(url, null);JMXConnector获取到MBeanServerConnection。MBeanServerConnection应该就是负责MBean的管理。
第四步,通过MBeanServerConnection mbsc = jmxc.getMBeanServerConnection();MBeanServerConnection获取MBean的所有domain(后面省略一些没啥意思的操作):String domains[] = mbsc.getDomains(); Arrays.sort(domains); for (String domain : domains) { System.out.println("\tDomain = " + domain); }
第五步,通过 MBeanServerConnection 获取 MBean 的数量:
echo("\nMBean count = " + mbsc.getMBeanCount());
echo("\nQuery MBeanServer MBeans:");
Set<ObjectName> names = new TreeSet<ObjectName>(mbsc.queryNames(null, null));
for (ObjectName name : names) {
echo("\tObjectName = " + name);
}
第六步,通过代理控制 MBean 对象:
ObjectName mbeanName = new ObjectName("com.example:type=Hello");
HelloMBean mbeanProxy = JMX.newMBeanProxy(mbsc, mbeanName,HelloMBean.class, true);
第七步,给该 MBean 添加监听事件:
// 自定义的监听器
ClientListener listener = new ClientListener();
// 给指定的Bean注册监听器
mbsc.addNotificationListener(mbeanName, listener, null, null);
第八步,修改 MBean 触发消息通知:
mbeanProxy.setCacheSize(150);
总结
官方提供的测试程序《jmx_examples.zip》
本篇文章只是体验一番JMX服务,这里对实战3里的 JMXConnector 、 Proxy 还有 URL 方式很懵逼。通过本篇文章,能大致窥探到一些例如Tomcat的admin端操作的原理~
