JMX 简介
JMX 的全称为 Java Management Extensions,是一种监控、管理正在运行的 Java 应用程序的机制,并且支持开发人员自定义 MBean 监视器。
常用 MXBean
通过查询 java.lang.management.PlatformManagedObject 的继承接口能够得知当前 JDK 版本提供的所有 MXBean。
| 类名 | 功能 | 
|---|---|
| java.lang.management.CompilationMXBean | 编译器的管理接口 | 
| java.lang.management.ClassLoadingMXBean | 类加载模块的管理接口 | 
| java.lang.management.BufferPoolMXBean | 堆外内存和映射内存的管理接口 | 
| java.lang.management.MemoryManagerMXBean | 内存管理器的管理接口 | 
| java.lang.management.MemoryMXBean | 虚拟机的内存系统的管理接口 | 
| java.lang.management.MemoryPoolMXBean | 内存池的管理接口 | 
| java.lang.management.OperatingSystemMXBean | 操作系统的管理接口 | 
| java.lang.management.GarbageCollectorMXBean | 垃圾回收的管理接口,继承至 MemoryManagerMXBean | 
| java.lang.management.RuntimeMXBean | 虚拟机的运行时系统的管理接口 | 
| java.lang.management.ThreadMXBean | 线程的管理接口 | 
| java.lang.management.PlatformLoggingMXBean | 日志模块的管理接口 | 
| javax.management.DynamicMBean | 扩展管理接口,用于自定义管理器 | 
在 com.sun.management 包下,还提供了部分 MXBean 的扩展接口,对原来管理器的功能进行了增强。
管理方式
监视器的获取方式分为 本地、远程、代理,通常情况下使用远程方式进行管理。
本地
对于运行在本机的程序直接使用 java.lang.ManagementFactory 的静态方法便可以获取。
远程
远程模式可以通过建立与其他虚拟机的连接后对其进行操作。
- 建立连接
 
String url = "service:jmx:rmi:///jndi/rmi://" + host + ":"+ port + "/" + extend;
JMXServiceURL serviceURL = new JMXServiceURL(url);
JMXConnector conn = JMXConnectorFactory.connect(serviceURL);
MBeanServerConnection connection;
try {
    connection = conn.getMBeanServerConnection();
} catch(IOException e) {
	e.printStackTrace();
}
- 监听管理
 
MemoryMXBean memBean = ManagementFactory.getPlatformMXBean(connection, MemoryMXBean.class);
代理
由于 Mbean 在注册的时候有 ObjectName 的存在,对于自定义 MBean 则必须使用此方式指定 ObjectName 方可获取。
MemoryMXBean memBean = ManagementFactory.newPlatformMXBeanProxy(connection, ManagementFactory.MEMORY_MXBEAN_NAME, MemoryMXBean.class);
// 指定 MBean 的特定属性值
ObjectName objectName = new ObjectName("domain:type=name");
// 对应 getUser 或者 setUser 操作
connection.setAttribute(objectName, new Attribute("User","remote"));
DynamicMBean dynamicMBean = MBeanServerInvocationHandler.newProxyInstance(connection, objectName, DynamicMBean.class, true);
注册方式
自定义 MBean
Java 规范对注册的 MBean 的命名有严格的规范,要求必须是以 MBean 结尾的接口 (MXBean 也行,不过为了与 JDK 的区分,一般使用 MBean)。
注册的方法也是有标准的,以 set 或 get 开头并且参数规范的方法将会被隐藏,对于参数只支持 基本类型、数组、void。隐藏的方法可以通过 属性 来访问,属性将由 JDK 解析方法名生成。
为了方便 ManagementFactory 调用,可以使用提供的 DynamicMBean 接口进行实现,或者使用更简便的 com.sun.jmx.mbeanserver.StandardMBeanSupport 进行构建。
注册
MBean 的注册管理是通过 javax.management.MBeanServer 来进行的,并且指定名称 javax.management.ObjectName。
StandardMBeanSupport mbean = new StandardMBeanSupport(new Object(), Interface.class);
MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer();
try {
    final ObjectName objectName;
    try {
        objectName = ObjectName.getInstance("domain:type=name");
    } catch (MalformedObjectNameException e) {
        e.printStackTrace();
        return;
    }
    // 跳过 ManagementPermission 的权限校验
    AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
        public Void run() throws MBeanRegistrationException, NotCompliantMBeanException {
            try {
                if (beanServer.isRegistered(objectName)) {
                    beanServer.unregisterMBean(objectName); 
                }
                beanServer.registerMBean(mbean, objectName);
                return null;
            } catch (InstanceAlreadyExistsException | InstanceNotFoundException var2) {
                return null;
            }
        }
    });
} catch (PrivilegedActionException e) {
    e.printStackTrace();
}
开放端口
当需要通过远程进行 Java 应用管理时,就需要通过提供端口以供访问了,通过先前的 MBeanServer 构建 JMX 连接。
try {
    LocateRegistry.createRegistry(port);
} catch (RemoteException e) {
    return;
}
try {
    JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://127.0.0.1:" + port + "/" + extend);
    JMXConnectorServer cs = JMXConnectorServerFactory.newJMXConnectorServer(url, null, beanServer);
    cs.start();
} catch (IOException e) {
    e.printStackTrace();
}
安全
对远程开放端口可设置 SSL、权限校验,只需对 JMXConnectorServer 增加配置选项即可
Map<String, Object> env = new HashMap<String, Object>();
// 对 env 增加配置
...
JMXConnectorServer cs = JMXConnectorServerFactory.newJMXConnectorServer(url, env, mbs);
cs.start(); 
开启 SSL 连接
// Server
env.put(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE, new SslRMIClientSocketFactory()); 
env.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE, new SslRMIServerSocketFactory(); 
设置权限认证
// Server
SimpleAuthenticator auth = new SimpleAuthenticator();
evn.put(JMXConnectorServer.AUTHENTICATOR, auth);
env.put("jmx.remote.sasl.callback.handler", new CallbackHandler() {...}); 
class SimpleAuthenticator implements JMXAuthenticator {
    private final Map<String, Auth> authRole;
    public SimpleAuthenticator() {
        authRole = new HashMap<>();
    }
    public boolean addAuth(String username, String password) {
        return addAuth(username, password, true);
    }
    public boolean addAuth(String username, String password, boolean readOnly) {
        if (authRole.containsKey(username)) {
            return false;
        }
        authRole.put(username, new Auth(password, readOnly));
        return true;
    }
    public boolean removeAuth(String username) {
        return authRole.remove(username) != null;
    }
    @Override
    public Subject authenticate(Object credentials) {
        if (!(credentials instanceof String[])) {
            throw new SecurityException("Authentication failed!");
        }
        String[] credentialsInfo = (String[]) credentials;
        if (credentialsInfo.length != 2) {
            throw new SecurityException("Authentication failed!");
        }
        String userName = credentialsInfo[0];
        Auth auth = authRole.get(userName);
        if (auth == null) {
            throw new SecurityException("Invalid Authentication!");
        }
        String password = credentialsInfo[1];
        if (auth.password.equals(password)) {
            return new Subject(auth.readOnly, Collections.singleton(new JMXPrincipal(userName)), 
                Collections.EMPTY_SET, Collections.EMPTY_SET);
        }
        throw new SecurityException("Invalid Authentication!");
    }
    class Auth {
        String password;
        boolean readOnly;
        Auth(String password, boolean readOnly) {
            this.password = password;
            this.readOnly = readOnly;
        }
    }
}
// Client
String[] credentials = new String[] { "username" , "password" }; 
env.put(JMXConnectorServer.AUTHENTICATOR, credentials);
env.put("jmx.remote.sasl.callback.handler", new CallbackHandler() {...}); 
JMXConnector conn = JMXConnectorFactory.connect(serviceURL, env);
协议扩展
// Server
Security.addProvider(new com.sun.security.sasl.Provider());
env.put("jmx.remote.profiles", "TLS SASL/PLAIN"); 
env.put("jmx.remote.x.access.file", "config" + File.separator + "access.properties"); 
JMXServiceURL url = new JMXServiceURL("jmxmp", null, 5555); 
JMXConnectorServer cs = JMXConnectorServerFactory.newJMXConnectorServer(url, env, beanServer); 
cs.start(); 
// Client
Security.addProvider(new com.sun.security.sasl.Provider());
env.put("jmx.remote.profiles", "TLS SASL/PLAIN"); 
JMXServiceURL url = new JMXServiceURL("jmxmp", null, port); 
JMXConnector jmxc = JMXConnectorFactory.connect(url, env); 
详细步骤可见: https://docs.oracle.com/cd/E19698-01/816-7609/6mdjrf86r/index.html
监听
主要用于 JMX 连接状态和 MXBean 调用的监听。
通过实现 javax.management.NotificationListener 创建监听处理器,加入到 NotificationBroadcaster 的实现类下。
jmxConnector.addNotificationListener((notification, handback) -> {...}, null, handback);
如果需要自定义监听广播可以继承 NotificationBroadcasterSupport 或 NotificationEmitterSupport,其内部已经实现了 addNotificationListener、removeNotificationListener 方法。