JVM实践:可视化监控和故障处理工具

347 阅读4分钟

前言

  Java提供了很多用于性能监控和故障处理的工具,可以用它们对Java程序的性能,GC等等进行分析。这些工具可以分为命令行类和可视化工具类。命令行类工具比较简单,可以自行查阅资料。可视化工具集成了这些命令,并且更加易用,直观。本文主要依照《深入理解Java虚拟机》第三版,对Java的可视化故障处理工具进行实践。

  可视化故障处理工具主要有JSHDB,JConsole,VisualVM以及Java Mission Control等等。本文主要针对JConsole和Visual VM这两种工具,根据《深入理解Java虚拟机》中的例子进行实践。

事前准备

  本文的实验都是借助IDEA这款IDE运行。在IDEA中配置JVM参数可以参见我的上一篇博客:JVM实践:内存分配与GC 里的事前准备部分。

JConsole

  JConsole是一款基于JMX的可视化监视、管理工具。可以在Jdk的bin目录下双击打开。

image.png

也可以在命令行中输入Jconsole命令,直接打开如下:

image.png

image.png

  打开之后可以看到本机的各个JVM进程。咱们如果用命令行对进程分析时,还需要运行jps命令,先找到进程的id,之后才能进一步使用别的命令。但是JConsole直接可以列出这些进程,双击对应的进程就可以开始分析了。

内存监控

  这部分实验参考了书上的代码如下:

public class Lab {
    static class OOMObject{
        // 一个placeholder占64k
        public byte[] placeholder = new byte[64 * 1024];
    }
    public static void fillHeap(int num) throws InterruptedException {
        List<OOMObject> list = new ArrayList<>();
        for(int i = 0; i < num; ++i) {
            Thread.sleep(50);
            // 不停的向堆里加入新的数据
            list.add(new OOMObject());
        }
        System.gc();
    }
    public static void main(String[] args) throws Exception{
        fillHeap(1000);
        while(true) {

        }
    }
}

  为了更好的观察,在fillHeap(1000)之后设置了一个死循环。

image.png

主界面如上,可以方便的看到堆内存,线程数等指标。

image.png

  在内存tab可以看到各个区的内存情况,这里给出一个Eden区的使用情况。大伙也可以看看别的区的情况。关于这块的分析,也比较简单,书上写的也比较清楚,我就不赘述了。

线程监控

  正如上面的图,JConsole有一个线程tab,可以用此功能对进程里的线程进行监控。

public static void main(String[] args) {
    Object lock = new Object(); //创建synchronized锁的对象
    // 创建死循环的线程
    new Thread(() -> {
        while (true) {

        }
    },"testBusyThread").start();
    // 创建一个等待状态的线程
    new Thread(() -> {
        synchronized (lock) {
            try{
                lock.wait();
            }catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    },"testLockThread").start();
}

  把书里的代码稍微改了一下,用匿名的Thread类对象进行线程的启动,run方法用lambda表达式减少了一些代码的编写。。。运行这个代码,打开JConsole,

2.PNG

3.PNG

  来到线程那个tab下面,可以在左侧选择线程进行查看。
对于testLockThread线程,代码中,我们先是通过synchronized(lock),获得了lock对象的锁,之后又调用lock.wait(),释放了该锁。从图片可以看出,总等待数为1,这和我们的代码也是符合的。
对testBusyThread线程,可以看到它停留在第五行,正好是我们代码里的while(true)
也可以用JConsole来检测死锁。

public static void main(String[] args) {
    Object obj1 = new Object();
    Object obj2 = new Object();
    new Thread(() -> {
        synchronized (obj1) {
            System.out.println("thread1 get obj1");
            try {
                Thread.sleep(1000);
            }catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("thread1 wait obj2....");
            synchronized (obj2) {
                System.out.println("thread1 get obj2");
            }
        }
    },"thread1").start();
    new Thread(() -> {
        synchronized (obj2) {
            System.out.println("thread2 get obj2");
            System.out.println("thread2 wait obj1....");
            synchronized (obj1) {
                System.out.println("thread2 get obj1");
            }
        }
    },"thread2").start();
}

  为了便于理解,死锁的代码和书上不一样。thread1获得obj1后,sleep了1秒,从而确保thread2可以在thread1之前成功获得obj2。之后,两个线程互相等待对方持有的资源,构成了死锁。

image.png

  由图可见,成功造成了死锁。接下来去JConsole看一下:

  点击检测死锁按钮,可以来到死锁tab。

image.png

image.png

image.png

  工具清晰的展现了死锁的情况。
从内存监控和线程监控两个例子来看,JConsole还是很好用的。

Visual VM

安装和配置

  由于我的JDK里并没有Visual VM这个exe,需要手动安装下。
首先去 visualvm.github.io/ 进行软件的下载。下载过程很简单。

image.png

  接着在IDEA里配置Visual VM。在File->settings里的Plugins里进行搜索,然后install即可。

4.PNG

  接着对一些参数进行配置,还是在settings里。

image.png

  分别配置安装路径和JDK路径就可以。

image.png

  配好了之后,IDEA右上方会出现两个图标,就是带着Visual VM一块Run或Debug。

image.png

  和JConsole类似,也是可以直接进入进程分析,不需要知道进程的id。软件提供了很多功能,如生成快照,分析性能什么的,都是图形化操作,鼠标点点点就可以。

分析死锁

  举个栗子,可以用它来分析死锁。运行本文的死锁代码。

image.png

  在Visual VM的线程界面,可以看到列出了所有线程以及它们的运行状态。如图检测到了死锁,接着点击右边的Thread dump

image.png

  查看dump文件,可以发现其清楚的标出了死锁的情况,thread1,thread2相互等待对方的资源。