JMX 官方教程:http://docs.oracle.com/javase/tutorial/jmx/index.html
这篇博客参考官方教程以及个人的理解,通过实际的代码和操作来学会使用 JMX。
JMX 简介
不想看简介的可以直接跳到标准MBean
J2SE 5.0 版本中引入了Java管理扩展(JMX)。它提供了一种在运行时动态管理资源的体系结构。
JMX主要用于配置和监控资源的状态,使用JMX还可以监视和管理Java虚拟机。因为JMX可以远程管理应用程序,所以很容易在分布式环境中使用,这篇博客会通过示例来展示JMX使用方法,通过这些用法再结合其他理论概念性的文章阅读时,会更容易理解掌握。
使用JMX技术,给定的资源由一个或多个称为Managed Bean(即MBean)的 Java对象进行监控。这些MBean注册在称为MBean服务器的核心管理的对象服务器中。MBean服务器用来管理代理,JMX 可以在已启用Java编程语言的大多数设备上运行。
基于JMX技术的代理是直接控制资源并使其可用于远程管理应用程序的标准管理代理。JMX代理通常位于与他们控制的资源相同的机器上,但这不是必须的。
JMX代理的核心组件是MBean服务器,MBean服务器是MBean注册的托管对象服务器。JMX代理还包括一组用于管理MBean的服务,以及至少一个通信适配器或连接器,以允许管理应用程序访问。
实现JMX代理时,您不需要知道将要管理的资源的语义或功能。事实上,JMX代理甚至不需要知道它将提供哪些资源,因为符合JMX规范的任何资源都可以使用任何提供资源需要的服务的JMX代理。类似地,JMX代理不需要知道将访问它的管理应用程序的功能。
下面我们会先写一个简单的 MBean,然后使用符合JMX规范的JConsole监控和管理工具进行查看。
JMX规范定义了五种类型的MBean:
- Standard MBeans
- Dynamic MBeans
- Open MBeans
- Model MBeans
- MXBeans
- MXBeans
常用就是标准MBeans和动态MBeans,下面是一个简单的标准MBean的例子。。
标准MBean
标准MBean要求必须创建一个以MBean
为后缀的接口,接口中定义标准的getter和setter方法名,还可以定义其他可以执行的方法。
首先,在com.example
中定义接口HelloMBean
。
package com.example;
public interface HelloMBean {
//-----------
// operations
//-----------
public void sayHello();
public int add(int x, int y);
//-----------
// attributes
//-----------
// a read-only attribute called Name of type String
public String getName();
// a read-write attribute called CacheSize of type int
public int getCacheSize();
public void setCacheSize(int size);
}
接口中提供了可以调用的方法,无参数并且没有返回值的sayHello
和需要参数并且有返回值的add
方法。还提供了只读的属性Name
以及可以读写的CacheSize
。
继承该接口,创建一个实现类Hello
。
package com.example;
public class Hello implements HelloMBean {
private final String name = "Reginald";
private static final int DEFAULT_CACHE_SIZE = 200;
private int cacheSize = DEFAULT_CACHE_SIZE;
public void sayHello() {
System.out.println("hello, world");
}
public int add(int x, int y) {
return x + y;
}
public String getName() {
return this.name;
}
public int getCacheSize() {
return this.cacheSize;
}
public synchronized void setCacheSize(int size) {
this.cacheSize = size;
System.out.println("Cache size now " + this.cacheSize);
}
}
下面通过简单代码来发布这个MBean。
package com.example;
import java.lang.management.ManagementFactory;
import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;
import javax.management.MBeanServer;
import javax.management.ObjectName;
public class Main {
/* For simplicity, we declare "throws Exception".
Real programs will usually want finer-grained exception handling. */
public static void main(String[] args) throws Exception {
// Get the Platform MBean Server
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
// Construct the ObjectName for the Hello MBean we will register
ObjectName mbeanName = new ObjectName("com.example:type=Hello");
// Create the Hello World MBean
Hello mbean = new Hello();
// Register the Hello World MBean
mbs.registerMBean(mbean, mbeanName);
// Wait forever
System.out.println("Waiting for incoming requests...");
Thread.sleep(Long.MAX_VALUE);
}
}
首先通过 ManagementFactory
自动创建 MBeanServer
。每个 JMX MBean都必须有一个对象名,就是 ObjectName
对象,这个对象必须符合规范域:参数1=值1,参数2=值2
这种形式,这里用的包名,并且设置了type
为Hello
。然后创建了MBean 对象,通过 MBeanServer
注册MBean,通过这几步简单的操作后,JMX服务就发布了。通过Thread.sleep
保证程序不会退出。
当前com/example
目录下有HelloMBean,Hello 和 Main 3 个类,在 com 同级目录下打开命令窗口,使用下面的命令编译和执行代码:
javac com/example/*.java
然后执行:
java com.example.Main
服务启动后会输出 Waiting for incoming requests...
。
使用 JConsole 查看 JMX 服务
如果你在 Path 环境变量中配置过 Java 的 bin 目录,你可以打开一个新的命令窗口,然后输入 jconsole 启动,或者手动从 Java 安装目录下的 bin 中启动,打开如下窗口。
选择本地进程中 com.example.Main
,然后连接。连接过程中会提示安全连接失败,选择不安全的方式进行连接。
在概览界面会显示JVM的基本信息,这些信息也是通过JMX实现的,从标签中选择【MBean】,从左侧选择我们的在 ObjectName
中定义的域 com.example
,可以看到下面有个 Hello 节点,点击 Hello 可以看到下面的信息。
属性
在 Hello 下面还有属性和操作两个节点。先看属性,如下所示。
这里可以看到根据 JavaBean 规范从方法名中提取出来的两个属性,需要注意的是,MBean 中的属性首字母都是大写的。如果我们程序内部会动态改变这两个值,那么在这里点击中间的【刷新】按钮时是可以看到属性的最新状态。从属性节点点击具体的属性名还可以查看该属性的详细信息。
在这个界面上,我们可以看到 CacheSize 值是蓝色的,说明该值是可修改的,Name 值黑色,是只读的,这和我们接口中定义的 getter 和 setter 方法是一致的。
点击 CacheSize 的值,然后修改为 100,鼠标点击其他地方(如Name),此时控制台会输出Cache size now 100
的信息。通过这种方式,我们就实现了动态修改配置的功能。稍后我们修改代码来监控 CacheSize
属性的变化。
操作
点开 Hello 下面的操作。
可以看到接口中提供的两个方法,点击【sayHello】后可以从控制台看到输出的日志。
在add 方法后面填入两个参数,再点击【add】可以看到返回值提示框中的计算结果。
通过 JMX 除了可以监控和配置参数外,还能执行一些定义好的操作。
监控变化和通知
JMX API定义了一种可以使 MBean 生成通知的机制,可以用于指示状态变更,检测事件或问题。
要生成通知,MBean 必须实现 NotificationEmitter
接口或扩展 NotificationBroadcasterSupport
。要发送通知,还需要需要构造一个 javax.management.Notification
类或一个子类(例如 AttributeChangedNotification
)的实例,并将该实例传递给NotificationBroadcasterSupport.sendNotification
。
每个通知都有来源。来源是生成通知的 MBean 的对象名称。
每个通知都有一个序列号,建议用一个自增的序号,这样可以通过序号对通知进行排序。
对上面的 Hello
做下面几处修改,首先是类签名:
public class Hello
extends NotificationBroadcasterSupport
implements HelloMBean {
//增加序号
private long sequenceNumber = 1;
继承了 NotificationBroadcasterSupport
类,然后重写下面的方法:
@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
方法,当属性值变化时发送通知。
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);
}
这里产生了一个 AttributeChangeNotification
属性变更通知。
为了可以让程序自动变更 CacheSize 的值,我们对 Main 简单改造一下。
package com.example;
import java.lang.management.ManagementFactory;
import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import java.util.Random;
import java.util.concurrent.TimeUnit;
public class Main {
/* For simplicity, we declare "throws Exception".
Real programs will usually want finer-grained exception handling. */
public static void main(String[] args) throws Exception {
// Get the Platform MBean Server
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
// Construct the ObjectName for the Hello MBean we will register
ObjectName mbeanName = new ObjectName("com.example:type=Hello");
// Create the Hello World MBean
final Hello mbean = new Hello();
// Register the Hello World MBean
mbs.registerMBean(mbean, mbeanName);
// Wait forever
System.out.println("Waiting for incoming requests...");
new Thread(new Runnable(){
public void run(){
Random random = new Random();
while(true){
try {
TimeUnit.SECONDS.sleep(random.nextInt(10));
} catch(Exception e){}
mbean.setCacheSize(random.nextInt(10) + mbean.getCacheSize());
}
}
}).start();
Thread.sleep(Long.MAX_VALUE);
}
}
增加了一个独立线程去修改 CacheSize
的值。
使用同样的命令编译和运行 Main,启动后使用 JConsole 连接程序。
此时在属性界面,点击刷新的时候,就能够看到 CacheSize 的变化。
点击 Hello 下面多出来的【通知】,如下图所示。
点击【订阅】,稍等片刻后,会看到这里出现了通知,如下图所示。
接收的通知会倒叙显示。通过这种方式,我们可以将配置的变化通知给感兴趣的订阅者。
远程连接
前面连接都是本地进程,如何连接远程进程呢?
JMX默认提供了下面这些启动参数,在运行程序时设置好这些参数就可以开启远程连接。
使用下面方式执行 com.example.Main
(命令过长时,win 使用 ^
换行,linux 使用 \
换行)。
java -Dcom.sun.management.jmxremote.port=9999 ^
-Dcom.sun.management.jmxremote.authenticate=false ^
-Dcom.sun.management.jmxremote.ssl=false ^
com.example.Main
在 JConsole 中选择远程进程,输入 127.0.0.1:9999
,点击连接,如下图所示。
连接后就和本地的操作一样了。
从上图可以注意到,远程进程有两种写法,第一种就是上面用的 ip:port 方式。
除此之外,还有一种 service 的方式,针对上面的例子,这里的写法如下:
service:jmx:rmi:///jndi/rmi://127.0.0.1:9999/jmxrmi
看到这里应该对JMX有个基本的了解了,由于篇幅过长,所以通过编码方式访问 JMX 应用的用法在下一篇博客中介绍。
代码下载
为了避免不必要的麻烦,这里提供完整的代码下载。
链接:http://pan.baidu.com/s/1bpvptNL
密码:7xoj