理解jmx
一、JMX的定义
Java 管理扩展(Java Management Extension,JMX)是从jdk1.4开始的,但从1.5时才加到jdk里面,并把API放到java.lang.management包里面。
如果一个 Java 对象可以由一个遵循 JMX 规范的管理器应用管理,那么这个Java 对象就可以称为一个可由 JMX 管理的资源。
要使一个 Java 对象可管理,则必须创建相应的 MBean 对象,并通过这些 MBean 对象管理相应的 Java 对象。当拥有 MBean 类后,需要将其实例化并注册到 MBeanServer 上。
相信很多做Java开发的同学都使用过JDK自带的 jconsole 或者 jvisualvm 监控过JVM的运行情况,但不知道有没有留意过它们会有一个MBean的功能/标签,通过MBean可以看到在JVM中运行的组件的一些属性和操作。JMX(Java Management Extensions),监控管理框架,通过使用JMX可以监控和管理应用程序。JMX最常见的场景是监控Java程序的基本信息和运行情况,任何Java程序都可以开启JMX,然后使用JConsole或Visual VM进行预览
在 Java 程序运行过程中,对 JVM 和系统的监测一直是开发人员在开发过程中所需要的,在 Java SE 5 之前,需要使用底层 JVM api 才能监测 Java 程序运行过程中 JVM 和系统的一些情况,相关的开发效率比较低。为了解决这个问题,Sun 在 Java SE 5 种发布了 JMX 用来管理监测 Java 程序。JMX 提供大量轻量级的监测 JVM 和运行中对象/线程的方式,从而提升了 Java 的管理监测能力。
JMX(Java Management Extensions)是一个为应用程序植入管理功能的框架。JMX是一套标准的代理和服务,实际上,用户可以在任何Java应用程序中使用这些代理和服务实现管理。JMX让程序有被管理的功能,例如你开发一个WEB网站,它是在24小时不间断运行,那么你肯定会对网站进行监控,如每天的UV、PV是多少;又或者在业务高峰的期间,你想对接口进行限流,就必须去修改接口并发的配置值。
应用场景:中间件软件WebLogic的管理页面就是基于JMX开发的,而JBoss则整个系统都基于JMX构架。
对于一些参数的修改,网上有一段描述还是比较形象的:
1、程序初哥一般是写死在程序中,到要改变的时候就去修改代码,然后重新编译发布。
2、程序熟手则配置在文件中(JAVA一般都是properties文件),到要改变的时候只要修改配置文件,但还是必须重启系统,以便读取配置文件里最新的值。
3、程序好手则会写一段代码,把配置值缓存起来,系统在获取的时候,先看看配置文件有没有改动,如有改动则重新从配置里读取,否则从缓存里读取。
4、程序高手则懂得物为我所用,用JMX把需要配置的属性集中在一个类中,然后写一个MBean,再进行相关配置。另外JMX还提供了一个工具页,以方便我们对参数值进行修改。
二、JMX架构图:
从图中我们可以看到,JMX的结构一共分为三层:
1、基础层:主要是MBean,Managed Beans 被管理的资源(MBean)代表了某种资源,MBean可以暴露一些接口,用于对MBean所代表的资源进行查看、修改等操作。
MBean分为如下四种,我接下来主要介绍standard MBean
类型 描述
standard MBean 这种类型的MBean最简单,它能管理的资源(包括属性,方法,时间)必须定义在接口中,然后MBean必须实现这个接口。它的命名也必须遵循一定的规范,例如我们的MBean为Hello,则接口必须为HelloMBean。
dynamic MBean 必须实现javax.management.DynamicMBean接口,所有的属性,方法都在运行时定义
open MBean 此MBean的规范还不完善,正在改进中
model MBean 与标准和动态MBean相比,你可以不用写MBean类,只需使用javax.management.modelmbean.RequiredModelMBean即可。RequiredModelMBean实现了ModelMBean接口,而ModelMBean扩展了DynamicMBean接口,因此与DynamicMBean相似,Model MBean的管理资源也是在运行时定义的。与DynamicMBean不同的是,DynamicMBean管理的资源一般定义在DynamicMBean中(运行时才决定管理那些资源),而model MBean管理的资源并不在MBean中,而是在外部(通常是一个类),只有在运行时,才通过set方法将其加入到model MBean中。后面的例子会有详细介绍
2、适配层:JMX agents (JMX代理)管理着一个或多个MBean,并将MBean暴露给客户端。JMX代理的核心组件是MBean Server,用于接受客户端的连接,并对客户端的请求做出响应。MBeanServer,主要是提供对资源的注册和管理。
3、接入层:提供远程访问的入口,Remoete Connectors 可以简单理解成客户端,最贴近用户,用于接受用户的请求,并将请求发送给JMX代理,然后将JMX代理的响应返回给用户。
举个例子,假设公司现在有一大堆粮食(有很多种类)需要管理,并设计了一套方案用来管理粮食。他们是这样设计的:用一类特殊的对象来管理这些粮食并且搭建了一个服务器,该服务器管理着这些粮食,而且还开发了一个对用户友好的程序供用户查看,购买各种各样的粮食。在这个例子中,用来表示粮食的对象就是MBean,而服务器就是JMX Aagent,对用户友好的程序其实就是客户端。
接下来会用程序来介绍三种访问JMX的方式:
三、JDK的小工具Jconsole访问
1、 首先定义一个MBean接口,接口的命名规范为以具体的实现类为前缀(这个规范很重要)
package jmx;
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();
}
2、定义一个实现类,实现上面的接口:
1 package jmx;
2
3 /*
4 * 该类名称必须与实现的接口的前缀保持一致(即MBean前面的名称
5 */
6 public class Hello implements HelloMBean
7 {
8 private String name;
9
10 private String age;
11
12 public void getTelephone()
13 {
14 System.out.println("get Telephone");
15 }
16
17 public void helloWorld()
18 {
19 System.out.println("hello world");
20 }
21
22 public void helloWorld(String str)
23 {
24 System.out.println("helloWorld:" + str);
25 }
26
27 public String getName()
28 {
29 System.out.println("get name 123");
30 return name;
31 }
32
33 public void setName(String name)
34 {
35 System.out.println("set name 123");
36 this.name = name;
37 }
38
39 public String getAge()
40 {
41 System.out.println("get age 123");
42 return age;
43 }
44
45 public void setAge(String age)
46 {
47 System.out.println("set age 123");
48 this.age = age;
49 }
53 }
3、定义agent层:
package jmx;
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 server = ManagementFactory.getPlatformMBeanServer();
ObjectName helloName = new ObjectName("jmxBean:name=hello");
//create mbean and register mbean
server.registerMBean(new Hello(), helloName);
Thread.sleep(60*60*1000);
}
}
1、其中第13行是通过工厂类获取MBeanServer,用来做MBean的容器 。
2、第14行中的ObjectName中的取名是有一定规范的,格式为:“域名:name=MBean名称”,其中域名和MBean的名称可以任意取。这样定义后,就可以唯一标识我们定义的这个MBean的实现类了。
3、第16行是将Hello这个类注入到MBeanServer中,注入需要创建一个ObjectName类
这样,一个简单的JMX的DEMO已经写完了,现在我们通过JDK提供的Jconsole来进行操作。
用Jconsole连接过程中会报错,本地连接“提示安全连接失败”,需要设置jvm启动参数
-Dcom.sun.management.jmxremote
-Djava.rmi.server.hostname=127.0.0.1
-Dcom.sun.management.jmxremote.port=8888
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=false
参数含义
-Dcom.sun.management.jmxremote 布尔 是否支持远程JMX访问,默认true
-Dcom.sun.management.jmxremote.port 数值 监听端口号,方便远程访问
-Dcom.sun.management.jmxremote.authenticate 布尔 是否需要开启用户认证,默认开启
-Dcom.sun.management.jmxremote.ssl 布尔 是否对连接开启SSL加密,默认开启
3.在这个界面上,我们可以给程序中HelloMBean的属性赋值,也可以调用其中的方法:
四、通过JMX提供的工具页访问
这里,我们复用上面的接口和实现类,只需要改动适配层,这里需要到导入外部jar包jdmk
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");
HtmlAdaptorServer adapter = new HtmlAdaptorServer();
server.registerMBean(adapter, adapterName);
adapter.start();
}
}
我们访问地址:http://localhost:8082,点击name=hello:
五、通过客户端程序进行远程访问
1、这里需要对agent进行修改,增加ip和porta绑定部分的逻辑
package jmxTest;
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程序,用于与agent进行远程连接:
HashMap<String, Object> prop = new HashMap<String, Object>();
prop.put(JMXConnector.CREDENTIALS, "jmx.Hello");
JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi");
JMXConnector conn = JMXConnectorFactory.connect(url, prop);
MBeanServerConnection mbsc = conn.getMBeanServerConnection();
ObjectName mbeanName = new ObjectName("com.jmx.demo9:name=jmx.Hello");
HelloMBean hello = JMX.newMBeanProxy(mbsc, mbeanName, HelloMBean.class);
hello.setName("World!");
hello.sayHello();
Notification
MBean之间的通信是必不可少的,Notification就起到了在MBean之间沟通桥梁的作用。JMX 的通知由四部分组成:
1、Notification这个相当于一个信息包,封装了需要传递的信息
2、Notification broadcaster这个相当于一个广播器,把消息广播出。
3、Notification listener 这是一个监听器,用于监听广播出来的通知信息。
4、Notification filiter 这个一个过滤器,过滤掉不需要的通知。这个一般很少使用。
这里我们使用日常打招呼的场景:jack与我偶遇,jack说:hi;我礼貌的回答:hello,jack。
package jmx;
/*
* 该类名称必须与实现的接口的前缀保持一致(即MBean前面的名称
*/
public class Hello implements HelloMBean
{
private String name;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public void printHello()
{
System.out.println("Hello World, " + name);
}
public void printHello(String whoName)
{
System.out.println("Hello , " + whoName);
}
}
package jmx;
/*
* 接口名必须以MBean结尾
*/
public interface HelloMBean
{
public String getName();
public void setName(String name);
public void printHello();
public void printHello(String whoName);
}
package jmx;
import javax.management.Notification;
import javax.management.NotificationBroadcasterSupport;
public class Jack extends NotificationBroadcasterSupport implements JackMBean
{
private int seq = 0;
public void hi()
{
//创建一个信息包
Notification notify =
//通知名称;谁发起的通知;序列号;发起通知时间;发送的消息
new Notification("jack.hi",this,++seq,System.currentTimeMillis(),"jack");
sendNotification(notify);
}
}
package jmx;
public interface JackMBean
{
public void hi();
}
这里的类Jack不仅实现了MBean接口,还继承了NotificationBroadcasterSupport。jack在这里创建并发送了一个消息包。
package jmx;
import javax.management.Notification;
import javax.management.NotificationListener;
public class HelloListener implements NotificationListener
{
public void handleNotification(Notification notification, Object handback)
{
if(handback instanceof Hello)
{
Hello hello = (Hello)handback;
hello.printHello(notification.getMessage());
}
}
}
package jmx;
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 server = ManagementFactory.getPlatformMBeanServer();
ObjectName helloName = new ObjectName("yunge:name=Hello");
Hello hello=new Hello();
server.registerMBean(hello, helloName);
Jack jack = new Jack();
server.registerMBean(jack, new ObjectName("jack:name=Jack"));
jack.addNotificationListener(new HelloListener(), null, hello);
Thread.sleep(500000);
}
}
linux下利用JMX监控Tomcat
利用JMX监控Tomcat,就是相当于部署在tomcat上的应用作为服务端,也就是被管理资源的对象。然后通过程序或者jconsole远程连接到该应用上来。远程连接需要服务器端提供ip和port。如果需要加密访问的话,还需要配置用户名、密码等参数。
主要是在tomcat下的文件catalina.sh中进行一些环境变量的配置配置:
-Dcom.sun.management.jmxremote=true 相关 JMX 代理侦听开关
-Djava.rmi.server.hostname 服务器端的IP
-Dcom.sun.management.jmxremote.port=29094 相关 JMX 代理侦听请求的端口
-Dcom.sun.management.jmxremote.ssl=false 指定是否使用 SSL 通讯
-Dcom.sun.management.jmxremote.authenticate=false 指定是否需要密码验证
这样就可以通过客户端或者jconsole对tomcat进行监控。
远程连接
毫无疑问,若想远程连接访问,肯定需要mBeanServer注册一个或多个端口,如rmi端口,http端口等。
2.1 rmi端口注册及访问
有两种方法,一种直接在代码里面指定rmi端口,并绑定,如下,此种方法需要使用客户端连接代码访问,另一种代码不用指定端口,之需把mbean注册到platformMBeanServer 里面,并在启动进程时加jmx参数指定,用这种方法可以通过jconsole,jvisualvm远程访问。
@Test
public void testJmxRmiRegist() throws Exception {
int rmiPort = 2222;
String jmxServerName = "com.dxz.study.TestJmxRmiRegist";
// jdkfolder/bin/rmiregistry.exe 9999
Registry registry = LocateRegistry.createRegistry(rmiPort);
MBeanServer mbs = MBeanServerFactory.createMBeanServer(jmxServerName);
System.out.println(mbs);
// mbs = MBeanServerFactory.createMBeanServer();
// 新建MBean ObjectName, 在MBeanServer里标识注册的MBean
ObjectName name = new ObjectName(jmxServerName + ":type=HelloWorld");
// HtmlAdaptorServer adapter = new HtmlAdaptorServer();
// 在MBeanServer里注册MBean, 标识为ObjectName(com.tenpay.jmx:type=Echo)
mbs.registerMBean(new HelloWorld(), name);
JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:" + rmiPort + "/" + jmxServerName);
System.out.println("JMXServiceURL: " + url.toString());
JMXConnectorServer jmxConnServer = JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbs);
jmxConnServer.start();
Thread.sleep(1000 * 60 * 10);
}
上面程序是新建了个mbeanserver,并通过rmi绑定到2222端口上,等待客户端连接。
2.1.2 通过jmx参数启动进程
JVMARGS="$JVMARGS -Dcom.sun.management.jmxremote.port=8888 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false"
通过这种把进程的jmx监控绑定指定的端口,即可在远端通过jconsole进行监控。
java进程自带的mbean
当我们在用jconsole、jvisualvm进行监控java进程时,通常都能看到cpu、内存、线程、垃圾收集等使用情况,其实数据都是通过jmx从jvm提供的一些mbean里面取的。主要如下:
ClassLoadingMXBean
ClassLoadMXBean 包括一些类的装载信息,比如有多少类已经装载 / 卸载(unloaded),虚拟机类装载的 verbose 选项(即命令行中的 Java – verbose:class 选项)是否打开,还可以帮助用户打开 / 关闭该选项。
CompilationMXBean
CompilationMXBean 帮助用户了解当前的编译器和编译情况,该 mxbean 提供的信息不多。
GarbageCollectorMXBean
相对于开放人员对 GC 的关注程度来说,该 mxbean 提供的信息十分有限,仅仅提供了 GC 的次数和 GC 花费总时间的近似值。但是这个包中还提供了三个的内存管理检测类:MemoryManagerMXBean,MemoryMXBean 和 MemoryPoolMXBean。
MemoryManagerMXBean
这个类相对简单,提供了内存管理类和内存池(memory pool)的名字信息。
MemoryMXBean
这个类提供了整个虚拟机中内存的使用情况,包括 Java 堆(heap)和非 Java 堆所占用的内存,提供当前等待 finalize 的对象数量,它甚至可以做 gc(实际上是调用 System.gc)。
MemoryPoolMXBean
该信息提供了大量的信息。在 JVM 中,可能有几个内存池,因此有对应的内存池信息,因此,在工厂类中,getMemoryPoolMXBean() 得到是一个 MemoryPoolMXBean 的 list。每一个 MemoryPoolMXBean 都包含了该内存池的详细信息,如是否可用、当前已使用内存 / 最大使用内存值、以及设置最大内存值等等。
OperatingSystemMXBean
该类提供的是操作系统的简单信息,如构架名称、当前 CPU 数、最近系统负载等。
RuntimeMXBean
运行时信息包括当前虚拟机的名称、提供商、版本号,以及 classpath、bootclasspath 和系统参数等等。
ThreadMXBean
在 Java 这个多线程的系统中,对线程的监控是相当重要的。ThreadMXBean 就是起到这个作用。ThreadMXBean 可以提供的信息包括各个线程的各种状态,CPU 占用情况,以及整个系统中的线程状况。从 ThreadMXBean 可以得到某一个线程的 ThreadInfo 对象。这个对象中则包含了这个线程的所有信息。
要获得这些信息,我们首先通过 java.lang.management.ManagementFactory这个工厂类来获得一系列的 MXBean。