好用的性能分析工具–VisualVM

原博客地址:http://zorufa876.iteye.com/blog/625649


最近在学TDA(Thread Dump Analyzer)的时候,发现一款很好用的查看JVM的工具–VisualVM,这个工具是Sun在JDK1.6 Update7之后的版本中推出的,就放在bin目录下面,惭愧的是我竟然一直都没发现。
    简单说来,VisualVM是jConsole的升级版,但它可比jConsole好用多了。它能为您提供强大的thread 和heap分析能力。它囊括的命令行工具包括 JConsole, jstack,jstat, jmap ,jps。具体怎么操作我就不说了,很简单,大家可以参考这几个网址:
VisualVM入门指南:https://visualvm.dev.java.net/zh_CN/gettingstarted.html 
VIsualVM介绍:    http://www.iteye.com/topic/516447 
jstatd介绍:      http://java.sun.com/javase/6/docs/technotes/tools/share/jstatd.html

下面我就提一下我在学习操作这个工具的过程中遇到的几个问题,不过在看问题一和问题二之前最好先阅读一下jstatd介绍会比较好。

问题一 :在服务端启动jstatd的时候,我执行的命令如下:jstatd -J-Djava.security.policy=jstatd.all.policy
我发现它会报
java.rmi.ConnectIOException: non-JRMP server at remote endpoint
        at sun.rmi.transport.tcp.TCPChannel.createConnection(TCPChannel.java:230)
        at sun.rmi.transport.tcp.TCPChannel.newConnection(TCPChannel.java:184)
        at sun.rmi.server.UnicastRef.newCall(UnicastRef.java:322)
        at sun.rmi.registry.RegistryImpl_Stub.rebind(Unknown Source)
        at java.rmi.Naming.rebind(Naming.java:160)
        at sun.tools.jstatd.Jstatd.bind(Jstatd.java:40)
        at sun.tools.jstatd.Jstatd.main(Jstatd.java:126)
这个错误,而报这个错误的
原因是因为我在上面的命令中没有指定端口号,所以jstatd就采用默认的端口号1099,可是由于这个端口号已经被别的程序给占用了,所以会报上面这个 错误。因此,我就用指定的端口号来执行jstatd
比如:jstatd -J-Djava.security.policy=jstatd.all.policy -p 2222,问题就解决了。

问题二 :在执行了jstatd -J-Djava.security.policy=jstatd.all.policy -p 2222这个命令以后,后面又用ctrl+C把它给stop了,那么你再重新启动2222这一端口的jstatd的话,你会发现VisualVM调用不到 这个jstatd了,并且服务器过一会儿就会报:
java.rmi.ConnectIOException: error during JRMP connection establishment; nested exception is:
        java.net.SocketTimeoutException: Read timed out
        at sun.rmi.transport.tcp.TCPChannel.createConnection(TCPChannel.java:286)
        at sun.rmi.transport.tcp.TCPChannel.newConnection(TCPChannel.java:184)
        at sun.rmi.server.UnicastRef.newCall(UnicastRef.java:322)
        at sun.rmi.registry.RegistryImpl_Stub.rebind(Unknown Source)
        at java.rmi.Naming.rebind(Naming.java:160)
        at sun.tools.jstatd.Jstatd.bind(Jstatd.java:40)
        at sun.tools.jstatd.Jstatd.main(Jstatd.java:126)
Caused by: java.net.SocketTimeoutException: Read timed out
        at java.net.SocketInputStream.socketRead0(Native Method)
        at java.net.SocketInputStream.read(SocketInputStream.java:129)
        at java.io.BufferedInputStream.fill(BufferedInputStream.java:218)
        at java.io.BufferedInputStream.read(BufferedInputStream.java:237)
        at java.io.DataInputStream.readByte(DataInputStream.java:248)
        at sun.rmi.transport.tcp.TCPChannel.createConnection(TCPChannel.java:228)
        … 6 more
这样的错误。这是因为虽然jstatd被stop了,但是这个端口的进程还是存在的,所以要么你再换一个没用过的端口号,要么你把原来的还在被占用的端口 号给kill掉,然后重新启动jstack,再重新启动VisualVM,就可以了。

参考资料:http://java.sun.com/j2se/1.4.2/docs/api/java/lang/reflect/InvocationTargetException.html 
          http://blog.csdn.net/dingyuan963/archive/2009/07/09/4333071.aspx

 
会操作工具了,就来实际模拟一下怎么找bug吧,

一:查找线程死锁:
先写一段死锁的代码

public class Deadlocker {  
  
    private static Object lock_1 = new int[1];  
    private static Object lock_2 = new int[1];  
  
    public class Thread1 extends Thread {  
  
        @Override  
        public void run() {  
            System.out.println("thread 1 start");  
            synchronized (lock_1) {  
                try {  
                    Thread.sleep(5000);  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
                System.out.println("thread 1 get lock 1 need lock 2");  
                synchronized (lock_2) {  
                }  
            }  
            System.out.println("thread 1 end");  
        }  
    }  
  
    public class Thread2 extends Thread {  
  
        @Override  
        public void run() {  
            System.out.println("thread 2 start");  
            synchronized (lock_2) {  
                try {  
                    Thread.sleep(5000);  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
                System.out.println("thread 2 get lock 2 need lock 1");  
                synchronized (lock_1) {  
                }  
            }  
            System.out.println("thread 2 end");  
        }  
    }  
  
    public static void main(String[] args) {  
        Thread1 thread1 = new Deadlocker().new Thread1();  
        Thread2 thread2 = new Deadlocker().new Thread2();  
        thread1.start();  
        thread2.start();  
    }  
}  

直接eclipse里面运行,启动VisualVM的远程监控。
你会很容易的发现在VisualVM的“线程”tab中有两条线程的颜色特别鲜艳,如下图:

好用的性能分析工具–VisualVM_第1张图片
很明显,Thread-1和Thread-0是有问题的两条线程:
再按一下上图中的“线程 dump”按钮,找到这两个线程的详细信息:
好用的性能分析工具–VisualVM_第2张图片
可以看出互相锁住了资源


二:查找内存异常:
同样的,从激烟那里搞来他上次分享时写的能占用很多内存的代码

import java.util.ArrayList;
import java.util.List;


public class TestMemory {
 private static  List testMemory = new ArrayList();
 public static void test(){
  if (testMemory.isEmpty()) {
   testMemory.add(0L);
  }
  if (testMemory.size() < Integer.MAX_VALUE - 1000000) {
   int start = testMemory.size() - 1;
   for (int i = 0; i < 1000000; i++) {
    testMemory.add((long) (start + i));
    if (i == 800000 ) {
    String s = "test";
    }
   }
  }
  
 }
 
 public static void main(String[] args) {
 
test();
 }
}

直接debug运行,在String s="test"这里打个断点,然后查看VisualVM中“监视”这个tab

 好用的性能分析工具–VisualVM_第3张图片

要想确定到底是调用哪个方法导致堆内存占用太多,需要点击“堆 dump”按钮(注1:),这一步需要一些时间,得到如下页面:

 好用的性能分析工具–VisualVM_第4张图片
通过“大小”的排序,发现java.util.Long所占用的内存是最大的。好,就查这个类的实例,双击它(注2:),得到如下页面:

好用的性能分析工具–VisualVM_第5张图片

然后由于这些实例是从小到大排序的,所以找到最后一个实例,如图:
 好用的性能分析工具–VisualVM_第6张图片

可以发现testMemory就是那个占用内存最大的实例了。

 
最后还想再提醒一句 ,VisualVM的功能是不止这些的,点击菜单栏上的工具->插件选项,你会发现 VisualVM还有很多好用的插件没安装,比如VisualVM-TDA-Module这个插件,它就是整合了TDA的功能的,至于其他的各位好好去发 掘吧。

注1: 对于“堆 dump”来说,在远程监控jvm的时候,VisualVM是没有这个功能的,只有本地监控的时候才有。另外,就算是本地监控,它在dump和得到实例的 速度那是相当的慢的。所以鉴于这几个原因,不建议用VisualVM,而是用jmap加上Mat来分析内存情况。


你可能感兴趣的:(visualvm,java,visualvm)