JMX学习

1. 絮絮叨叨

1.1 Java程序包含哪些线程?

  • 使用Java进行多线程编程的人,多少可能都知道执行main()方法的是一个名为main的线程

  • 通过new Thread新建线程,若不指定线程名,默认线程名为Thread-xx。其中xx是从0开始的编号

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> System.out.println("I'm " + Thread.currentThread().getName())).start();
        }
    }
    
  • 为何默认线程名为Thread-xx,通过Thread类的对应构造函数就可以知道

    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
    
  • 其实,早在很久之前,自己就有过一个疑问:一个Java程序除了我们所知的main线程、自定义线程,是否还包含其他线程?

  • 据我目前掌握的知识,通过jstack命令可以实现

  • 在学习并发编程时,发现有一个非常好用的ThreadMXBean接口,就可以帮助我们了解Java程序究竟包含哪些线程

    public static void main(String[] args) {
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
        for (ThreadInfo threadInfo : threadInfos) {
            System.out.println("[" + threadInfo.getThreadId() + "]" + " " + threadInfo.getThreadName());
        }
    }
    
  • 获取的线程信息如下,线程10和5,根据自己的经验,应该是idea在运行Java程序时自己添加的线程,以实现监听、响应程序中断

1.2 关于ThreadMXBean接口

  • ThreadMXBean是接口,通过ManagementFactory获取到的肯定是ThreadMXBean接口的实现类的一个实例对象

  • 这里使用了Java的多态,将实现类的对象赋值给接口引用

  • ThreadImpl就是ThreadMXBean接口一个实现类

    public static ThreadMXBean getThreadMXBean() {
        return ManagementFactoryHelper.getThreadMXBean();
    }
    
    public static synchronized ThreadMXBean getThreadMXBean() {
        if (threadMBean == null) {
            threadMBean = new ThreadImpl(jvm);
        }
    
        return threadMBean;
    }
    

其他使用场景

  • 通过ThreadMXBean除了可以获取Java程序中的线程信息,还支持很多其他有用的操作
    • 获取程序中死锁的线程ID:findDeadlockedThreads()findMonitorDeadlockedThreads()Java ThreadMXBean & 死锁检测
    • 获取线程数:当前活动线程数getThreadCount()、当前活动的守护线程数getDaemonThreadCount()、活动线程数峰值getPeakThreadCount()
    • 获取cpu时间:getThreadCpuTime(long id),获取用户态cpu时间:getThreadUserTime(long id)

2. JMX概述

  • 通过ThreadMXBean获取Java程序中的线程信息时,资料中有这样的描述

    下面使用JMX来查看一个普通的Java程序包含哪些线程

  • 看完示例代码后,感觉这就是JMX?好像和自己了解的不太一样?

  • 目前,很多大数据组件都可以通过JMX获取节点或者集群的运行情况,这些不同维度的数据一般被称作metric

  • 以Presto为例

    • presto的指标有:集群或节点内存信息,正在运行的查询数、查询失败率,gc次数和cpu时间等
    • 基于jmx这个catalog,执行SQL获取对应metric的数据
    • 将metric的值上报到kafka,借助Druid + Granfna实现对集群或节点的实时监控
    • 一旦发现某些metric值异常,可以进行监控告警
  • 因此,自己对jmx的认知就是:一个可以动态获取程序运行状态的工具,对监控程序的运行非常有用

JMX的定义

  • JMX是Java Management Extensions 的缩写,是Java的一种管理扩展工具
  • 官方定义: JMX是一套标准的代理和服务,用户可以在任何Java应用程序中使用这些代理和服务实现管理
  • 可以通过JDK工具JConsole、网页、客户端与JMX服务器进行交互,从而管理或获取程序的状态
  • 中间件软件WebLogic的管理页面、Tomcat、Jboss等都是基于JMX开发的

JMX架构图

  • JMX的底层:又称基础层,是被管理的对象MBean。
    • 主要有三种:标准MBean、动态MBean和MXBean,本文主要基于标准MBean实现JMX
  • 代理层:MBeanServer,对MBean进行注册和管理。
  • 远程管理层:又称接入层,可以通过http、RMI、SNMP等方式实现对MBeanServer的远程访问

一些说明

  • 如果只把JMX看做是动态获取程序运行状态的工具,是比较狭隘的
  • 因为通过JMX不仅可以获取程序运行状态,还可以向程序传递参数从而影响程序的运行
  • MBean中,广义的get方法,提供获取程序运行状态的参数;广义的set方法,可以向程序传递参数

3. 实战

  • 通过JMX管理Java程序,需要以下三步
    • 定义MBean接口(实现代理的基础),实现MBean:
      • MBean接口名必须以MBean为后缀,以MBean实现类为前缀
      • 例如,一个MBean接口为HelloMBean,则其实现类为Hello
    • 向MBeanServer注册MBean
    • 以某种方式进行代理访问,从而实现与MBean的交互

3.1 基于JConsole的JMX示例

  • 创建管理学生的MBean接口:

    • 两个属性nam和age,需要针对属性添加getter/setter方法,以保证可读/可写
    • 其中,name可读可写,而age只读
    • 注意: 获取、设置值的方法不能随意定义为getXXX或者setXXX,否则XXX将被解读成属性。实际上,它可能并不是属性字段
    public interface StudentMBean {
        String getName();
    
        void setName(String name);
    
        int getAge();
    
        void printStudentInfo();
    
        String studentInfo();
    }
    
    
  • 实现MBean接口

    class Student implements StudentMBean {
        private String name;
        private int age;
    
        public Student() {
    
        }
    
        public Student(String name, int age) {
            this.name = name;
            this.age = 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 printStudentInfo() {
            System.out.println("Student: " + name + ", age: " + age);
        }
    
        @Override
        public String studentInfo() {
            return "Student: " + name + ", age: " + age;
        }
    }
    
  • 向MBeanServer注册MBean

    public class JConsoleAgent {
        public static void main(String[] args) throws MalformedObjectNameException, NotCompliantMBeanException,
                InstanceAlreadyExistsException, MBeanRegistrationException, InterruptedException {
            // 创建MBeanServer
            MBeanServer server = ManagementFactory.getPlatformMBeanServer();
            // 创建ObjectName以唯一标识MBean,其中studentMBean为域名,boy为MBean的名称,可以自由定义
            ObjectName student = new ObjectName("studentMBean:name=boy");
            // 将student注册到MBeanServer中,注册时实现了ObjectName与MBean的绑定
            server.registerMBean(new Student("jack", 24), student);
    
            // 程序休眠一段时间,方便观察通过JConsole体验JMX
            TimeUnit.MINUTES.sleep(30);
        }
    }
    
  • 在命令行中输入jconsole以启动JConsole工具,选择对应的代理创建连接

  • 最终,studentBean的信息如下:

  • name属性为可读可写,可以直接修改name(通过刷新按钮,实现值的修改)

  • printStudentInfo()是一个无参、void方法,直接点击方法名即可运行该方法

  • printStudentInfo方法有输出,最终会在JConsoleAgent的运行界面打印输出信息

3.2 基于网页的JMX示例

  • 添加HtmlAdaptorServer的maven依赖

    <!-- https://mvnrepository.com/artifact/com.sun.jdmk/jmxtools -->
    <dependency>
        <groupId>com.sun.jdmk</groupId>
        <artifactId>jmxtools</artifactId>
        <version>1.2.1</version>
    </dependency>
    
  • 保持MBean不变,创建HttpAdapterAgent,通过对JConsoleAgent进行一些改造,就可以通过网页与JMX进行交互

    public class HttpAdapterAgent {
        public static void main(String[] args) throws MalformedObjectNameException, NotCompliantMBeanException,
                InstanceAlreadyExistsException, MBeanRegistrationException, InterruptedException {
            // 创建MBeanServer
            MBeanServer server = ManagementFactory.getPlatformMBeanServer();
            // 创建ObjectName以唯一标识MBean,其中studentMBean为域名
            // boy为MBean的名称,可以自由定义
            ObjectName student = new ObjectName("studentMBean:name=boy");
            // 将student注册到MBeanServer中,注册时实现了ObjectName与MBean的绑定
            server.registerMBean(new Student("jack", 24), student);
    
            // 创建并注册HtmlAdaptorServer,从而可以通过网页管理MBean
            HtmlAdaptorServer adaptorServer = new HtmlAdaptorServer();
            ObjectName adapter = new ObjectName("httpAdapter:name=web");
            server.registerMBean(adaptorServer, adapter);
            // 启动HtmlAdaptorServer
            adaptorServer.start();
        }
    }
    
  • 访问本地的8082端口:http://localhost:8082/,将存在如下网页

  • 选择进入studentMBean域、name为boy的MBean,信息展示更加清晰】

3.3 通过客户端进行远程访问

定义支持远程访问的MBeanServer

  • 修改Agent使其支持远程访问

    public class RemoteAgent {
        public static void main(String[] args) throws MalformedObjectNameException, NotCompliantMBeanException,
                InstanceAlreadyExistsException, MBeanRegistrationException, InterruptedException, IOException {
            // 创建MBeanServer
            MBeanServer server = ManagementFactory.getPlatformMBeanServer();
            ObjectName student = new ObjectName("studentMBean:name=girl");
            server.registerMBean(new Student("lucy", 24), student);
    
            // 为MBeanServer注册端口号和url
            LocateRegistry.createRegistry(8888);
            // 若需要支持JConsole链接,url的结尾必须为jmxrmi
            JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:8888/jmxrmi");
            // 创建支持远程连接的服务器
            JMXConnectorServer connectorServer = JMXConnectorServerFactory.newJMXConnectorServer(url, null, server);
            // 启动服务
            connectorServer.start();
        }
    }
    
  • 此时,尚未定义客户端,可以先通过JConsole进行访问

  • studentMBean域、name为girl的MBean信息如下

定义客户端

  • 通过MBeanServer的url实现对指定MBean的管理
    • 可以直接通过MBeanServerConnection访问MBean中的属性和方法,也可以通过代理实现访问
    • 注意: 属性值定义时首字母虽然为小写,但通过JMX解析后实际是首字母大写
    public class Client {
        public static void main(String[] args) throws IOException, MalformedObjectNameException, AttributeNotFoundException, MBeanException, ReflectionException, InstanceNotFoundException, InvalidAttributeValueException {
            // 创建与server的连接
            JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:8888/jmxrmi");
            JMXConnector jmxConnector = JMXConnectorFactory.connect(url, null);
            MBeanServerConnection serverConnection = jmxConnector.getMBeanServerConnection();
    
            // 定义想要访问的MBean,与MBeanServer中注册的一致
            ObjectName student = new ObjectName("studentMBean:name=girl");
    
            // 获取MBeanServer所有domain
            String[] domains = serverConnection.getDomains();
            System.out.println("MBeanServer存在如下域名:");
            for (String domain: domains) {
                System.out.println(domain);
            }
    
            System.out.println("=================================");
            // 直接修改或访问属性值,属性必须大写
            serverConnection.setAttribute(student, new Attribute("Name", "grace"));
            System.out.println("Student: " + serverConnection.getAttribute(student, "Name"));
            // 直接调用MBean中的方法
            String info = (String) serverConnection.invoke(student, "studentInfo", null, null);
            System.out.println(info);
    
            // 创建代理实现访问
            StudentMBean proxy = MBeanServerInvocationHandler.newProxyInstance(serverConnection, student, StudentMBean.class, false);
            System.out.println("age: " + proxy.getAge());
        }
    }
    
  • 访问结果如下

3.4 JVM参数 + JCOnsol,实现JMX远程访问

  • 2.1中基于JConsole的JMX示例,只能通过本地进程的方式进行访问

  • 如果想要以最小的代价使其支持远程访问,可以在程序运行时添加以下jvm参数(idea的run Configurations)

    -Dcom.sun.management.jmxremote
    -Dcom.sun.management.jmxremote.port=8880
    -Dcom.sun.management.jmxremote.authenticate=false
    -Dcom.sun.management.jmxremote.ssl=false
    -Djava.rmi.server.hostname=localhost
    
  • 然后重新启动JConsoleAgent,通过localhost:8880便可实现远程访问

  • 注意: 任意一个Java程序,都包含默认的MBean,如下图所示

  • 通过java命令启动程序的命令如下,注意:请使用类的完全限定名

    java -Dcom.sun.management.jmxremote  -Dcom.sun.management.jmxremote.port=8880 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=localhost vivo.internet.study.jmx.JConsoleAgent 
    
  • 当然,同样可以通过Client程序实现与JMX的交互:

    service:jmx:rmi:///jndi/rmi://localhost:8880/jmxrmi
    

4. 总结

  • 通过工作所接触的大数据组件,自己对JMX的理解非常表面:就是一个可以动态获取程序运行状态的工具,从而实现对Java程序的监控

  • 通过这次的学习,发现JMX的强大远超想象:

    • 可以通过MBean获取程序的状态
    • 可以修改MBean中属性,从而实现动态向程序传参
    • 可以调用MBean中的方法,实现某些操作
    • 甚至可以在多个MBean之间进行通信,参考博客:jmx学习
  • JMX的架构:基础层、代理层、接入层,不同的接入方式:JConsole、网页、客户端

  • JMX的实现示例

    • 实现JMX的三大步骤:MBean的定义与实现、MBean的注册、以某种方式接入JMX
    • 基于JConsole(本地访问)、网页、远程访问(客户端和JConsole),三种不同接入方式的具体实现
    • 如果通过JVM参数实现并简化JMX的远程访问实现
  • 一些注意事项:

    • 通过ObjectName实现MBean实例的唯一标识,包含域名和MBean的name
    • MBean的属性一般首字母小写,客户端远程访问时需要首字母大写
    • 远程访问时,客户端想访问的Mbean必须与MBeamServer中注册的MBean一致

参考文档:

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值