arthas命令实现原理-MBean的使用

本文介绍了MBean和MXBean的基本概念及应用,MBean是一种托管的Java对象,遵循JMX规范,MXBean则是一种特殊类型的MBean,支持将自定义类型转换为开放类型。文章详细解释了如何定义和使用这两种Bean,并展示了如何通过它们获取JVM的状态信息。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

MBean,MXBean简介

MBean是一个托管的java bean对象,MBean是一个托管Java对象,类似于JavaBeans组件,遵循JMX(Java Management Extensions,即Java管理扩展)规范中规定的设计模式。MBean可以表示任何需要管理的资源。MBeans 公开了一个管理接口,该接口由以下部分组成:

  • 一组可读或可写属性,或两者兼而有之
  • 一组可调用的操作
  • 自我描述

JMX 规范中定义了如下五种类型的MBean:

  • Standard MBeans    
  • Dynamic MBeans    
  • Open MBeans        
  • Model MBeans
  • MXBean

本文主要描述standard MBeans以及MXBeans类。

Stadard Mbeans

标准的MBean就是定义一个java 接口,该接口必须以MBean结尾,同时定义定义一个接口的实现类,实现类的名称是接口名称去掉MBean后的名字。 每一个定义在接口中的方法代表一个MBean的属性或者操作。MBean通过公共方法以及遵从特定的设计模式封装了属性和操作,以便暴露给管理应用程序。例如,一个只读属性在管理构件中只有Get方法,既有Get又有Set方法表示是一个可读写的属性。

定义HelloMBean接口,该接口必须以MBean结尾

package com.allen.mbean;
public interface HelloMBean {
    public void sayHello();
    public int add(int x, int y);
    public String getName();
    public int getCacheSize();
    public void setCacheSize(int size);
}

定义MBean接口的实现类,当前类的名称为接口名称去掉结尾的MBean剩余的字符

package com.allen.mbean;
public class Hello  implements HelloMBean {
    public void sayHello() {
        System.out.println("hello, world");
    }
    public int add(int x, int y) {
        return x + y;
    }
    public String getName() {
        return this.name;
    }
    public int getCacheSize() {
        return this.cacheSize;
    }
    public synchronized void setCacheSize(int size) {
        this.cacheSize = size;
        System.out.println("Cache size now " + this.cacheSize);
    }
    private final String name = "Reginald";
    private int cacheSize = DEFAULT_CACHE_SIZE;
    private static final int
            DEFAULT_CACHE_SIZE = 200;
}

创建测试MBean功能的类, 当前类启动后在MBeanServer中注册Hello这个对象。 启动参数中增加 -Djava.rmi.server.hostname=localhost 当时我的mac电脑启动程序每添加这个参数导致jconsole连不上。

package com.allen.mbean;
import javax.management.*;
import java.lang.management.ManagementFactory;
public class HelloMBeanTest {
    public static void main(String[] args) throws InterruptedException, MalformedObjectNameException, NotCompliantMBeanException, InstanceAlreadyExistsException, MBeanRegistrationException {
        MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
        ObjectName name = new ObjectName("com.example:type=Hello");
        Hello mbean = new Hello();
        mbs.registerMBean(mbean, name);
        Thread.sleep(Long.MAX_VALUE);
    }
}

使用jconsole连到当前jvm进程在MBean的tab页下可以看到有CacheSize,Name属性, sayHello,add操作

其实还可以通过jmx 连接到当前jvm,使用jmx远程操作当前Mbean,代码如下,其中FindUrlByPid 是本地通过pid查找到对应进程jmx的url串, 可通过获取对应的源码

package com.allen.mbean;
import javax.management.*;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import java.io.IOException;
public class ClientDemo {
    // 执行参数中注意增加上HelloMBeanTest运行的pid
    public static void main(String[] args) throws IOException, MalformedObjectNameException, InstanceNotFoundException, ReflectionException, AttributeNotFoundException, MBeanException {
        String url = FindUrlByPid.main(args);
        if (url == null) {
            System.out.println("can not find the jmx url by pid");
            return;
        }
        JMXServiceURL jmxServiceURL = new JMXServiceURL(url);
        JMXConnector connect = JMXConnectorFactory.connect(jmxServiceURL, null);
        MBeanServerConnection mBeanServerConnection = connect.getMBeanServerConnection();
        ObjectInstance objectInstance = mBeanServerConnection.getObjectInstance(new ObjectName("com.example:type=Hello"));
        System.out.println(mBeanServerConnection.getAttribute(new ObjectName("com.example:type=Hello"), "Name"));
        mBeanServerConnection.invoke(new ObjectName("com.example:type=Hello"), "sayHello", new Object[]{}, new String[]{});
    }
}

MXBeans

MXBean是一种特殊的MBean,不仅特殊在名字不一样,主要是在于在接口中会引用到一些其他类型的类时,其表现方式的不一样。在MXBean中,如果一个MXBean的接口定义了一个属性是一个自定义类型,如果MXBean定义了一种自定义的类型,当JMX使用这个MXBean时,这个自定义类型就会被转换成一种标准的类型,这些类型被称为开放类型,是定义在javax.management.openmbean包中的。而这个转换的规则是,如果是原生类型,如int或者是String,则不会有变化,但如果是其他自定义类型,则被转换成CompositeDataSupport类,这样,JMX调用这个MXBean提供的接口的时候,classpath下没有这个自定义类型也是可以调用成功的,但是换做MBean,则调用方的classpath下必须存在这个自定义类型的类定义。

在标准Mbean的基础上增加了User类型的属性,jconsole查看属性时会显示不可用

将MBean更换为MXBean后,通过jconsole查看属性时可以正常查看到

arthas使用的MXBean

arthas中部分系统状态信息的命令的实现是通过MXBean的调用返回对应的数据,下表是我现阶段的统计的命令的以及当前命令使用到的MXBean

命令

MXBean

描述

vmoption

HotSpotDiagnosticMXBean

通过该命令可以查看或者修改jvm 诊断性配置参数,如 PrintGC, PrintGCDetails等参数

thread

ThreadMXBean

通过该命令我们可以查看线程线程信息

jvm

RuntimeMXBean

ClassLoadingMXBean

CompilationMXBean

GarbageCollectorMXBean

MemoryManagerMXBean

MemoryMXBean

OperatingSystemMXBean

当前命令会展示jvm的runtime, CLASS-LOADING,COMPILATION等一系列状态信息

dashboard

聚合几种mxbean进行信息的获取

仪表盘的作用主要是展示thread, memory, gc, vm信息,也是通过jvm命令中的个别MXBean实现的

arthas-jvm命令实现流程

从前面章节可以看出arthas服务端时怎么处理请求, 当我们在客户端执行jvm命令之后,到arthas内部后由JvmCommand类进行处理,核心方法为:

    @Override
    public void process(CommandProcess process) {
        JvmModel jvmModel = new JvmModel();
        addRuntimeInfo(jvmModel);
        addClassLoading(jvmModel);
        addCompilation(jvmModel);
        if (!garbageCollectorMXBeans.isEmpty()) {
            addGarbageCollectors(jvmModel);
        }
        if (!memoryManagerMXBeans.isEmpty()) {
            addMemoryManagers(jvmModel);
        }
        addMemory(jvmModel);
        addOperatingSystem(jvmModel);
        addThread(jvmModel);
        addFileDescriptor(jvmModel);
        process.appendResult(jvmModel);
        process.end();
    }

从上面的代码中,我们可以看出,当前方法核心流程就时创建一个JvmModel对象,向该对象中添加各类信息,运行信息,类加载信息,gc信息,内存占用,线程等各类信息,接下来我们就几种信息获取进行具体的分析。着重介绍Runtime信息,Classloading信息,Thread信息的获取,其它方面的信息与这三种实现方式大体一致。

Runtime信息

//RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();
        String bootClassPath = "";
        try {
            bootClassPath = runtimeMXBean.getBootClassPath();
        } catch (Exception e) {
            // under jdk9 will throw UnsupportedOperationException, ignore
        }
        String group = "RUNTIME";
        jvmModel.addItem(group,"MACHINE-NAME", runtimeMXBean.getName());
        jvmModel.addItem(group, "JVM-START-TIME", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(runtimeMXBean.getStartTime())));
        jvmModel.addItem(group, "MANAGEMENT-SPEC-VERSION", runtimeMXBean.getManagementSpecVersion());
        jvmModel.addItem(group, "SPEC-NAME", runtimeMXBean.getSpecName());
        jvmModel.addItem(group, "SPEC-VENDOR", runtimeMXBean.getSpecVendor());
        jvmModel.addItem(group, "SPEC-VERSION", runtimeMXBean.getSpecVersion());
        jvmModel.addItem(group, "VM-NAME", runtimeMXBean.getVmName());
        jvmModel.addItem(group, "VM-VENDOR", runtimeMXBean.getVmVendor());
        jvmModel.addItem(group, "VM-VERSION", runtimeMXBean.getVmVersion());
        jvmModel.addItem(group, "INPUT-ARGUMENTS", runtimeMXBean.getInputArguments());
        jvmModel.addItem(group, "CLASS-PATH", runtimeMXBean.getClassPath());
        jvmModel.addItem(group, "BOOT-CLASS-PATH", bootClassPath);
        jvmModel.addItem(group, "LIBRARY-PATH", runtimeMXBean.getLibraryPath());

从上面的代码中,可以清晰的看出来Runtime信息都是通过RuntimeMXBean对象获取的,通过该对象我们可以获取系统运行时的一些信息。通过这个MXBean我们可以获取到很多有用的信息:

  • 通过getName信息,可以获取到当前进程的id, getName获取的数据的格式时pid@hostname
  • 通过getStartTime,可以获取到jvm启动的时间
  • 通过getInputArguments,可以获取到jvm启动时间
  • 。。。 不一一举例

Classloading信息

  ClassLoadingMXBean classLoadingMXBean = ManagementFactory.getClassLoadingMXBean();
  private void addClassLoading(JvmModel jvmModel) {
        String group = "CLASS-LOADING";
        jvmModel.addItem(group, "LOADED-CLASS-COUNT", classLoadingMXBean.getLoadedClassCount());
        jvmModel.addItem(group, "TOTAL-LOADED-CLASS-COUNT", classLoadingMXBean.getTotalLoadedClassCount());
        jvmModel.addItem(group, "UNLOADED-CLASS-COUNT", classLoadingMXBean.getUnloadedClassCount());
        jvmModel.addItem(group, "IS-VERBOSE", classLoadingMXBean.isVerbose());
    }

通过ClassLoadingMXBean可以获取到jvm加载到类个数,卸载到类个数,总共加载到类个数,是否为类加载系统启用verbose输出,其实该MXBean还有一个setVerbose方法,用于动态设置类加载的verbose属性

Thread信息

   ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
    private void addThread(JvmModel jvmModel) {
        String group = "THREAD";
        jvmModel.addItem(group, "COUNT", threadMXBean.getThreadCount())
                .addItem(group, "DAEMON-COUNT", threadMXBean.getDaemonThreadCount())
                .addItem(group, "PEAK-COUNT", threadMXBean.getPeakThreadCount())
                .addItem(group, "STARTED-COUNT", threadMXBean.getTotalStartedThreadCount())
                .addItem(group, "DEADLOCK-COUNT",getDeadlockedThreadsCount(threadMXBean));
    }

核心是通过ThreadMXBean进行数据的获取, 在arthas,jvm命令中使用ThreadMXBean相对简单,仅仅获取的线程数,守护线程数,peak状态的线程数,启动过的线程数,死锁线程数。

dashboard命令同样会展示cpu占用率比较高的10个线程, 其中还使用到了getThreadCpuTime的方法,通过该方法可以获取的线程的的cpu时间

arthas用到这些MXBean的作用

java APM(Application Performance Monitor 应用性能监控)程序,中对与jvm的信息采集中,使用到很多MXBean,如我之前研究的的pinpoint实现中,对于应用性能采集:

activethread, buffer, cpu, deadlock, filedscriptor, gc, loadedclass, memory, totalthread这些指标跟arthas的实现获取方式几乎一致,都是通过mxbean获取的。

后续如果有项目需求,采集系统性能,个人觉得完全可以通过这样的jdk提供的MXBean进行获取

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值