前言
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目录下双击打开。
也可以在命令行中输入Jconsole命令,直接打开如下:
打开之后可以看到本机的各个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)之后设置了一个死循环。
主界面如上,可以方便的看到堆内存,线程数等指标。
在内存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,
来到线程那个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。之后,两个线程互相等待对方持有的资源,构成了死锁。
由图可见,成功造成了死锁。接下来去JConsole看一下:
点击检测死锁按钮,可以来到死锁tab。
工具清晰的展现了死锁的情况。
从内存监控和线程监控两个例子来看,JConsole还是很好用的。
Visual VM
安装和配置
由于我的JDK里并没有Visual VM这个exe,需要手动安装下。
首先去 visualvm.github.io/ 进行软件的下载。下载过程很简单。
接着在IDEA里配置Visual VM。在File->settings里的Plugins里进行搜索,然后install即可。
接着对一些参数进行配置,还是在settings里。
分别配置安装路径和JDK路径就可以。
配好了之后,IDEA右上方会出现两个图标,就是带着Visual VM一块Run或Debug。
和JConsole类似,也是可以直接进入进程分析,不需要知道进程的id。软件提供了很多功能,如生成快照,分析性能什么的,都是图形化操作,鼠标点点点就可以。
分析死锁
举个栗子,可以用它来分析死锁。运行本文的死锁代码。
在Visual VM的线程界面,可以看到列出了所有线程以及它们的运行状态。如图检测到了死锁,接着点击右边的Thread dump
查看dump文件,可以发现其清楚的标出了死锁的情况,thread1,thread2相互等待对方的资源。