JMX笔记整理

JMX的定义

JMX 全称为 Java Management Extensions,翻译过来就是 Java 管理扩展,用来管理和监测 Java 程序。最常用到的就是对于 JVM 的监测和管理,比如 JVM 内存、CPU 使用率、线程数、垃圾收集情况等等。另外,还可以用作日志级别的动态修改,比如 log4j 就支持 JMX 方式动态修改线上服务的日志级别。最主要的还是被用来做各种监控工具,比如文章开头提到的 Spring Boot Actuator、JConsole、VisualVM 等。

JMX 既是 Java 管理系统的一个标准,一个规范,也是一个接口,一个框架。有标准、有规范是为了让开发者可以定制开发自己的扩展功能,而且作为一个框架来讲,JDK 已经帮我们实现了常用的功能,尤其是对 JVM 的监控和管理。

JMX架构

img

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

  • 基础层
  • 适配层
  • 接入层

基础层

基础层主要是MBean,被管理的资源。

JMX 是通过各种 MBean(Managed Bean) 传递消息的,MBean 其实就是我们经常说的 Java Bean,只不过由于它比较特殊,所以称之为 MBean。既然是个 Bean,里面就是一些属性和方法,外界就可以获取被管理的资源的状态和操纵MBean的行为。JMX 中共有四种类型的 MBean,分别是 Standard MBean, Dynamic MBean, Open MBean, Model MBeanJDK 提供的 MBean 主要在 java.lang.managementjavax.management包里面。

Standard MBean

Standard MBean 就是普通的 Java Bean 没有区别,它也是 JMX 中最简单、使用最多的一种。主要在java.lang.management包里面。

它能管理的资源(包括属性,方法,时间)必须定义在接口中,然后MBean必须实现这个接口。它的命名也必须遵循一定的规范,例如我们的MBean为Hello,则接口必须为HelloMBean。

Dynamic MBean

Dynamic MBean 其实是一种妥协的产物,由于已经存在一些这种 MBean,而将其改造成标准 MBean 比较费力而且不切实际,所以就有了动态 MBean。Dynamic MBean 的接口在 javax.management.DynamicMBean这里,里面定义一些接口方法,比如动态获取属性、设置属性等。

Dynamic MBean 必须实现javax.management.DynamicMBean接口,所有的属性,方法都在运行时定义。

Open MBean

Open MBeanDynamic MBean 的一种。

Open MBean 与其它动态 MBean 的唯一区别在于,前者对其公开接口的参数和返回值有所限制 —— 只能是基本类型或者 javax.management.openmbean包内的 ArrayTypeCompositeTypeTarbularType 等类型。这主要是考虑到管理系统的分布,很可能远端管理系统甚至 MBServer 层都不具有 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中。

适配层

MBeanServer

MBeanServer 主要是提供对资源的注册和管理。一般一个 JVM 只有一个 MBeanServer,所有的 MBean 都要注册到 MBeanServer 上,并通过 MBeanServer 对外提供服务。一般用 ManagementFactory.getPlatformMBeanServer()方法获取当前 JVM 内的 MBeanServer

接入层

接入层提供远程访问的入口。

适配器和连接器

写好的 MBean 注册到 MBeanServer 上之后,功能已经具备了。适配器连接器就是将这些功能开放出来的方式。 比如 HTTP协议适配器,就是将功能以 HTTP 协议开放出去,这样我们就可以在浏览器使用了。但是 JDK 只是提供了适配器的实现标准,并没有具体的实现,比较常用的是 HtmlAdaptorServer,需要 jmxtools.jar 包的支持。

连接器是各种客户端最常用的,JDK 提供的默认连接器是 RMI 连接器,JConsoleVisualVM 都是使用它。

实现并使用一个 MBean

定义 MBean 接口 和实体类

// 接口
public interface UserMBean {

    String getName();

    String getPassword();

    String getPhone();

    void say();
}
//接口命名规范:实体类名+MBean
public class User implements UserMBean {

    @Override
    public String getName() {
        return "风筝";
    }

    @Override
    public String getPassword() {
        return "密码不可见";
    }

    @Override
    public String getPhone() {
        return "18900000000";
    }

    @Override
    public void say() {
        System.out.println("Hello JMX");
    }
}

将定义好的 MBean 注册到 MBeanServer

public static void main(String[] args) throws Exception {
        //获取MBeanServer。
        MBeanServer server = ManagementFactory.getPlatformMBeanServer();
        //定义对象名称。
        //ObjectName 是 MBean 的唯一标示,一个 MBeanServer 不能有重复。完整的格式「自定义命名空间:type=自定义类型,name=自定义名称」。当然可以只声明 type ,不声明 name。
        ObjectName userName = new ObjectName("FengZheng:type=customer,name=customerUserBean");
        //注册MBean
        server.registerMBean(new User(), userName);
  
        try {
            //这个步骤很重要,注册一个端口,绑定url后用于客户端通过rmi方式连接JMXConnectorServer
            LocateRegistry.createRegistry(8999);
            //URL路径的结尾可以随意指定,但如果需要用Jconsole来进行连接,则必须使用jmxrmi
            JMXServiceURL url = new JMXServiceURL
                    ("service:jmx:rmi:///jndi/rmi://localhost:8999/jmxrmi");
            //Connector
            JMXConnectorServer jcs = JMXConnectorServerFactory.newJMXConnectorServer(url, null, server);
            System.out.println("begin rmi start");
            //启动Server
            jcs.start();
            System.out.println("rmi start");
        } catch (RemoteException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        Thread.sleep(60 * 60 * 1000);
}

使用JMX

JConsole

img image-20210817220321948 image-20210817220420535

使用 RMI 方式连接

RMI 一般是用来连接远程服务的,当然本地进程也可以。这也是实现连接远程服务客户端的第一步。我们在注册 MBean 的时候,有没有注意到注册完成后,还有一大段代码,那段代码就是用来开启 RMI 连接的,开启 8999 端口作为 RMI 访问端口,然后客户端就可以用固定的连接串连接了。

连接串的格式 service:jmx:rmi:///jndi/rmi://host:port/jmxrmi

public class Client {
    public static void main(String[] args) throws IOException, Exception, NullPointerException {
        //JMX地址
        String jmxUrl = "service:jmx:rmi:///jndi/rmi://localhost:8999/jmxrmi";
        monitor(jmxUrl);
    }

    public static void monitor(String url) throws Exception{
        //获得一个 JMXServiceURL对象
        JMXServiceURL jmxServiceURL = new JMXServiceURL(url);
        //获取 JMXConnector,连接 Server,
        JMXConnector jmxc = JMXConnectorFactory.connect(jmxServiceURL, null);
        //获取到  MBeanServerConnection
        MBeanServerConnection msc = jmxc.getMBeanServerConnection();
        //获取所有命名空间
        String[] domains = msc.getDomains();
        for (String domain : domains) {
            System.out.println(domain);
        }
    }
}

Html适配器

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 是 MBean 的唯一标示,一个 MBeanServer 不能有重复。完整的格式「自定义命名空间:type=自定义类型,name=自定义名称」。当然可以只声明 type ,不声明 name。
        ObjectName userName = new ObjectName("FengZheng:type=customer,name=customerUserBean");
        //注册MBean
        server.registerMBean(new User(), userName);
         //注册一个Adapter
         ObjectName adapterName = new ObjectName("UserAgent:name=htmladapter,port=8999");   
         HtmlAdaptorServer adapter = new HtmlAdaptorServer();   
         server.registerMBean(adapter, adapterName);  
         adapter.start();
    }
}

我们访问地址:[http://localhost:8999,点击name=customerUserBean:即可查看MBean。

MBean 的获取

正如各种工具里的 MBean 的树形展示方式一样, MBean 本身就是以这种层级关系存在的。

MBean 包含在 Domain 里,Domain 相当于是一套独立的空间,这个空间里可以定义各种 type,各种 name 的 ObjectName

通过 ObjectName 可以获取到 MBean 的各种信息,包括属性、操作、通知。

有些属性是简单数据类型,比如 int、long、double、String 类型,另外有些是比较复杂的,比方说 com.sun.management:type=HotSpotDiagnostic 的属性 DiagnosticOptions 就是 javax.management.openmbean.CompositeData 类型。还有的属性的数据类型是 javax.management.openmbean.TabularData。这些都要单独处理。

Notification

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

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

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

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

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

发送一个通用类型的通知,任何一个监听者都会得到该通知。因此,监听者需提供过滤器来选择所需要接受的通知。任何类型的MBean,标准的或动态的,都可以作为一个通知发送者,也可以作为一个通知监听者,或两者都是。

场景:日常打招呼的场景:jack与我偶遇,jack说:hi;我礼貌的回答:hello,jack。

package jmx;

/*
 * 接口名必须以MBean结尾
 */
public interface HelloMBean
{
    public String getName();   
    
    public void setName(String name);   
  
    public void printHello();   
  
    public void printHello(String whoName);
}
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;

public interface JackMBean
{
    public void hi();
}
package jmx;

import javax.management.Notification;
import javax.management.NotificationBroadcasterSupport;

/**
* Jack不仅实现了MBean接口,还继承了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);
    }

}

Listener:

package jmx;

import javax.management.Notification;
import javax.management.NotificationListener;

/**
 实现:NotificationListener
 handleNotification
*/
public class HelloListener implements NotificationListener
{
   /**
   handback ,通知发起者
   notification,通知消息
   */
    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();
        //注册 Hello,
        ObjectName helloName = new ObjectName("yunge:name=Hello");    
        Hello hello=new Hello();          
        server.registerMBean(hello, helloName);  
        //注册 Jack
        Jack jack = new Jack();
        server.registerMBean(jack, new ObjectName("jack:name=Jack"));
        //Jack 添加listener
        jack.addNotificationListener(new HelloListener(), null, hello);
        Thread.sleep(500000);
    }
}
接口
public interface NotificationListener extends java.util.EventListener   {
    public void handleNotification(Notification notification, Object handback);
}
public interface NotificationBroadcaster {

    public void addNotificationListener(NotificationListener listener,
                                        NotificationFilter filter,
                                        Object handback)
            throws java.lang.IllegalArgumentException;

    public void removeNotificationListener(NotificationListener listener)
            throws ListenerNotFoundException;

    public MBeanNotificationInfo[] getNotificationInfo();
}

public interface NotificationEmitter extends NotificationBroadcaster {
    public void removeNotificationListener(NotificationListener listener,
                                           NotificationFilter filter,
                                           Object handback)
            throws ListenerNotFoundException;
}

public class NotificationBroadcasterSupport implements NotificationEmitter {
  
}  

启用远程JMX

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

参考

https://zhuanlan.zhihu.com/p/166530442

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值