当前位置: 代码迷 >> 综合 >> 好用的性能分析工具——VisualVM
  详细解决方案

好用的性能分析工具——VisualVM

热度:86   发布时间:2023-12-12 23:40:13.0

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,就可以了。
参考资料:

问题三:点击VisualVM的“Profiler”tab的时候,会弹出一个没有任何提示信息的错误小框,如下图:
 
并且报这个错误
Exception in thread “Attach Listener” java.lang.reflect.InvocationTargetException
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
        at java.lang.reflect.Method.invoke(Unknown Source)
        at sun.instrument.InstrumentationImpl.loadClassAndStartAgent(Unknown Source)
        at sun.instrument.InstrumentationImpl.loadClassAndCallAgentmain(Unknown Source)
Caused by: java.lang.NullPointerException
        at org.netbeans.lib.profiler.server.ProfilerActivate15.getArchiveFile(ProfilerActivate15.java:75)
        at org.netbeans.lib.profiler.server.ProfilerActivate15.activate(ProfilerActivate15.java:96)
        at org.netbeans.lib.profiler.server.ProfilerActivate15.agentmain(ProfilerActivate15.java:61)
        … 6 more
查了很久,发现是我这个安装这个工具的目录路径中是有中文的,可能是因为中文的关系所以它在解析目录的时候出错了。虽然这个bug的原因比较白痴,不过通过查资料,发现InvocationTargetException这个异常一般是在通过反射方式来获取接口的实际实现的方法时会抛出的。而这个异常由于并没有覆盖getMessage方法,所以会弹出一个没有任何提示信息的小框了,也算是有收获。
参考资料: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 TestDeadLock implements Runnable
{
 public int flag = 1;
 static Object o1= new Object();
 static Object o2 = new Object();
 public static void main(String[] args)
 {
  TestDeadLock td1 = new TestDeadLock();
  TestDeadLock td2 = new TestDeadLock();
  td1.flag=1;
  td2.flag=0;
  Thread t1 = new Thread(td1);
  Thread t2 = new Thread(td2);
  t1.start();
  t2.start();
 }

 public void run()
 {
  if(flag == 1){
   synchronized(o1){
    try
    {
     Thread.sleep(500);
    }
    catch (InterruptedException e)
    {
    }
    synchronized(o2){
     System.out.println(”1″);
    }
   }
  }
  if(flag==0){
   synchronized(o2){
    try
    {
     Thread.sleep(500);
    }
    catch (InterruptedException e){
    }
    synchronized(o1){
     System.out.println(”0″);
    }
   }
  }
 }

}

然后把这段代码放到linux服务端的应用中,随便把这段代码写到哪个类都行。接着再启动服务器,启动VisualVM的远程监控,然后执行这段代码。
你会很容易的发现在VisualVM的“线程”tab中有两条线程的颜色特别鲜艳,如下图:

 
很明显,Thread-32和Thread-33是有问题的两条线程:
再按一下上图中的“线程 dump”按钮,找到这两个线程的详细信息:
 
查看一下上图右下角有关于Thread33这个线程的详细信息,很明显,是MsmPlanList这个类有问题。

二:查找内存异常:
同样的,从激烟那里搞来他上次分享时写的能占用很多内存的代码
public class TestMemory {
 private List<Long> testMemory = new ArrayList<Long>();

 public 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));
   }
  }
  
 }
}

随便把这段代码写到哪个类都行,然后运行jboss服务器,执行指定的类,然后查看VisualVM中“监视”这个tab,看下图右下角四副图中的堆内存使用情况图,发现每次执行程序,堆内存确实是大了一点。

 

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

 
通过“大小”的排序,发现java.util.Long所占用的内存是最大的。好,就查这个类的实例,双击它(注2:),得到如下页面:

然后由于这些实例是从小到大排序的,所以找到最后一个实例,如图:
 

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

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

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

注2:这里还想提醒的是,如果你在得到实例的过程中报了如下图的错误:
 
那么,就表明你给VisualVM分配的堆内存不够,找到${visualvm}/etc/visualvm.conf 这个文件,修改
default_options=”-J-Xms24m -J-Xmx192m -J-为
default_options=”-J-Xms24m -J-Xmx1024m -J-
再重启VisualVM就行了。