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 方法。