JMX 管理接口
JMX(Java Management Extensions)是JavaSE提供的监控应用程序状态、修改程序配置的标准接口。通过JMX,我们可以很方便的查看当前Java程序的内存堆栈、垃圾回收情况等许多信息。我们也可以自定义JMX接口,用于查看内部运行情况,以及修改应用配置。
很多偏向底层的中间件,其管理都是通过JMX实现的,比如Weblogic等。相比于自定义一组管理协议或是写一个管理页面,JMX提供了一种系统监控和管理的标准方式。
基本概念
JMX的架构如图所示:

- MBean:提供了我们需要监控和管理的内容的JavaBean,其中的资源会通过MBeanServer暴露出去
- MBeanServer:用于注册MBean以及响应JMX客户端的操作
上面图其实很直观,Java程序启动后,系统自带的MBean以及我们自定义的MBean会被注册到MBeanServer;MBeanServer这个JMX服务端组件集成了多种协议的Connector,支持包括RMI、HTTP/SOAP等协议;程序启动后,外部客户端就可以通过JMX端口以对应协议访问相关MBean提供的资源。
为应用程序开启JMX
JDK自带的JConsole工具其实就是一个JMX客户端,我们可以用该工具进行JMX的连接测试。
本地Java程序JMX支持
大部分JVM默认开启了JMX功能,我们不需要做任何配置,但默认情况仅限于本地进程间通讯,我们可以使用JDK自带的JConsole工具直接连接一个本地Java程序。

上图中,我们可以看到本地的Java进程,JConsole工具可以直接连接查看。
开启RMI协议JMX支持
很多情况我们可能需要远程访问JMX,例如我们开发一个监控程序通过JMX监控一组Java应用,那么此时就需要JMX通过网络协议来连接,其中最常用的是RMI。
开启基于RMI的JMX需要添加如下JVM启动参数:
-Djava.rmi.server.hostname=127.0.0.1 -Dcom.sun.management.jmxremote.port=1099 -Dcom.sun.management.jmxremote.rmi.port=1099 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
参数中指定了JMX的主机名(具体使用时填写连接时对应主机IP)、端口,以及不开启密码认证、不开启SSL的配置(生产环境可以考虑开启)。JConsole中的远程进程可以通过指定这些参数进行连接。
容器环境下的JMX支持
除此之外,如果是k8s容器环境,这里我们就无法填写hostname主机名了,因为k8s调度下的容器IP不是固定的。此时的最佳解决方案是这样的:我们主机名还是写127.0.0.1,同时使用kubectl命令,将容器内端口映射到本地,来实现JMX的远程连接。映射命令例子如下:
.\kubectl.exe --kubeconfig ./config port-forward -n dev order-687998bc67-srmjr --address 0.0.0.0 1099:1099
JConsole连接JMX
JConsole连接JMX效果如图所示:

连接上后,我们就可以通过图形界面查看各种信息,或者直接选择相应的MBean查看各种属性了。上图中,我们查看了com.sun.management.OperatingSystemMXBean这个类的Name属性,属性值表示我这里使用的操作系统是Windows11。
自定义MBean
上面图中我们可以看到大量系统自带的MBean,其中包含了许多系统状态信息。实际上,我们也可以注册自定义MBean。下面是一个例子:
AppInfoMBean.java
package com.gacfox.demojmx;
/**
* @author gacfox
*/
public interface AppInfoMBean {
String getInfo();
void doSomething();
}
AppInfo.java
package com.gacfox.demojmx;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @author gacfox
*/
public class AppInfo implements AppInfoMBean {
@Override
public String getInfo() {
// 返回一些例子信息
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return String.format("Hello, JMX! Current Time is %s.", sdf.format(new Date()));
}
@Override
public void doSomething() {
System.out.println("Method doSomething called!");
}
}
Main.java
package com.gacfox.demojmx;
import java.lang.management.ManagementFactory;
import javax.management.MBeanServer;
import javax.management.ObjectName;
/**
* @author gacfox
*/
public class Main {
public static void main(String[] args) {
// 注册到MBeanServer
try {
MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
AppInfo appInfo = new AppInfo();
ObjectName objectName = new ObjectName("com.gacfox.demojmx:type=AppInfo");
mBeanServer.registerMBean(appInfo, objectName);
} catch (Exception e) {
e.printStackTrace();
}
// 作为例子,写一个死循环防止程序结束
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
JMX中对MBean的写法有一定要求:
- 必须为“接口/实现类”模式
- 接口命名必须以
MBean或MXBean结尾 - 实现类必须为标准JavaBean写法
遵循上述写法编写MBean后,我们即可将其注册到MBeanServer,这个对象可以通过ManagementFactory.getPlatformMBeanServer()方法获取。注册时需要指定一个MBean名字,通常写法都是包名:type=类型。注意这里MBean是单例的。
通过JConsole连接后,效果如下:

我们可以看到,MBean下有两类内容,属性和操作:
- 属性对应Get/Set方法,如果只有Get方法那么属性只读,如果有Set方法那么该属性还可以编辑
- 操作对应我们自定义方法,支持带参数
通过代码调用JMX
上面例子中我们是通过JConsole工具调用JMX,查看MBean资源的。如果我们基于JMX自己开发一个监控系统,其实可以直接通过代码方式调用JMX。下面是一个例子:
package com.gacfox.demojmxclient;
import javax.management.MBeanInfo;
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
/**
* @author gacfox
*/
public class Main {
public static void main(String[] args) {
try {
// 建立JMX连接
JMXServiceURL jmxServiceURL = new JMXServiceURL(
"service:jmx:rmi://localhost:1099/jndi/rmi://localhost:1099/jmxrmi");
JMXConnector jmxConnector = JMXConnectorFactory.connect(jmxServiceURL);
MBeanServerConnection connection = jmxConnector.getMBeanServerConnection();
// 根据ObjectName对象调用
ObjectName objectName = new ObjectName("com.gacfox.demojmx:type=AppInfo");
// 获取MBean信息
MBeanInfo mBeanInfo = connection.getMBeanInfo(objectName);
System.out.println(mBeanInfo);
// 获取属性(调用Get方法)
Object info = connection.getAttribute(objectName, "Info");
System.out.println(info);
// 调用操作
connection.invoke(objectName, "doSomething", null, null);
// 关闭JMX连接
jmxConnector.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
代码中,我们直接通过JMX的URL方式连接服务端,然后通过ObjectName定义即可找到MBean,接下来就可以读写属性,或是调用操作了。
上面JMX客户端的操作都是通过MBean名、属性名、操作名等实现,完全不与JMX服务端代码耦合,这也是Java语言反射机制的强大之处,C++就很难实现类似的效果。