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 的静态方法便可以获取。

远程

远程模式可以通过建立与其他虚拟机的连接后对其进行操作。

  1. 建立连接
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();
}
  1. 监听管理
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)。

注册的方法也是有标准的,以 setget 开头并且参数规范的方法将会被隐藏,对于参数只支持 基本类型、数组、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);

如果需要自定义监听广播可以继承 NotificationBroadcasterSupportNotificationEmitterSupport,其内部已经实现了 addNotificationListener、removeNotificationListener 方法。