JMX

最后更新:2019-03-15

1. 什么是JMX

JMX(Java Management Extensions)是一个为应用程序植入管理功能的框架。JMX是一套标准的代理和服务,实际上,用户可以在任何Java应用程序中使用这些代理和服务实现管理。

从图中我们可以看到,JMX的结构一共分为三层:

  • 基础层:主要是MBean,被管理的资源。
  • 适配层:MBeanServer,主要是提供对资源的注册和管理。
  • 接入层:提供远程访问的入口。

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. Jconsole访问

首先定义一个MBean接口,接口的命名规范为以具体的实现类为前缀

public interface HelloMBean {
    String getName();

    void setName(String name);

    String getAge();

    void setAge(String age);

    void helloWorld();

    void helloWorld(String str);

    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;
    }
}

定义agent层

public class HelloAgent {
    public static void main(String[] args) throws JMException, Exception {
        // 通过工厂类获取MBeanServer,用来做MBean的容器 。
        MBeanServer server = ManagementFactory.getPlatformMBeanServer();
        // ObjectName中的取名是有一定规范的,格式为:“域名:name=MBean名称”,
        // 其中域名和MBean的名称可以任意取。这样定义后,就可以唯一标识我们定义的这个MBean的实现类了。
        ObjectName helloName = new ObjectName("jmxBean:name=hello");
        // 将Hello这个类注入到MBeanServer中,注入需要创建一个ObjectName类
        server.registerMBean(new Hello(), helloName);
        Thread.sleep(60 * 60 * 1000);
    }
}

运行HelloAgent后,运行JConsole连接HelloAgent,可以给程序中HelloMBean的属性赋值,也可以调用其中的方法:

3. 通过JMX提供的工具页访问

引入jmdk

<repositories>
	<!--  版权問題,所以maven仓库基本都下架了jmxtool。-->
	<repository>
		<id>dist.wso2.org/maven2</id>
		<name>wso2.org Maven Repository</name>
		<url>http://dist.wso2.org/maven2/</url>
	</repository>
</repositories>

<dependencies>
	<dependency>
		<groupId>com.sun.jdmk</groupId>
		<artifactId>jmxtools</artifactId>
		<version>1.2.1</version>
	</dependency>
</dependencies>

修改HelloAgent

// 通过工厂类获取MBeanServer,用来做MBean的容器 。
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
// ObjectName中的取名是有一定规范的,格式为:“域名:name=MBean名称”,
// 其中域名和MBean的名称可以任意取。这样定义后,就可以唯一标识我们定义的这个MBean的实现类了。
ObjectName helloName = new ObjectName("jmxBean:name=hello");
// 将Hello这个类注入到MBeanServer中,注入需要创建一个ObjectName类
server.registerMBean(new Hello(), helloName);

// 在这里创建一个AdaptorServer,这个类将决定MBean的管理界面,这里用最普通的Html型界面。AdaptorServer其实也是一个MBean。
ObjectName adapterName = new ObjectName("HelloAgent:name=htmladapter,port=8082");
HtmlAdaptorServer adapter = new HtmlAdaptorServer();
server.registerMBean(adapter, adapterName);
adapter.start();

访问http://localhost:8082,点击name=hello:

4. 通过客户端程序进行远程访问

修改HelloAgent

// 通过工厂类获取MBeanServer,用来做MBean的容器 。
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
// ObjectName中的取名是有一定规范的,格式为:“域名:name=MBean名称”,
// 其中域名和MBean的名称可以任意取。这样定义后,就可以唯一标识我们定义的这个MBean的实现类了。
ObjectName helloName = new ObjectName("jmxBean:name=hello");
// 将Hello这个类注入到MBeanServer中,注入需要创建一个ObjectName类
server.registerMBean(new Hello(), helloName);

// 这个步骤很重要,注册一个端口,绑定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");

可以使用Jconsole进行远程访问,输入localhost:9999即可。

我们也可以自己实现客户端

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的反射注入的方式进行方法的调用。

5. Notification

MBean之间的通信是必不可少的,Notification就起到了在MBean之间沟通桥梁的作用。JMX 的通知由四部分组成:

  1. Notification这个相当于一个信息包,封装了需要传递的信息

  2. Notification broadcaster这个相当于一个广播器,把消息广播出。

  3. Notification listener 这是一个监听器,用于监听广播出来的通知信息。

  4. Notification filiter 这个一个过滤器,过滤掉不需要的通知。这个一般很少使用。

修改Hello

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);
    }
}

新建一个MBean,需要继承NotificationBroadcasterSupport

public interface NotifyMBean {
    void hi();
}

public class Notify extends NotificationBroadcasterSupport implements NotifyMBean {
    private int seq = 0;
    
    public void hi() {
        //创建一个信息包
        Notification notify =
                //通知名称;谁发起的通知;序列号;发起通知时间;发送的消息
                new Notification("edgar.hi", this, ++seq, System.currentTimeMillis(), "jack");
        sendNotification(notify);
    }
}

新建一个HelloListener

public class HelloListener implements NotificationListener {

    public void handleNotification(Notification notification, Object handback) {
        if (handback instanceof Hello) {
            Hello hello = (Hello) handback;
            hello.printHello(notification.getMessage());
        }
    }

}

注册

MBeanServer server = ManagementFactory.getPlatformMBeanServer();
ObjectName helloName = new ObjectName("yunge:name=Hello");
Hello hello=new Hello();
server.registerMBean(hello, helloName);
Notify notify = new Notify();
server.registerMBean(notify, new ObjectName("notify:name=Edgar"));
notify.addNotificationListener(new HelloListener(), null, hello);

6. 监控Java程序

想监控Java程序,需要在程序启动时加上JMX相关参数。

#相关 JMX 代理侦听开关
-Dcom.sun.management.jmxremote=true
#服务器端的IP
-Djava.rmi.server.hostname=localhost
#相关 JMX 代理侦听请求的端口
-Dcom.sun.management.jmxremote.port=9102
#指定是否需要密码验证
-Dcom.sun.management.jmxremote.authenticate=false 
#指定是否使用 SSL 通讯
-Dcom.sun.management.jmxremote.ssl=false

7. 参考资料

https://www.cnblogs.com/dongguacai/p/5900507.html

Edgar

Edgar
一个略懂Java的小菜比