JMX入门

发布时间 2023-06-03 14:20:23作者: shigp1

一、简介

Java管理扩展(JMX)技术是Java平台标准版(Java SE平台)的标准部分。JMX技术是在Java 2平台标准版(J2SE)5.0版本中添加到该平台的。JMX技术提供了一种简单、标准的管理资源(如应用程序、设备和服务)的方法。因为JMX技术是动态的,所以您可以在创建、安装和实现资源时使用它来监视和管理资源。您还可以使用JMX技术来监视和管理Java虚拟机(Java VM)。JMX规范用Java编程语言定义了用于管理和监控应用程序和网络的体系结构、设计模式、API和服务。使用JMX技术,给定的资源由一个或多个被称为ManagedBeans(MBean)的Java对象检测。这些MBean在核心托管对象服务器(称为MBean服务器)中注册。MBean服务器充当管理代理,可以在大多数已启用Java编程语言的设备上运行。规范定义了JMX代理,您可以使用这些代理来管理已正确配置用于管理的任何资源。JMX代理由一个MBean服务器和一组用于处理MBean的服务组成,MBean服务器中注册了MBean。通过这种方式,JMX代理直接控制资源,并使其可用于远程管理应用程序。对资源进行检测的方式完全独立于管理基础架构。因此,无论资源的管理应用程序是如何实现的,都可以使资源变得可管理。JMX技术定义了标准连接器(称为JMX连接器),使您能够从远程管理应用程序访问JMX代理。使用不同协议的JMX连接器提供相同的管理接口。因此,不管使用什么通信协议,管理应用程序都可以透明地管理资源。JMX代理也可以由不符合JMX规范的系统或应用程序使用,只要这些系统或应用支持JMX代理即可。

二、特点

  • JMX技术使Java应用程序能够在无需大量投资的情况下进行管理。

基于JMX技术的代理(JMX代理)可以在大多数支持Java技术的设备上运行。因此,Java应用程序可以变得易于管理,而对其设计几乎没有影响。Java应用程序只需要嵌入一个托管对象服务器,并使其某些功能作为在对象服务器中注册的一个或多个托管bean(MBean)可用。

  • JMX技术提供了管理Java应用程序、系统和网络的标准方法。

例如,Java Platform,Enterprise Edition(Java EE)5应用程序服务器符合JMX体系结构,因此可以使用JMX技术进行管理。

  • JMX技术可以用于Java虚拟机的开箱即用管理。

Java虚拟机(Java VM)是使用JMX技术进行高度检测的。您可以启动JMX代理来访问内置的Java虚拟机工具,从而远程监控和管理Java虚拟机。

  • JMX技术提供了一种可扩展的动态管理体系结构。

每个JMX代理服务都是一个独立的模块,可以根据需要插入到管理代理中。这种基于组件的方法意味着JMX解决方案可以从占地面积小的设备扩展到大型电信交换机及其他设备。JMX规范提供了一组核心代理服务。可以在管理基础设施中开发和动态加载、卸载或更新附加服务。

  • JMX技术利用了现有的标准Java技术。

只要需要,JMX规范就会引用现有的Java规范,例如Java命名和目录接口(J.N.D.I.)API。

  • 基于JMX技术的应用程序(JMX应用程序)可以从NetBeansIDE模块创建。

您可以从NetBeans更新中心获得一个模块(在NetBeans界面中选择Tools->Update Center),该模块使您能够使用NetBeans IDE创建JMX应用程序。这降低了JMX应用程序的开发成本。

  • JMX技术与现有的管理解决方案和新兴技术相集成。

JMXAPI是任何管理系统供应商都可以实现的开放接口。JMX解决方案可以使用查找和发现服务以及协议,例如Jini网络技术和服务定位协议(SLP)。

三、体系结构

 

Instrumentation

要使用JMX技术管理资源,必须首先用Java编程语言对资源进行检测。您使用称为MBean的Java对象来实现对资源的指令插入的访问。MBean必须遵循JMX规范中定义的设计模式和接口。这样做可以确保所有MBean以标准化的方式提供托管资源检测。除了标准MBean之外,JMX规范还定义了一种特殊类型的MBean,称为MXBean。MXBean是一个MBean,它只引用一组预定义的数据类型。一旦MBean对资源进行了检测,就可以通过JMX代理对其进行管理。MBean不需要知道它们将使用的JMX代理。MBean被设计为灵活、简单且易于实现。应用程序、系统和网络的开发人员可以以标准的方式使其产品易于管理,而无需了解或投资于复杂的管理系统。可以用最少的努力使现有资源易于管理。此外,JMX规范的插装级别提供了一种通知机制。此机制使MBean能够生成通知事件并将其传播到其他级别的组件。

JMX Agent

基于JMX技术的代理(JMX代理)是一种标准管理代理,它直接控制资源并使其可用于远程管理应用程序。JMX代理通常与它们控制的资源位于同一台机器上,但这种安排不是必需的。

JMX代理的核心组件是MBean服务器,这是一个注册MBean的托管对象服务器。JMX代理还包括一组用于管理MBean的服务,以及至少一个允许管理应用程序访问的通信适配器或连接器。当您实现JMX代理时,您不需要知道它将管理的资源的语义或功能。事实上,JMX代理甚至不需要知道它将为哪些资源提供服务,因为任何符合JMX规范的资源都可以使用任何提供资源所需服务的JMX代理。类似地,JMX代理不需要知道将访问它的管理应用程序的功能。

Remote Management

JMX技术工具可以通过多种不同的方式访问,既可以通过简单网络管理协议(SNMP)等现有管理协议,也可以通过专有协议。MBean服务器依赖于协议适配器和连接器,使JMX代理可以从代理的Java虚拟机(Java VM)之外的管理应用程序访问。

每个适配器通过在MBean服务器中注册的所有MBean的特定协议提供一个视图。例如,HTML适配器可以在浏览器中显示MBean。

连接器提供了一个管理器端接口,用于处理管理器和JMX代理之间的通信。每个连接器通过不同的协议提供相同的远程管理接口。当远程管理应用程序使用此接口时,它可以通过网络透明地连接到JMX代理,而不考虑协议。JMX技术提供了一个标准解决方案,用于将JMX技术工具导出到基于Java远程方法调用(Java RMI)的远程应用程序。

四、应用场景

1、dashboard监控面板

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

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

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

以logback为例,只需在logback.xml配置文件中,增加单行配置即可启动JMX支持。

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

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

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

6、通知告警

JMX API定义了一种机制,使MBean能够生成通知,例如,通知状态更改、检测到的事件或问题。

五、MBean

MBean是一个托管Java对象,类似于JavaBeans组件,遵循JMX规范中提出的设计模式。MBean可以表示设备、应用程序或任何需要管理的资源。MBean公开了一个管理接口,该接口由以下内容组成:

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

JMX 中共有四种类型的 MBean,分别是 Standard MBean, Dynamic MBean, Open MBean, Model MBean。

类型 描述
Standard MBean 这种类型的MBean最简单,它能管理的资源(包括属性,方法,时间)必须定义在接口中,然后MBean必须实现这个接口。它的命名也必须遵循一定的规范,例如我们的MBean为Hello,则接口必须为HelloMBean。
Dynamic MBean 必须实现javax.management.DynamicMBean接口,所有的属性,方法都在运行时定义。Open MBean和Model MBean都属于Dynamic MBean。
Open MBean Open 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中。

 

常用的MBean

1、OperatingSystemMXBean

运行Java虚拟机的操作系统的管理接口。可以通过调用ManagementFactory.getOperatingSystemMXBean方法或从平台MBeanServer方法获得。

@Test
public void test1() {
    OperatingSystemMXBean operatingSystemMXBean = ManagementFactory.getOperatingSystemMXBean();
    String name = operatingSystemMXBean.getName();
    log.info("操作系统名称:{}", name);

    String arch = operatingSystemMXBean.getArch();
    log.info("操作系统架构:{}", arch);

    double systemLoadAverage = operatingSystemMXBean.getSystemLoadAverage();
    log.info("最后一分钟的系统平均负载:{}", systemLoadAverage);

    int availableProcessors = operatingSystemMXBean.getAvailableProcessors();
    log.info("Java虚拟机可用的处理器数:{}", availableProcessors);

    String version = operatingSystemMXBean.getVersion();
    log.info("操作系统版本:{}", version);

    ObjectName objectName = operatingSystemMXBean.getObjectName();
    log.info("objectName:{}", objectName);
}

log是lombok注解。

2、RuntimeMXBean

Java虚拟机运行时系统的管理接口。可以通过调用ManagementFactory.getRuntimeMXBean方法或从平台MBeanServer方法获得。

@Test
public void test2() {
    RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();
    String name = runtimeMXBean.getName();
    log.info("Java虚拟机名称:{}", name);

    boolean bootClassPathSupported = runtimeMXBean.isBootClassPathSupported();
    log.info("Java虚拟机是否支持引导类加载器用于搜索类文件的引导类路径机制:{}", bootClassPathSupported);

    if (bootClassPathSupported) {
        String bootClassPath = runtimeMXBean.getBootClassPath();
        log.info("引导类加载器路径:{}", bootClassPath);
    }

    String classPath = runtimeMXBean.getClassPath();
    log.info("系统类加载器路径:{}", classPath);

    List<String> inputArguments = runtimeMXBean.getInputArguments();

    log.info("传递给Java虚拟机的输入参数:{}", inputArguments);

    String libraryPath = runtimeMXBean.getLibraryPath();
    log.info("Java类库路径:{}", libraryPath);

    String managementSpecVersion = runtimeMXBean.getManagementSpecVersion();
    log.info("运行的Java虚拟机实现的管理接口的规范版本:{}", managementSpecVersion);

    long pid = runtimeMXBean.getPid();
    log.info("正在运行的Java虚拟机进程id:{}", pid);

    String specVendor = runtimeMXBean.getSpecVendor();
    log.info("Java虚拟机规范供应商:{}", specVendor);

    String specVersion = runtimeMXBean.getSpecVersion();
    log.info("Java虚拟机规范版本:{}", specVersion);

    long startTime = runtimeMXBean.getStartTime();
    log.info("Java虚拟机的启动时间:{}毫秒", startTime);

    Map<String, String> systemProperties = runtimeMXBean.getSystemProperties();
    log.info("系统属性开始");

    for (Map.Entry<String, String> entry : systemProperties.entrySet()) {
        log.info("\t\tkey:【{}】,value:【{}】", entry.getKey(), entry.getValue());
    }

    log.info("系统属性结束");

    long uptime = runtimeMXBean.getUptime();
    log.info("Java虚拟机的正常运行时间:{}毫秒", uptime);

    String vmName = runtimeMXBean.getVmName();
    log.info("Java虚拟机实现名称:{}", vmName);

    String vmVendor = runtimeMXBean.getVmVendor();
    log.info("Java虚拟机实现供应商:{}", vmVendor);

    String vmVersion = runtimeMXBean.getVmVersion();
    log.info("Java虚拟机版本:{}", vmVersion);

    ObjectName objectName = runtimeMXBean.getObjectName();
    log.info("objectName:{}", objectName);
}

3、MemoryMXBean

Java虚拟机内存系统的管理接口。可以通过调用ManagementFactory.getMemoryMXBean方法或从平台MBeanServer方法获得。

@Test
public void test3() {
    MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
    int objectPendingFinalizationCount = memoryMXBean.getObjectPendingFinalizationCount();
    log.info("待回收的对象的大致数量:{}", objectPendingFinalizationCount);

    log.info("堆使用情况");
    MemoryUsage heapMemoryUsage = memoryMXBean.getHeapMemoryUsage();
    long init = heapMemoryUsage.getInit();
    log.info("\tJava虚拟机最初从操作系统用于内存管理的内存量:{}字节", init);
    long used = heapMemoryUsage.getUsed();
    log.info("\t已使用:{}字节", used);
    long max = heapMemoryUsage.getMax();
    log.info("\t内存管理的最大内存量(-1表示没有限制):{}字节", max);
    long committed = heapMemoryUsage.getCommitted();
    log.info("\tJava虚拟机可以使用的最大内存量:{}字节", committed);

    log.info("\n");

    MemoryUsage nonHeapMemoryUsage = memoryMXBean.getNonHeapMemoryUsage();
    log.info("堆外使用情况");

    long init1 = nonHeapMemoryUsage.getInit();
    log.info("\tJava虚拟机最初从操作系统用于内存管理的内存量:{}字节", init1);
    long used1 = nonHeapMemoryUsage.getUsed();
    log.info("\t已使用:{}字节", used1);

    long max1 = nonHeapMemoryUsage.getMax();
    log.info("\t内存管理的最大内存量(-1表示没有限制):{}字节", max1);

    long committed1 = nonHeapMemoryUsage.getCommitted();
    log.info("\tJava虚拟机可以使用的最大内存量:{}字节", committed1);
}

4、ThreadMXBean

Java虚拟机的线程系统的管理接口。可以通过调用ManagementFactory.getThreadMXBean方法或从平台MBeanServer方法获得。

 @Test
public void test4() {
    Object obj = new Object();
    /**
     * 测试线程持有监视器情况
     */
    new Thread(() -> {
        synchronized (obj) {
            try {
                TimeUnit.SECONDS.sleep(30);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            log.info("测试线程:{}", Thread.currentThread().getName());
        }
    },"测试线程1")
            .start();

    try {
        TimeUnit.MICROSECONDS.sleep(200);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }

    new Thread(() -> {
        synchronized (obj) {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            log.info("测试线程:{}", Thread.currentThread().getName());
        }
    },"测试线程2")
            .start();

    Lock lock = new ReentrantLock();
    /**
     * 测试线程持有监视锁情况
     */
    new Thread(() -> {
        try {
            lock.lock();
            try {
                TimeUnit.SECONDS.sleep(30);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            log.info("测试线程:{}", Thread.currentThread().getName());
        } finally {
            lock.unlock();
        }
    },"测试线程3")
            .start();

    try {
        TimeUnit.MICROSECONDS.sleep(200);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }

    new Thread(() -> {
        try {
            lock.lock();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            log.info("测试线程:{}", Thread.currentThread().getName());
        } finally {
            lock.unlock();
        }
    },"测试线程4")
            .start();

    Object A = new Object();
    Object B = new Object();
    /**
     * 测试线程持有监视器死锁情况
     */
    new Thread(() -> {
        synchronized (A) {
            try {
                TimeUnit.MICROSECONDS.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            synchronized (B) {
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                log.info("测试线程:{}", Thread.currentThread().getName());
            }
        }
    },"测试线程5")
            .start();


    new Thread(() -> {
        synchronized (B) {
            try {
                TimeUnit.MICROSECONDS.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            synchronized (A) {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                log.info("测试线程:{}", Thread.currentThread().getName());
            }
        }
    },"测试线程6")
            .start();

    Lock lock1 = new ReentrantLock();
    Lock lock2 = new ReentrantLock();
    /**
     * 测试线程持有锁死锁情况
     */
    new Thread(() -> {
        try {
            lock1.lock();
            try {
                TimeUnit.MICROSECONDS.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            lock2.lock();
            try {
                TimeUnit.SECONDS.sleep(20);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            log.info("测试线程:{}", Thread.currentThread().getName());
        } finally {
            lock2.unlock();
            lock1.unlock();
        }
    },"测试线程7")
            .start();


    new Thread(() -> {
        try {
            lock2.lock();
            try {
                TimeUnit.MICROSECONDS.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            lock1.lock();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            log.info("测试线程:{}", Thread.currentThread().getName());
        } finally {
            lock1.unlock();
            lock2.unlock();
        }
    },"测试线程8")
            .start();

    try {
        TimeUnit.SECONDS.sleep(4);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }

    ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
    int threadCount = threadMXBean.getThreadCount();
    log.info("当前活动线程数,包括守护进程线程和非守护进程线程:{}", threadCount);

    long[] allThreadIds = threadMXBean.getAllThreadIds();

    for (long allThreadId : allThreadIds) {
        ThreadInfo threadInfo = threadMXBean.getThreadInfo(allThreadId);
        String threadName = threadInfo.getThreadName();
        //线程处于BLOCKED状态的次数。
        long blockedCount = threadInfo.getBlockedCount();
        //自上次启用线程争用监视以来,线程处于BLOCKED状态的总累积时间。如果禁用了线程争用监视,则此方法返回-1。
        long blockedTime = threadInfo.getBlockedTime();

        String lockName = threadInfo.getLockName();
        // 线程被阻塞的所锁所持有的线程id
        long lockOwnerId = threadInfo.getLockOwnerId();
        //线程被阻塞的所锁所持有的线程名称
        String lockOwnerName = threadInfo.getLockOwnerName();

        LockInfo lockInfo = threadInfo.getLockInfo();

        MonitorInfo[] lockedMonitors = threadInfo.getLockedMonitors();

        LockInfo[] lockedSynchronizers = threadInfo.getLockedSynchronizers();

        int priority = threadInfo.getPriority();
        //线程的StackTraceElement对象的数组。
        StackTraceElement[] stackTrace = threadInfo.getStackTrace();

        Thread.State threadState = threadInfo.getThreadState();
        //线程处于WAITING或TIMED_WAITING状态的次数
        long waitedCount = threadInfo.getWaitedCount();
        //自启用线程争用监视以来,线程处于WAITING或TIMED_WAITING状态的累计总时间。如果禁用了线程争用监视,则此方法返回-1。
        long waitedTime = threadInfo.getWaitedTime();

        log.info("线程id:{},线程名:{}", allThreadId, threadName);
        log.info("\t\t线程状态:{},线程处于BLOCKED状态的次数:{},自上次启用线程争用监视以来,线程处于BLOCKED状态的总累积时间(如果禁用了线程争用监视,则此方法返回-1):{},线程处于WAITING或TIMED_WAITING状态的次数:{},/自启用线程争用监视以来,线程处于WAITING或TIMED_WAITING状态的累计总时间(如果禁用了线程争用监视,则此方法返回-1):{}", threadState, blockedCount, blockedTime, waitedCount, waitedTime);
        log.info("\t\t线程等待的锁名:{},线程被阻塞的所锁所持有的线程id:{},线程被阻塞的所锁所持有的线程名称:{},线程被阻止等待的对象的className:{}", lockName, lockOwnerId, lockOwnerName, lockInfo != null ? lockInfo.getClassName() : null);
        log.info("\t\t线程持有的监视器对象:{}",printLockedMonitors(lockedMonitors));
        log.info("\t\t线程持有的同步器对象:{}", printLockedSynchronizers(lockedSynchronizers));
        log.info("\t\t线程的堆栈:{}", stackTrace);
    }

    log.info("\n");

    long currentThreadCpuTime = threadMXBean.getCurrentThreadCpuTime();
    log.info("当前线程的总CPU时间(以纳秒为单位):{}", currentThreadCpuTime);
    long currentThreadUserTime = threadMXBean.getCurrentThreadUserTime();
    log.info("当前线程在用户模式下的总CPU时间(以纳秒为单位):{}", currentThreadUserTime);
    int daemonThreadCount = threadMXBean.getDaemonThreadCount();
    log.info("当前运行的后台进程线程数:{}", daemonThreadCount);
    int peakThreadCount = threadMXBean.getPeakThreadCount();
    log.info("自Java虚拟机启动或峰值重置以来的峰值实时线程计数:{}", peakThreadCount);

    long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();
    log.info("死锁的线程id数组:{}", deadlockedThreads);

    long[] monitorDeadlockedThreads = threadMXBean.findMonitorDeadlockedThreads();
    log.info("监视器死锁的线程id数组:{}", monitorDeadlockedThreads);
}


private String printLockedMonitors(MonitorInfo[] lockedMonitors) {
    if (lockedMonitors != null && lockedMonitors.length > 0) {
       return Arrays.stream(lockedMonitors).map(MonitorInfo::getClassName).collect(Collectors.joining());
    } else {
        return null;
    }
}

private String printLockedSynchronizers(LockInfo[] lockedSynchronizers) {
    if (lockedSynchronizers != null && lockedSynchronizers.length > 0) {
        return Arrays.stream(lockedSynchronizers).map(LockInfo::getClassName).collect(Collectors.joining());
    } else {
        return null;
    }
}

可以从控制台看到:
 


 

5、ClassLoadingMXBean

Java虚拟机的类加载系统的管理接口。可以通过调用ManagementFactory.getClassLoadingMXBean方法或从平台MBeanServer获得。

 @Test
public void test5() {
    ClassLoadingMXBean classLoadingMXBean = ManagementFactory.getClassLoadingMXBean();
    int loadedClassCount = classLoadingMXBean.getLoadedClassCount();
    long unloadedClassCount = classLoadingMXBean.getUnloadedClassCount();
    long totalLoadedClassCount = classLoadingMXBean.getTotalLoadedClassCount();
    log.info("当前加载在Java虚拟机中的类的数:{},自Java虚拟机开始执行以来卸载的类的总数:{},自Java虚拟机开始执行以来已加载的类的总数:{}", loadedClassCount, unloadedClassCount, totalLoadedClassCount);
}

6、GarbageCollectorMXBean

Java虚拟机垃圾收集的管理接口。

@Test
public void test6() {
    List<GarbageCollectorMXBean> garbageCollectorMXBeans = ManagementFactory.getGarbageCollectorMXBeans();
    for (GarbageCollectorMXBean garbageCollectorMXBean : garbageCollectorMXBeans) {
        // GC名称
        String name = garbageCollectorMXBean.getName();
        long collectionCount = garbageCollectorMXBean.getCollectionCount();
        long collectionTime = garbageCollectorMXBean.getCollectionTime();
        String[] memoryPoolNames = garbageCollectorMXBean.getMemoryPoolNames();
        log.info("名称:{},收集次数:{},收集耗时:{}毫秒,内存池名:{}", name, collectionCount, collectionTime, memoryPoolNames);
    }
}

可以查看使用的是哪种垃圾收集器,收集耗时,收集次数。

 
 
 
 
 

参考:https://blog.csdn.net/ory001/article/details/124760181