JMX:Java程序监控的基石

一、什么是JMX?

JMX (Java Management Extensions)是一个为应用程序植入管理功能的框架 ,从Java 1.5开始引入到标准Java技术平台中。

JMX是Java管理系统的一个标准、规范,开发者可以据此定制开发自己的扩展功能;除了JDK自身实现了的一些扩展功能,第三方类库或中间件也对此进行扩展,比如spring、quartz-job、commons-pool2、tomcat。

JMX主要被用来监测和管理 Java 程序,包括对JVM 内存、CPU 、线程、垃圾收集情况等等。目前市面上的Java监控工具基本都用到了JMX,比如jconsole、jmc、jvisualvm、jprofiler、arthas等。

二、JMX架构

1.MBean

MBean(Managed Bean)即被管理的资源,本质上是一个Bean对象,它包含一些属性和方法,用来获取被管理的资源的状态和操纵资源的行为;JMX 中共有四种类型的 MBean,分别是 Standard MBean, Dynamic MBean, Open MBean, Model MBean。JDK 提供的 MBean 主要在 java.lang.management 和 javax.management包里面。

1.1 MBean类型
类型描述
Standard MBean这种类型的MBean最简单,它能管理的资源(包括属性,方法,时间)必须定义在接口中,然后MBean必须实现这个接口。它的命名也必须遵循一定的规范,例如我们的MBean为Hello,则接口必须为HelloMBean。
Dynamic MBean必须实现javax.management.DynamicMBean接口,所有的属性,方法都在运行时定义。Open MBean和Model MBean都属于Dynamic MBean。
Open MBeanOpen MBean 与其它动态 MBean 的唯一区别在于,前者对其公开接口的参数和返回值有所限制 —— 只能是基本类型或者 javax.management.openmbean包内的 ArrayType、CompositeType、TarbularType 等类型。这主要是考虑到管理系统的分布,很可能远端管理系统甚至 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中。
1.2 常用的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() 获取,注意,这里获取到的是一个集合,因为垃圾收集器分为老年代和新生代两个。

1.3 MBean和MXBean的区别

java.lang.management包下的MBean为什么是以MXBean结尾,而不是以MBean结尾?以MemoryUsage自定义对象为例,如果MBean服务器的客户端无法访问其属性,则无法管理资源信息。对于非Java语言的MBean服务器的客户端,问题更严重。MXBean框架会将自定义的对象转化为Java平台标准的class(基本类型及javax.management.openmbean包下的类)。JMX通过代理(JMX.newMBeanProxy)的方式兼容MBean和MXBean,两者使用上很相似。

MBean和MXBean的区别

比较MBeanMXBean
命名约定MBean接口必须以MBean结尾;实现类名称必须为MBean之前的部分(Thing->ThingMBean)MXBean接口以MXBean结尾或者在接口上增加@MXBean;实现类名称无特殊要求(ThatOne->ThingMXBean)
自定义对象不允许允许

:① com.sun.jmx.mbeanserver.Introspector#implementsMBean

2.MBeanServer

MBeanServer 是负责管理 MBean 的,一般一个 JVM实例(进程) 只有一个 MBeanServer,所有的 MBean 都要注册到 MBeanServer 上,并通过 MBeanServer 对外提供服务。一般用 java.lang.management.ManagementFactory#getPlatformMBeanServer方法获取当前 JVM 内的 MBeanServer。

3.适配器和连接器

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

4.JMX客户端

前面提到使用到JMX的诊断工具有,jconsole、jmc、jvisualvm、jprofiler、arthas,各工具直接实现方式相差比较大,下面对一些常用的诊断工具的实现机制简单的说明下。

工具实现机制优缺点说明
jconsole基于RMI协议监控本地进程依赖“图形化”桌面支持本地进程、远程进程;可以添加JMX连接或jstatd连接
jvisualvm基于RMI协议监控本地进程依赖“图形化”桌面支持本地进程、远程进程;可以添加JMX连接
arthasJava Agent不依赖“图形化”桌面仅支持本地进程(可以通过Arthas Tunnel服务间接管理远程Agent)

①jstatd:jstatd是jdk(位于%JAVA_HOME%/bin/jstatd.exe)提供的一个RMI Server应用程序,用于监控Java HotSpot VMs的创建和终止,并提供远程监控服务。

②Java Agent:Java Agent是Java 1.5开始提供的一种用于构建独立于应用程序的之外的代理程序。Java 1.5仅支持premain(运行在主程序之前)Agent;Java 1.6开始支持agentmain(运行在主程序之后,arthas采用的就是该方式)Agent。Java Agent用途也很广泛,像arthas、chaosblade混沌工程,具体实现了哪些功能,看看arthas说明文档就知道了,包括基于JMX MBean实现的功能以及在线重定义class、方法调用耗时分析等等。

三、JMX应用场景

1.dashboard监控面板

用于监测和管理JVM的常用资源,比如 JVM 内存、CPU 使用率、线程数、垃圾收集情况等等。

根据需要可以结合OSHI类库(基于JNA的本机操作系统和硬件信息库)一起使用;OSHI可以监控磁盘使用率、网络接口、计算机传感器等。

2.动态修改线上日志级别

以logback为例,只需在logback.xml配置文件中,增加<jmxConfigurator />单行配置即可启动JMX支持。[logback官方示例](

3.查看数据库连接池使用情况

使用过alibaba开源的数据库连接池druid都知道,它有着不错的监控页面,可以查看连接池使用情况、SQL执行历史及执行时间统计,但若是为了性能考虑切换为HikariCP数据库连接池哪?

  • 注册MBean
// 注册MBean com.zaxxer.hikari.pool.PoolBase#handleMBeans(com.zaxxer.hikari.pool.HikariPool, boolean)
hikariDataSource.setRegisterMbeans(true);
hikariDataSource.setPoolName("ceshi");
spring:
  datasource:
    hikari:
      register-mbeans: true
      pool-name: ceshi
  • 使用日志框架打印连接池使用情况
MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
// poolName为连接池name,默认规则为
ObjectName poolName = new ObjectName("com.zaxxer.hikari:type=Pool (ceshi)");
HikariPoolMXBean poolProxy = JMX.newMXBeanProxy(mBeanServer, poolName, HikariPoolMXBean.class);
// 获取激活的连接数
int idleConnections = poolProxy.getActiveConnections();

4.查看自定义连接池使用情况

例如以jedisPool连接池为例,它使用commons-pools框架实现,并注册到MBean服务器,你完全可以通过MBean浏览器查看redis连接池使用情况。

5.查看quartz-job任务执行情况

  • 启用JMX

以spring-boot为例,可以在application.yml配置文件中增加如下配置

spring:
  quartz:
    properties:
      org.quartz.scheduler.jmx.export: true
      # 可选    
      org.quartz.scheduler.jmx.objectName: "org.quartz.core.jmx:type=QuartzSchedulerMBean, name=ruoyi"     

6.通知告警

可通过任务轮询的方式定时获取资源状态并进行邮件、短信、钉钉等通知。

quartz-job自动任务框架为例,QuartzSchedulerMBeanImpl增加了通知机制,我们可以使用jconsole工具“订阅”通知,也可以自己使用java代码订阅并处理。

  • 自定义代码监听通知
        MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
        ObjectName poolName = new ObjectName("org.quartz.core.jmx:type=QuartzSchedulerMBean, name=ruoyi");
        mBeanServer.addNotificationListener(poolName, (notification, handback) -> {
            // 发送邮件、短信、钉钉通知
            System.out.println(notification.toString());
        }, null, null);

四、自定义MBean

虽然 Java 提供了实现 MBean 的标准和规则,但平时我们几乎不需要开发 MBean。绝大多数的开发者接触到的也仅仅是使用 JDK 或者第三方定义好的 MBean,即便是第三方有实现 MBean,也是非常少的。我们知道的有 Tomcat 和 Spring Boot Actuator。

1.MBean接口方式

注意:此种方式不支持自定义对象。

  • HelloMBean
package com.example.demo.mbean;

public interface HelloMBean {
    String getName();

    void setName(String name);

    int getAge();

    void setAge(int age);

    void print();
}

  • Hello
package com.example.demo.mbean;

public class Hello implements HelloMBean {

    private String name;
    private int age;

    @Override
    public String getName() {
        return name;
    }

    @Override
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public int getAge() {
        return age;
    }

    @Override
    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public void print() {
        System.out.println(name);
    }
}

  • 注册MBean
package com.example.demo;

import com.example.demo.mbean.Hello;
import com.example.demo.mbean.HelloMBean;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import javax.management.*;
import java.lang.management.ManagementFactory;

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
        Hello hello = new Hello();
        hello.setName("张三");
        hello.setAge(20);
        // 注册MBean
        MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
        try {
            // 规范 域名(一般用包名):type=接口名,name=MBean名称
            ObjectName objectName = new ObjectName("com.example.demo.mbean:type=HelloMBean,name=hello");
            mBeanServer.registerMBean(hello, objectName);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T9lP2B9I-1652448276916)(JMX:Java程序监控的基石.assets/image-20220211112805961.png)]

2.MXBean接口方式

  • Address
package com.example.demo.mbean;

import lombok.Data;

@Data
public class Address {
    private String address;
    private double lng;
    private double lat;
}

  • HelloxMXBean
package com.example.demo.mbean;

public interface HelloxMXBean {
    String getName();

    void setName(String name);

    int getAge();

    void setAge(int age);

    Address getAddress();

    void setAddress(Address address);

    void print();
}

  • HelloxMXBean
package com.example.demo.mbean;

public class Hellox implements HelloxMXBean {

    private String name;
    private int age;
    private Address address;

    @Override
    public String getName() {
        return name;
    }

    @Override
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public int getAge() {
        return age;
    }

    @Override
    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public Address getAddress() {
        return address;
    }

    @Override
    public void setAddress(Address address) {
        this.address = address;
    }

    @Override
    public void print() {
        System.out.println(name);
    }
}

  • 注册MBean
package com.example.demo;

import com.example.demo.mbean.Address;
import com.example.demo.mbean.Hellox;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import javax.management.MBeanServer;
import javax.management.ObjectName;
import java.lang.management.ManagementFactory;

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
        Address address = new Address();
        address.setAddress("合肥");

        Hellox hellox = new Hellox();
        hellox.setName("张三");
        hellox.setAge(20);
        hellox.setAddress(address);
        // 注册MBean
        MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
        try {
            // 规范 域名(一般用包名):type=接口名,name=MBean名称
            ObjectName objectName = new ObjectName("com.example.demo.mbean:type=HelloMXBean,name=hellox");
            mBeanServer.registerMBean(hellox, objectName);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

3.Spring Bean方式

1.推荐以Spring Bean className和bean name均以MBean结尾,约定大于配置。

2.同MBean方式,不支持自定义对象。

  • HelloSpringMBean
package com.example.demo.mbean;

import org.springframework.jmx.export.annotation.*;

@ManagedResource(objectName = "com.example.demo.mbean:name=helloSpringMBean", description = "My Managed Bean")
public class HelloSpringMBean {

    private String name;
    private int age;

    @ManagedAttribute
    public String getName() {
        return name;
    }

    @ManagedAttribute
    public void setName(String name) {
        this.name = name;
    }

    @ManagedAttribute
    public int getAge() {
        return age;
    }

    @ManagedAttribute
    public void setAge(int age) {
        this.age = age;
    }

    @ManagedOperation(description = "print")
    public void print() {
        System.out.println(name);
    }
}

  • Bean注入
    @Bean
    public HelloSpringMBean helloSpringMBean() {
        HelloSpringMBean helloSpringMBean = new HelloSpringMBean();
        helloSpringMBean.setName("张三");
        helloSpringMBean.setAge(20);
        return helloSpringMBean;
    }

五、参考资料

面试官问我 JMX 了解不,我说:什么?

web 版 JVM 监控器-GitHub源码

arthas-Gitee源码

JMX简单入门

JMX的MBean和MXBean的区别

JMX NOTIFICATION

  • 1
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

搬山境KL攻城狮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值