JMC 使用教程

JMC 介绍

Java Mission Control(JMC)是一个强大的 Java 应用程序分析工具,从 2018 年开始,JMC 被 Oracle 开源作为独立工具发布。JMC 是一款可以用于管理、监听、分析以及排除 Java 故障的高级工具,使用 JMC 可以对代码性能、内存分配、GC 延迟、CPU 使用等情况进行低开销的、高效且详细的分析。

JMC

截止本文编写时间,JMC 最新版本8.3 .

开源地址:https://github.com/openjdk/jmc

Oracle 官方地址:https://www.oracle.com/java/technologies/jdk-mission-control.htmld

Oracle JMC 下载:https://www.oracle.com/java/technologies/javase/products-jmc8-downloads.html

JMC 测试环境准备

下载安装 JMC 这里不做描述,根据自己的系统环境下载相应的 JMC 版本安装启动即可。有一点要注意的是,从 JMC 8.10 开始,所依赖的最低 JDK 版本为 JDK11,如果你是 JDK 8 环境,可以下载 JMC 8.0 版本。如果你所在的 JVM 环境为 32 位,那么应该使用 -Dcom.sun.management.jmxremote 参数来启动 JVM。

测试代码准备

在启动 JMC 之前,准备一段有很多问题的代码,这段代码的问题包括诸多问题。

文章测试代码地址:https://github.com/niumoo/JavaNotes/

  1. CPU 使用过高

    通过不断地进行浮点运算来提高对 CPU 的消耗。

    /**
     * 消耗CPU的线程
     * 不断循环进行浮点运算
     */
    private static void cpuHigh() {
        Thread thread = new Thread(() -> {
            Thread.currentThread().setName("cpu_high_thread");
            while (true){
                double pi = 0;
                for (int i = 0; i < Integer.MAX_VALUE; i++) {
                    pi += Math.pow(-1, i) / (2 * i + 1);
                }
                System.out.println("Pi: " + pi * 4);
            }
        });
        thread.start();
    }
    
  2. 内存使用过高

    不断地生成 BigDecimal 数字添加到 List 来增加对内存的使用。

    /**
     * 不断新增 BigDecimal 信息到 list
     */
    private static void allocate() {
        new Thread(()->{
            Thread.currentThread().setName("memory_allocate_thread");
            List<BigDecimal> list = new ArrayList<>();
            for (int i = 0; i < Integer.MAX_VALUE; i++) {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                list.add(new BigDecimal(i));
            }
        }).start();
    }
    
  3. 线程池堵塞

  4. 死锁

    线程 dead_thread_A 与 线程 dead_thread_B 互相等待造成死锁。

    /**
     * 死锁线程
     * 线程 dead_thread_A 与 线程 dead_thread_B 互相锁死
     */
    private static void deadThread() {
        /** 创建资源 */
        Object resourceA = new Object();
        Object resourceB = new Object();
        // 创建线程
        Thread threadA = new Thread(() -> {
            Thread.currentThread().setName("dead_thread_A");
            synchronized (resourceA) {
                System.out.println(Thread.currentThread() + " get ResourceA");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "waiting get resourceB");
                synchronized (resourceB) {
                    System.out.println(Thread.currentThread() + " get resourceB");
                }
            }
        });
    
        Thread threadB = new Thread(() -> {
            Thread.currentThread().setName("dead_thread_A");
            synchronized (resourceB) {
                System.out.println(Thread.currentThread() + " get ResourceB");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "waiting get resourceA");
                synchronized (resourceA) {
                    System.out.println(Thread.currentThread() + " get resourceA");
                }
            }
        });
        threadA.start();
        threadB.start();
    }
    

JMC 概览

启动测试代码后,启动 JMC 就可以在左侧的 JMV 浏览器中看到当前机器所有的 JVM 进程,选择测试代码进行通过 MBean 服务器进行连接,然后可以看到概览界面。

JMC 概览

通过概览界面,可以看到进程的 JVM 内存使用情况,JVM CPU 使用情况,可以对进程的总体情况有一个初步的判断。

JMC 触发器

JMC 触发器可以理解为一个 JVM 告警,可以对 JVM 进程的各种指标设置阈值,在超过指定阈值后可以收到告警提示,对进程异常情况监控十分方便。

JMC 触发器

这里对 CPU 占用率和死锁线程数进行监控,由于问题代码的原因,很快就触发了告警。

JMC 触发器

通过弹窗告警可以看到告警原因,以及对应的类信息,但是这里目前还看不到告警的具体原因,也就是说比如 CPU 占用率过高,并不知道为什么 CPU 会使用过高。

JMC 分析内存

通过内存页面可以看到堆内存分配情况,由于问题代码中再不断地分配 BigDecimal 对象,这里也可以看出 BigDecimal 对象占用了最多的内存。对分析内存过高情况有一定的帮助。

JMC 内存

如果想进一步分析内存占用来源,可以到线程页面,通过勾选分配复选框,可以看到哪个线程占用的内存最多,还可以看到线程的具体的调用堆栈。

JMC 线程

JMC 分析CPU

线程页面勾选 CPU 概要分析可以查看占用 CPU 最高的线程,这里线程 cpu_high_thread 在不断地进行浮点计算,占用了较多的 CPU。

JMC 分析 CPU

JMC 分析死锁

线程页面勾选死锁检测可以直接看到死锁线程信息,并且有具体的线程堆栈。

JMC 线程死锁

另外一种检查死锁的方式是直接打印线程信息,在线程信息的最后部分,会输出死锁线程信息。使用 JMC 可以通过诊断命令中的 Thread.print 命令来实现这个功能。

JMC 诊断命令

一如既往,文章测试代码地址:https://github.com/niumoo/JavaNotes/