介绍
Visual VM是一个功能强大的多合一故障诊断和性能监控可视化工具(文件过大可能会有些问题),在JDK6升级到JDK7以后作为JDK的一部分发布(jdk路径bin下的jvisualvm可以启动进行监控),完全免费的。
Visual VM也可以作为独立的软件安装,当前可在🔗下载进行安装。之所以强大,那是因为Visual VM支持插件安装,提供离线下载插件安装和在线安装插件。
本文也模拟了死锁和内存泄漏OOM场景通过该工具进行了简单分析,希望能对实际应用有所帮助。
安装
VisualVM作为Java VisualVM发布在Oracle JDK 6~8中,则我们可以在jdk的path路径下的bin目录运行jvisualvm命令来打开。如果需要独立安装,介绍中也提供了下载地址,安装比较简单就不作介绍了(同意协议直接安装即可)。另外我们来说下插件的安装。
插件在线安装
在线安装我们选择Tools->Downloaded->Available Plugins选择或搜索需要安装的插件进行install。
插件离线下载安装
在下载页面中我们可以点击如下所示的离线插件(*.nbm插件),选择自己想要的插件进行下载:
上面提供了指向所有VisualVM和Java VisualVM版本的插件手动下载的链接。单击插件中心URL并下载所需插件,我们点击VisualVM的2.1.1版本,如下所示:
就会进入到插件下载的目录:
选择我们需要的插件下载即可。安装的时候我们选择Tools->Downloaded->Add Plugins选择已下载的插件安装即可。
功能
我们可以看到主要的分析功能包含以下几个部分,其中后面三个是我们安装的插件。我们选择一些进行简单介绍,然后通过模拟示例来具体分析和发现问题。
Overview
应用程序的基本概况,如进程ID、Main class、启动参数等
Monitor
监控应用程序的CPU、堆、永久区、类加载和线程数的总体情况,通过“执行垃圾回收”和“Dump”按钮可手动执行Full Gc和Dump当前堆快照
Threads
提供详细的线程信息,单机右上角的“线程Dump”可以导出当前所有线程的堆栈信息(相当于使用jstack命令)。如果Visual VM在当前线程中找到死锁,则会以十分显眼的方式在该页面给予提示。
Sampler
显示了CPU和内存两个性能采样器,用于实时监控程序信息。
CPU采样器可以将CPU占用时间定为到方法,可以发现占用CPU时间最长的方法以及线程占用CPU的时间
内存采样器可以查看当前程序的堆信息和每个线程单独占用的情况。
简单示例分析
模拟死锁分析
首先我们先模拟一个简单的java死锁示例:
public class DeadLock implements Runnable{
private static final Logger logger = LoggerFactory.getLogger(DeadLock.class);
private static Object object1 = new Object();
private static Object object2 = new Object();
private int flag;
public DeadLock(int flag) {
super();
this.flag = flag;
}
@Override
public void run() {
if(flag == 1){
synchronized (object1){
logger.info("Thread-1-1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (object2){
logger.info("Thread-1-2");
}
}
} else {
synchronized (object2){
logger.info("Thread-2-1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (object1){
logger.info("Thread-2-2");
}
}
}
}
}
public void test(){
new Thread(new DeadLock(1)).start();
new Thread(new DeadLock(2)).start();
}
通过执行上面的test方法就会产生互相等待的死锁,这时如果我们本地已经打开了visualVM工具我们可以看到非常显眼的提示。
我们进行Thread Dump会发现有很详细的关于死锁的信息供我们分析,我们看到Thread Dump文件的最底部很展示了死锁的信息,这样我们很容易定位到问题。
上面的话我们是通过本地启动VisualVM工具直接进行监控Thread Dump的。我们也可以通过命令行的方式进行Thread Dump。我们来简单演示下。
首先我们通过如下命令查找到应用进程(展示所有java进程):
ps -ef|grep java
或者
jps -l
在进行dump之前我们也可以通过top 查看这个进程目前使用资源的情况。
然后通过jstack命令进行Thread Dump,如下命令所示(例如进程号是32430):
jstack -l > /Users/wukong/Desktop/tool/tdump.tdump 32430
然后将上面下载的tdump.tdump文件加载到VisualVM同样可以定位到死锁详细信息。
模拟内存泄漏分析
我们在本地idea启动的jvm参数简单添加如下参数:
-Xms50m -Xmx50m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/Users/wukong/Desktop/tool/heapdump.hprof
其中
-Xms表示堆内存初始值
-Xmx表示堆内存最大值
-XX:+HeapDumpOnOutOfMemoryError 设置首次内存溢出时导出当时堆中相关信息
-XX:HeapDumpPath=/tmp/heapdump.hprof 指定dump堆信息时的路径
然后启动程序执行如下所示方法:
public static void test(){
List<Student> list = new ArrayList<>();
for (int i = 0; i < 1000000; i++){
Student student = new Student(i, "张寿臣、马三立、常宝堃、侯宝林、刘宝瑞、李伯祥、高英培、马季、唐杰忠、李文华、侯耀文、石富宽、苏文茂、李金斗、冯巩、郭德纲、于谦、姜昆、岳云鹏、孙越等.");
list.add(student);
}
}
由于我们设置的堆内存最小和最大值都只有50M,则启动程序运行请求相关接口就会出现OOM,我们会看到打印的日志出现:
java.lang.OutOfMemoryError: GC overhead limit exceeded
Dumping heap to /Users/wukong/Desktop/tool/heapdump.hprof ...
Heap dump file created [27428405 bytes in 0.147 secs]
现在我们就把生成的dump文件load到visualVM工具中分析,在概要中我们可以清晰看到OOM的红色警示的线程。
右键选择Select in Threads,
进入到如下界面:
我们定位到具体的方法:at com.wk.manage.web.service.impl.TestService.test(TestService.java:15),然后点击上面红色箭头标识的ArrayList。
elementData展开可以看到具体对象,
通过上面简单分析我们可以发现通过这个工具可以很明显的定位到导致OOM的线程以及集合对象。对我们分析问题起到很大帮助。
总结
本文简单介绍了可视化性能监控工具VisualVm的基本功能以及本地模拟了一些示例分析。在线上如果出现内存泄露或者死锁导致系统运行很慢的话,我们首先会生成dump文件然后立即重启保证当前线上应用,然后分析dump文件定位问题和解决问题。
另外VisualVM的很多插件的功能也是很强大的,例如Btrace,VisualGC等插件,大家有兴趣可以研究下。 还有就是VisualVM是支持远程JMX连接的,但是不建议线上使用,所以在此没有介绍,有兴趣的也可以在测试环境自行研究下。
参考书籍:《实战Java虚拟机》