参考文章:
《JMX超详细解读》
《JMX》
写在开头:本文为学习后的总结,可能有不到位的地方,错误的地方,欢迎各位指正。
在学习tomcat源码架构的时候了解到其中使用了JMX来实现一些管理工作,于是便整理了这篇文章出来。
目录
一、JMX入门
1、JMX概念
2、JMX的架构
1、基础层
2、适配层
3、接入层
二、JMX的使用
1、使用Jconsole访问
1.1、资源注册
1.2、启动监控
2、通过JMX提供的工具页访问
3、通过客户端程序进行远程访问
三、补充
常用的 MBean
一、JMX入门
1、JMX概念
JMX(Java Management Extensions)是一个为应用程序植入管理功能的框架。JMX是一套标准的代理和服务,实际上,用户可以在任何Java应用程序中使用这些代理和服务实现管理。
个人理解其实就是Java 管理扩展,用来管理和监测 Java 程序。最常用到的就是对于 JVM 的监测和管理,比如 JVM 内存、CPU 使用率、线程数、垃圾收集情况等等。
比如${JAVA_HOME}的/bin目录下的jconsole,正式利用的JMX实现的资源监控。
2、JMX的架构
整个架构可以由底向上分为基础层(探测层)、适配层(代理层)、接入层(远程管理层),下面我们来分别介绍下。
1、基础层
这其中主要是被管理的资源MBean,MBean主要有四种,Standard MBean、Dynamic MBean、Open MBean、Model MBean,源码主要在 java.lang.management 和 javax.management包里面。
Standard MBean这种类型的MBean最简单,它能管理的资源(包括属性,方法,时间)必须定义在接口中,然后MBean必须实现这个接口。它的命名也必须遵循一定的规范,例如我们的MBean为Hello,则接口必须为HelloMBean。
Dynamic MBean必须实现javax.management.DynamicMBean接口,所有的属性,方法都在运行时定义。另外还有两类 MBean:Open MBean 和 Model MBean,实际上它们也都是动态 MBean。
2、适配层
MBeanServer 是负责管理 MBean 的,一般一个 JVM 只有一个 MBeanServer,所有的 MBean 都要注册到 MBeanServer 上,并通过 MBeanServer 对外提供服务。一般用 ManagementFactory.getPlatformMBeanServer()方法获取当前 JVM 内的 MBeanServer。
3、接入层
写好的 MBean 注册到 MBeanServer 上之后,功能已经具备了。适配器和连接器就是将这些功能开放出来的方式。比如 HTTP协议适配器,就是将功能以 HTTP 协议开放出去,这样我们就可以在浏览器使用了。但是 JDK 只是提供了适配器的实现标准,并没有具体的实现,比较常用的是 HtmlAdaptorServer,需要 jmxtools.jar 包的支持。
二、JMX的使用
以下内容参考自《JMX超详细解读》
1、使用Jconsole访问
1.1、资源注册
首先定义一个MBean接口,接口的命名规范为以具体的实现类为前缀
public interface HelloMBean
{
public String getName();
public void setName(String name);
public String getAge();
public void setAge(String age);
public void helloWorld();
public void helloWorld(String str);
public void getTelephone();
}
然后定义一个实现类,实现上面的接口:
/*
* 该类名称必须与实现的接口的前缀保持一致(即MBean前面的名称
*/
public class Hello implements HelloMBean
{
private String name;
private String age;
public void getTelephone()
{
System.out.println("get Telephone");
}
public void helloWorld()
{
System.out.println("hello world");
}
public void helloWorld(String str)
{
System.out.println("helloWorld:" + str);
}
public String getName()
{
System.out.println("get name 123");
return name;
}
public void setName(String name)
{
System.out.println("set name 123");
this.name = name;
}
public String getAge()
{
System.out.println("get age 123");
return age;
}
public void setAge(String age)
{
System.out.println("set age 123");
this.age = age;
}
}
最后进行注册,实现代理层
import java.lang.management.ManagementFactory;
import javax.management.JMException;
import javax.management.MBeanServer;
import javax.management.ObjectName;
public class HelloAgent
{
public static void main(String[] args) throws JMException, Exception
{
// 通过工厂类获取MBeanServer,用来做MBean的容器
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
// 创建一个ObjectName对象,注意取名规范
// 格式为:“域名:name=MBean名称”,域名和MBean的名称可以任意
ObjectName helloName = new ObjectName("jmxBean:name=hello");
// 将Hello这个类注入到MBeanServer中
server.registerMBean(new Hello(), helloName);
Thread.sleep(60*60*1000);
}
}
这样,一个简单的JMX的DEMO已经写完了,现在我们通过JDK提供的Jconsole来进行操作。
1.2、启动监控
JConsole 是 JDK 自带的工具,在${JAVA_HOME}的 bin 目录下,启动即可。
启动JConsole后可以看到会显示本地的java进程,包括我们正在运行的HelloAgent与JConsole本身。
打开我们的本地进程HelloAgent,点击属性会自动调用get方法:
在这个界面上,我们可以调用其中的方法来给属性赋值
注意ObjectName中的取名是有一定规范的,格式为:“域名:name=MBean名称”,其中域名和MBean的名称可以任意取。这样定义后,就可以唯一标识我们定义的这个MBean的实现类了。
2、通过JMX提供的工具页访问
复用上面的接口和实现类,只需要改动适配层,这里需要到导入外部jar包jdmk,注册工具页面访问适配器,而这个适配器本身也是个MBean 。
package jmx;
import java.lang.management.ManagementFactory;
import javax.management.JMException;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import com.sun.jdmk.comm.HtmlAdaptorServer;
public class HelloAgent
{
public static void main(String[] args) throws JMException, Exception
{
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
ObjectName helloName = new ObjectName("jmxBean:name=hello");
//create mbean and register mbean
server.registerMBean(new Hello(), helloName);
ObjectName adapterName = new ObjectName("HelloAgent:name=htmladapter,port=8082");
// 注册工具页面访问适配器,不难发现这也是个MBean
HtmlAdaptorServer adapter = new HtmlAdaptorServer();
server.registerMBean(adapter, adapterName);
adapter.start();
}
}
如果缺少HtmlAdaptorServer类的可以在pom里添加jmxtools的引用。
<dependency>
<groupId>com.sun.jdmk</groupId>
<artifactId>jmxtools</artifactId>
<version>1.2.1</version>
</dependency>
代理类运行后就可以通过地址http://localhost:8082来访问管路页面
点击name=hello,就进入到了hello这个MBean的管理页面,这里提供了与JConsole类似的管理功能。
3、通过客户端程序进行远程访问
这里需要对agent进行修改,增加ip和porta绑定部分的逻辑
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import javax.management.JMException;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;
public class HelloAgent
{
public static void main(String[] args) throws JMException, NullPointerException
{
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
ObjectName helloName = new ObjectName("jmxBean:name=hello");
//create mbean and register mbean
server.registerMBean(new Hello(), helloName);
try
{
//这个步骤很重要,注册一个端口,绑定url后用于客户端通过rmi方式连接JMXConnectorServer
LocateRegistry.createRegistry(9999);
//URL路径的结尾可以随意指定,但如果需要用Jconsole来进行连接,则必须使用jmxrmi
JMXServiceURL url = new JMXServiceURL
("service:jmx:rmi:///jndi/rmi://localhost:9999/jmxrmi");
JMXConnectorServer jcs = JMXConnectorServerFactory.newJMXConnectorServer(url, null, server);
System.out.println("begin rmi start");
jcs.start();
System.out.println("rmi start");
}
catch (RemoteException e)
{
e.printStackTrace();
}
catch (IOException e)
{
e.printStackTrace();
}
}}
如果没有client进行远程连接,可以使用Jconsole进行远程访问:
当然我们也可以自己写一个客户端Client程序,用于与agent进行远程连接:
import java.io.IOException;
import javax.management.Attribute;
import javax.management.MBeanServerConnection;
import javax.management.MBeanServerInvocationHandler;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
public class Client
{
public static void main(String[] args) throws IOException, Exception, NullPointerException
{
JMXServiceURL url = new JMXServiceURL
("service:jmx:rmi:///jndi/rmi://localhost:9999/jmxrmi");
JMXConnector jmxc = JMXConnectorFactory.connect(url,null);
MBeanServerConnection mbsc = jmxc.getMBeanServerConnection();
//ObjectName的名称与前面注册时候的保持一致
ObjectName mbeanName = new ObjectName("jmxBean:name=hello");
System.out.println("Domains ......");
String[] domains = mbsc.getDomains();
for(int i=0;i<domains.length;i++)
{
System.out.println("doumain[" + i + "]=" + domains[i] );
}
System.out.println("MBean count = " + mbsc.getMBeanCount());
//设置指定Mbean的特定属性值
//这里的setAttribute、getAttribute操作只能针对bean的属性
//例如对getName或者setName进行操作,只能使用Name,需要去除方法的前缀
mbsc.setAttribute(mbeanName, new Attribute("Name","杭州"));
mbsc.setAttribute(mbeanName, new Attribute("Age","1990"));
String age = (String)mbsc.getAttribute(mbeanName, "Age");
String name = (String)mbsc.getAttribute(mbeanName, "Name");
System.out.println("age=" + age + ";name=" + name);
HelloMBean proxy = MBeanServerInvocationHandler.
newProxyInstance(mbsc, mbeanName, HelloMBean.class, false);
proxy.helloWorld();
proxy.helloWorld("migu");
proxy.getTelephone();
//invoke调用bean的方法,只针对非设置属性的方法
//例如invoke不能对getName方法进行调用
mbsc.invoke(mbeanName, "getTelephone", null, null);
mbsc.invoke(mbeanName, "helloWorld",
new String[]{"I'll connect to JMX Server via client2"}, new String[]{"java.lang.String"});
mbsc.invoke(mbeanName, "helloWorld", null, null);
}
}
对资源里面的方法进行操作有两种方式:一是通过代理直接调用方法;二是通过JAVA的反射注入的方式进行方法的调用。
三、补充
常用的 MBean
有些指标是监控会用到的,比如内存、CPU、堆空间、线程、类加载情况相关的 MBean。
JDK 提供了一个 ManagementFactory,帮助我们方便的获取常用的 MBean。可以到 java.lang.management 包下找到这个类看一下注释和代码。
OperatingSystemMXBean
可以获取操作系统相关的信息,机器名称、内存使用、CPU使用等信息。可通过 ManagementFactory.getOperatingSystemMXBean() 方式获取。
RuntimeMXBean
可以获取当前 JVM 的信息,包括 JVM 参数和 JVM 相关的系统参数。可以通过 ManagementFactory.getRuntimeMXBean()方式获取。
MemoryMXBean
可以获取当前 JVM 的内存使用,包括堆内存和非堆内存。可以通过 ManagementFactory.getMemoryMXBean()获取。
ThreadMXBean
获取 JVM 线程使用情况,包括活动线程、守护线程、线程峰值等。可以通过 ManagementFactory.getThreadMXBean() 获取。
ClassLoadingMXBean
获取 JVM 类加载情况,包括已加载类、未加载类等。可以通过 ManagementFactory.getClassLoadingMXBean() 获取。
GarbageCollectorMXBean
获取 JVM 垃圾收集器的情况,包括使用的哪种垃圾收集器以及回收次数等等。可以通过 ManagementFactory.getGarbageCollectorMXBeans() 获取,注意,这里获取到的是一个集合,因为垃圾收集器分为老年代和新生代两个。