JMX(Java Management Extensions)是Java平台上为应用程序、设备、系统等植入管理功能的框架。JMX可以跨越一系列异构操作系统平台、系统体系结构和网络传输协议,灵活的开发无缝集成的系统、网络和服务管理应用。
结构
JMX Jconsole以及Web Browser属于应用层,我们可以不用关注:
- 分布层(Distributed layer):即图中RMI,HTTP/SOAP,HTTP/HTML,SNMP,这层通常被封装成适配器(Adapter),将JMX信息封装成调用接口提供给外层
- 代理层(Agent layer):即图中的MBeanServer,负责MBeans的注册管理
- 仪表层(Instrumentation layer):即图最下方的MBeans,每个MBean都是一个被管理的资源,通过对外暴露的方法和属性,实现被上层获取和调用
远程连接
如果要启用远程连接,需要在启动应用时添加以下参数:
1 2 3 4
| -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=8008 // jmx远程端口 -Dcom.sun.management.jmxremote.authenticate=false // 不启用认证 -Dcom.sun.management.jmxremote.ssl=false // 不使用SSL连接
|
连接Tomcat配置
可以直接修改catalina.sh
,也可以修改setenv.sh
(推荐):
摘自catalina.sh
Do not set the variables in this script. Instead put them into a script setenv.sh in CATALINA_BASE/bin to keep your customizations separate.
无需认证配置
1 2 3 4
| CATALINA_OPTS="$CATALINA_OPTS -Dcom.sun.management.jmxremote.port=7431 // jmx远程端口 -Dcom.sun.management.jmxremote.authenticate=false // 不启用认证 -Dcom.sun.management.jmxremote.ssl=false // 不使用SSL连接 -Djava.rmi.server.hostname=192.168.80.13" // 本机IP
|
认证配置
1 2 3 4 5 6
| CATALINA_OPTS="$CATALINA_OPTS -Dcom.sun.management.jmxremote.port=7431 // jmx远程端口 -Dcom.sun.management.jmxremote.authenticate=true // 启用认证 -Dcom.sun.management.jmxremote.ssl=false // 不使用SSL连接 -Djava.rmi.server.hostname=192.168.80.13 // 本机IP -Dcom.sun.management.jmxremote.acccess.file=/home/vv-dev/jconsole/jmxremote.access // 授权用户配置文件路径 -Dcom.sun.management.jmxremote.password.file=/home/vv-dev/jconsole/jmxremote.password" // 授权用户密码配置文件路径
|
jmxremote.access&jmxremote.password
两个文件模板可以在../jdk/jre/lib/management
路径下找到
此处需要对这两个文件变更权限:
jmxremote.access
文件中配置了用户名,以及对应的权限:
1 2 3 4
| monitorRole readonly // 只读,监控权限配置这个即可 controlRole readwrite \ // 读写权限 create javax.management.monitor.*,javax.management.timer.* \ unregister
|
jmxremote.password
文件中配置了用户名,以及对应的密码:
1 2
| monitorRole QED controlRole R&D
|
配置完成后需要重启服务,通过jconsole
,使用配置的用户名(monitorRole
)和密码(QED
)即可连接服务
获取示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public class JmxSimpleTest {
public static void main(String[] args) throws Exception { String host = "192.168.80.13:7431"; String jmxURL = "service:jmx:rmi:///jndi/rmi://" + host + "/jmxrmi"; JMXServiceURL serviceURL = new JMXServiceURL(jmxURL);
try (JMXConnector jMXConnector = JMXConnectorFactory.connect(serviceURL)) { MBeanServerConnection mbsc = jMXConnector.getMBeanServerConnection(); ClassLoadingMXBean classLoadingMXBean = ManagementFactory.getPlatformMXBean(mbsc, ClassLoadingMXBean.class); System.out.println("LoadedClassCount:" + classLoadingMXBean.getLoadedClassCount()); System.out.println("TotalLoadedClassCount:" + classLoadingMXBean.getTotalLoadedClassCount()); System.out.println("UnloadedClassCount:" + classLoadingMXBean.getUnloadedClassCount()); } } }
|
在ManagementFactory.java
中定义了一些我们可以直接获取到的jvm信息的类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| public final static String CLASS_LOADING_MXBEAN_NAME = "java.lang:type=ClassLoading";
public final static String COMPILATION_MXBEAN_NAME = "java.lang:type=Compilation";
public final static String MEMORY_MXBEAN_NAME = "java.lang:type=Memory";
public final static String OPERATING_SYSTEM_MXBEAN_NAME = "java.lang:type=OperatingSystem";
public final static String RUNTIME_MXBEAN_NAME = "java.lang:type=Runtime";
public final static String THREAD_MXBEAN_NAME = "java.lang:type=Threading";
public final static String GARBAGE_COLLECTOR_MXBEAN_DOMAIN_TYPE = "java.lang:type=GarbageCollector";
public final static String MEMORY_MANAGER_MXBEAN_DOMAIN_TYPE= "java.lang:type=MemoryManager";
public final static String MEMORY_POOL_MXBEAN_DOMAIN_TYPE= "java.lang:type=MemoryPool";
|
对比jconsole
,通过上面这种方式获取到的信息有限,例如OperatingSystem
只能获取到处理器数量等基本信息,无法获取到像是文件描述符数,交换空间大小等信息:
好在jmx还提供了另外一种方法MBeanServerConnection.getAttributes()
:
1 2 3 4 5
| ObjectName objectName = new ObjectName("java.lang:type=OperatingSystem"); AttributeList attributeList = mbsc.getAttributes(objectName, new String[] { "MaxFileDescriptorCount", "TotalSwapSpaceSize" }); for (Attribute attribute : attributeList.asList()) { System.out.println(attribute.getName() + ": " + attribute.getValue()); }
|
通过这种方法,我们也可以获取tomcat相关信息:
1 2 3 4 5
| ObjectName objectName = new ObjectName("Catalina:name=\"http-nio2-20041\",type=GlobalRequestProcessor"); MBeanInfo mBeanInfo = mbsc.getMBeanInfo(objectName); for (MBeanAttributeInfo attribute : mBeanInfo.getAttributes()) { System.out.println(attribute.getName() + ": " + mbsc.getAttribute(objectName, attribute.getName())); }
|
创建自定义MBean
假如现在要定义一个计数器Counter,按照惯例,Counter对应的MBean需要以MBean结尾:
1 2 3 4 5 6 7 8
| public interface CounterMBean {
long getCount();
void setCount(long count); void incr(); }
|
CounterMBean
中定义的方法,都是可以提供给远程调用的,现在做下实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class Counter implements CounterMBean {
private AtomicLong counter = new AtomicLong(0);
@Override public long getCount() { return counter.get(); }
@Override public void setCount(long count) { counter.set(count); }
@Override public void incr() { counter.incrementAndGet(); } }
|
接下来我们需要在MbeanServer
中注册CounterMBean
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public class CounterJmxRegisterTest { public static void main(String[] args) throws Exception {
Counter counter = new Counter();
MBeanServer server = ManagementFactory.getPlatformMBeanServer(); ObjectName objectName = new ObjectName("test.jmx:name=counter"); server.registerMBean(counter, objectName);
int rmiPort = 1099; LocateRegistry.createRegistry(rmiPort); JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:" + rmiPort + "/jmxrmi"); JMXConnectorServer jcs = JMXConnectorServerFactory.newJMXConnectorServer(url, null, server); jcs.start();
while (true) { counter.incr(); System.out.println(counter.getCount()); Thread.sleep(5000); } } }
|
ObjectName
定义规则为“域:键值对(可以多个)
”,域
的值可以任意,示例中为test.xyz
,:
后为多组键值对(在jconsole中以嵌套文件夹体现),如a=abc
或者a=a,b=b
运行后使用jconsole连接:
可以看到CounterMBean
已经注册了,并且提供了一个可执行的方法incr
,以及可修改的属性Count
,因为我们对Count
实现了get/set方法,所以显示的的可读/可写都是true
修改MBean属性值和调用方法
除了获取MBean的属性值外,还可以修改MBean的属性值和调用接口方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| public class CounterJmxTest {
public static void main(String[] args) throws Exception { String jmxURL = "service:jmx:rmi:///jndi/rmi://127.0.0.1:1099/jmxrmi"; JMXServiceURL serviceURL = new JMXServiceURL(jmxURL); try (JMXConnector jMXConnector = JMXConnectorFactory.connect(serviceURL)) { MBeanServerConnection mbsc = jMXConnector.getMBeanServerConnection();
ObjectName objectName = new ObjectName("test.jmx:name=counter"); long countValue = (long) mbsc.getAttribute(objectName, "Count"); System.out.println("CountValue:" + countValue);
mbsc.setAttribute(objectName, new Attribute("Count", 1024)); countValue = (long) mbsc.getAttribute(objectName, "Count"); System.out.println("CountValue:" + countValue); mbsc.invoke(objectName, "incr", null, null); mbsc.invoke(objectName, "incr", null, null); mbsc.invoke(objectName, "incr", null, null); countValue = (long) mbsc.getAttribute(objectName, "Count"); System.out.println("CountValue:" + countValue); } } }
|
通知(Notification)
Notification
起到了在MBean之间沟通桥梁的作用,Notification
模型和Java Event
模型类似,将一些重要的信息,状态的转变,数据的变更传递给Notification Listener
,以便资源的管理。Notification
由四部分组成:
1 2 3 4
| Notification:通用来说就是需要发送的消息,可以被直接使用,也可以根据传递的事件的需要而被扩展。 NotificationListener:用于监听广播出来的通知信息 NotificationFilter:为监听者提供了一个过滤通知的过滤器,过滤掉不需要的通知 NotificationBroadcaster:通知发送者需实现此接口,发送需要传递的信息
|
修改Counter
,添加一个通知:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| public class Counter extends NotificationBroadcasterSupport implements CounterMBean {
private AtomicLong counter = new AtomicLong(0);
private long sequenceNumber = 1;
@Override public long getCount() { return counter.get(); }
@Override public void setCount(long count) { Notification n = new Notification( "setCount", this, sequenceNumber++, System.currentTimeMillis(), "Count is setted, current: " + count); sendNotification(n);
counter.set(count); }
@Override public void incr() { counter.incrementAndGet(); } }
|
创建监听器:
1 2 3 4 5 6 7 8 9 10
| public class CounterListener implements NotificationListener {
@Override public void handleNotification(Notification notification, Object handback) {
System.out.println(notification.getMessage()); }
|
在注册MBean后,注册监听器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| public class CounterJmxRegisterTest { public static void main(String[] args) throws Exception {
Counter counter = new Counter();
MBeanServer server = ManagementFactory.getPlatformMBeanServer(); ObjectName objectName = new ObjectName("test.jmx:name=counter"); server.registerMBean(counter, objectName);
server.addNotificationListener(objectName, new CounterListener(), null, counter);
int rmiPort = 1099; LocateRegistry.createRegistry(rmiPort); JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:" + rmiPort + "/jmxrmi"); JMXConnectorServer jcs = JMXConnectorServerFactory.newJMXConnectorServer(url, null, server); jcs.start();
while (true) { counter.incr(); System.out.println(counter.getCount()); Thread.sleep(5000); } } }
|
当调用Counter.setCount
时,控制台输出:
1 2 3
| 1 Count is setted, current: 1024 1028
|
参考
JMX远程监控JVM
Deep Dive into Java Management Extensions (JMX)
从零开始玩转JMX
JMX超详细解读
JMX monitoring + Java custom metrics.