使用 VisualVM 进行性能分析及调优

转自:http://www.cnblogs.com/wade-xu/p/4369094.html

这篇总结的很不错(本人亲自操手学习),留着以后复习备用,很适合入门级的学习者:

VisualVM 是一款免费的,集成了多个 JDK 命令行工具的可视化工具,它能为您提供强大的分析能力,对 Java 应用程序做性能分析和调优。这些功能包括生成和分析海量数据、跟踪内存泄漏、监控垃圾回收器、执行内存和 CPU 分析,同时它还支持在 MBeans 上进行浏览和操作。本文主要介绍如何使用 VisualVM 进行性能分析及调优。

准备工作

自从 JDK 6 Update 7 以后已经作为 Oracle JDK 的一部分,位于 JDK 根目录的 bin 文件夹下,无需安装,直接运行即可。

 内存分析篇

VisualVM 通过检测 JVM 中加载的类和对象信息等帮助我们分析内存使用情况,我们可以通过 VisualVM 的监视标签对应用程序进行内存分析。

1)内存堆Heap

首先我们来看内存堆Heap使用情况,我本机eclipse的进程在visualVM显示如下:

随便写个小程序占用内存大的,运行一下

程序如下:

1package jvisualVM; 2 3publicclass JavaHeapTest { 4publicfinalstaticintOUTOFMEMORY = 200000000; 5 6private String oom; 7 8privateint length; 910StringBuffer tempOOM =new StringBuffer();1112publicJavaHeapTest(int leng) {13this.length = leng;1415inti = 0;16while(i < leng) {17i++;18try {19tempOOM.append("a");20}catch (OutOfMemoryError e) {21              e.printStackTrace();22break;23            }24        }25this.oom = tempOOM.toString();2627    }2829public String getOom() {30return oom;31    }3233publicint getLength() {34return length;35    }3637publicstaticvoid main(String[] args) {38JavaHeapTest javaHeapTest =new JavaHeapTest(OUTOFMEMORY);39        System.out.println(javaHeapTest.getOom().length());40    }4142}

查看VisualVM Monitor tab, 堆内存变大了

在程序运行结束之前, 点击Heap Dump 按钮, 等待一会儿,得到dump结果,可以看到一些Summary信息

点击Classes, 发现char[]所占用的内存是最大的

双击它,得到如下Instances结果

Instances是按Size由大到小排列的

第一个就是最大的, 展开Field区域的 values

StringBuffer类型的 全局变量 tempOOM 占用内存特别大, 注意局部变量是无法通过 堆dump来得到分析结果的。

另外,对于“堆 dump”来说,在远程监控jvm的时候,VisualVM是没有这个功能的,只有本地监控的时候才有。

###转载注明出处:http://www.cnblogs.com/wade-xu/p/4369094.html


2)永久保留区域PermGen

其次来看下永久保留区域PermGen使用情况

运行一段类加载的程序,代码如下:

1package jvisualVM; 2 3import java.io.File; 4import java.lang.reflect.Method; 5import java.net.MalformedURLException; 6import java.net.URL; 7import java.net.URLClassLoader; 8import java.util.ArrayList; 9import java.util.List;1011publicclass TestPermGen {1213privatestaticList insList =newArrayList();1415publicstaticvoidmain(String[] args)throws Exception {1617        permLeak();18    }1920privatestaticvoidpermLeak()throws Exception {21for(inti = 0; i < 1000; i++) {22URL[] urls = getURLS();23URLClassLoader urlClassloader =newURLClassLoader(urls,null);24Class logfClass = Class.forName("org.apache.commons.logging.LogFactory",true,urlClassloader);25Method getLog = logfClass.getMethod("getLog", String.class);26Object result = getLog.invoke(logfClass, "TestPermGen");27            insList.add(result);28System.out.println(i + ": " + result);29        }30    }3132privatestaticURL[] getURLS()throws MalformedURLException {33File libDir =newFile("C:/Users/wadexu/.m2/repository/commons-logging/commons-logging/1.1.1");34File[] subFiles = libDir.listFiles();35intcount = subFiles.length;36URL[] urls =new URL[count];37for(inti = 0; i < count; i++) {38urls[i] = subFiles[i].toURI().toURL();39        }40return urls;41    }424344}

一个类型装载之后会创建一个对应的java.lang.Class实例,这个实例本身和普通对象实例一样存储于堆中,我觉得之所以说是这是一种特殊的实例,某种程度上是因为其充当了访问PermGen区域中类型信息的代理者。

 运行一段时间后抛OutOfMemoryError了, VisualVM监控结果如下:

结论:PermGen区域分配的堆空间过小,我们可以通过设置-XX: PermSize参数和-XX:MaxPermSize参数来解决。

关于PermGen OOM深入分析请参考这篇文章

关于Perform GC, 请参考这篇文章

###转载注明出处:http://www.cnblogs.com/wade-xu/p/4369094.html


CPU分析篇

CPU 性能分析的主要目的是统计函数的调用情况及执行时间,或者更简单的情况就是统计应用程序的 CPU 使用情况。

没有程序运行时的 CPU 使用情况如下图:


运行一段 占用CPU 的小程序,代码如下

package jvisualVM;publicclass MemoryCpuTest {

    publicstaticvoidmain(String[] args)throws InterruptedException {

        cpuFix();

    }

    /**    * cpu 运行固定百分比

    *

    * @throws InterruptedException

    */publicstaticvoidcpuFix()throws InterruptedException {

        // 80%的占有率intbusyTime = 8;

        // 20%的占有率intidelTime = 2;

        // 开始时间longstartTime = 0;


        while(true) {

            // 开始时间startTime = System.currentTimeMillis();


            /*            * 运行时间

            */while(System.currentTimeMillis() - startTime < busyTime) {

                ;

            }


            // 休息时间            Thread.sleep(idelTime);

        }

    }

}

查看监视页面 Monitor tab


过高的 CPU 使用率可能是由于我们的项目中存在低效的代码;

在我们对程序施压的时候,过低的 CPU 使用率也有可能是程序的问题。


点击取样器Sampler, 点击“CPU”按钮, 启动CPU性能分析会话,VisualVM 会检测应用程序所有的被调用的方法,

在CPU samples tab 下可以看到我们的方法cpufix() 的自用时间最长, 如下图:

切换到Thread CPU Time 页面下,我们的 main 函数这个进程 占用CPU时间最长, 如下图:

###转载注明出处:http://www.cnblogs.com/wade-xu/p/4369094.html

线程分析篇

Java 语言能够很好的实现多线程应用程序。当我们对一个多线程应用程序进行调试或者开发后期做性能调优的时候,往往需要了解当前程序中所有线程的运行状态,是否有死锁、热锁等情况的发生,从而分析系统可能存在的问题。

在 VisualVM 的监视标签内,我们可以查看当前应用程序中所有活动线程(Live threads)和守护线程(Daemon threads)的数量等实时信息。


运行一段小程序,代码如下:

package jvisualVM;publicclassMyThreadextends Thread{


    publicstaticvoid main(String[] args) {


        MyThread mt1 =newMyThread("Thread a");

        MyThread mt2 =newMyThread("Thread b");


        mt1.setName("My-Thread-1 ");

        mt2.setName("My-Thread-2 ");


        mt1.start();

        mt2.start();

    }


    public MyThread(String name) {

    }

    publicvoid run() {


        while(true) {


        }

    }


}

Live threads 从11增加两个 变成13了

Daemon threads从8增加两个 变成10了 


VisualVM 的线程标签提供了三种视图,默认会以时间线的方式展现, 如下图:

可以看到两个我们run的程序里启的线程:My-Thread-1 和 My-Thread-2


另外还有两种视图分别是表视图和详细信息视图, 这里看一下每个Thread的详细视图:

###转载注明出处:http://www.cnblogs.com/wade-xu/p/4369094.html

 再来一段死锁的程序,看VisualVM 能否分析出来

package jvisualVM;publicclass DeadLock {

    publicstaticvoid main(String[] args) {

        Resource r1 =new Resource();

        Resource r0 =new Resource();

        Thread myTh1 =new LockThread1(r1, r0);

        Thread myTh0 =new LockThread0(r1, r0);

        myTh1.setName("DeadLock-1 ");

        myTh0.setName("DeadLock-0 ");

        myTh1.start();

        myTh0.start();

    }

}

    class Resource {

        privateint i;


        publicint getI() {

            return i;

        }


        publicvoidsetI(int i) {

            this.i = i;

        }


    }

    classLockThread1extends Thread {

        private Resource r1, r2;


        public LockThread1(Resource r1, Resource r2) {

            this.r1 = r1;

            this.r2 = r2;

        }


        @Override

        publicvoid run() {

            intj = 0;

            while(true) {

                synchronized (r1) {

                    System.out.println("The first thread got r1's lock " + j);

                    synchronized (r2) {

                        System.out.println("The first thread got r2's lock  " + j);

                    }

                }

                j++;

            }

        }


    }

    classLockThread0extends Thread {

        private Resource r1, r2;


        public LockThread0(Resource r1, Resource r2) {

            this.r1 = r1;

            this.r2 = r2;

        }


        @Override

        publicvoid run() {

            intj = 0;

            while(true) {

                synchronized (r2) {

                    System.out.println("The second thread got r2's lock  " + j);

                    synchronized (r1) {

                        System.out.println("The second thread got r1's lock" + j);

                    }

                }

                j++;

            }

        }


    }

打开VisualVM检测到的JVM进程,我们可以看到这个tab在闪,VisualVM已经检测到我这个package下面的DeadLock类出错了

切换到Thread tab, 可以看到死锁了, Deadlock detected!

另外可以点击Thread Dump 线程转储,进一步分析,在这里就不赘述了,有兴趣的读者可以自行实验。


参考文献:

http://www.ibm.com/developerworks/cn/java/j-lo-visualvm/

你可能感兴趣的:(使用 VisualVM 进行性能分析及调优)