JVisualVM简介与内存泄漏实战分析

一、JVisualVM能做什么

      VisualVM 是Netbeans的profile子项目,已在JDK6.0 update 7 中自带(java启动时不需要特定参数,监控工具在bin/jvisualvm.exe),能够监控线程,内存情况,查看方法的CPU时间和内存中的对 象,已被GC的对象,反向查看分配的堆栈(如100个String对象分别由哪几个对象分配出来的)。

     在JDK_HOME/bin(默认是C:\Program Files\Java\jdk1.6.0_13\bin)目录下面,有一个jvisualvm.exe文件,双击打开,从UI上来看,这个软件是基于NetBeans开发的了。

     可以进行远程和本地监控。远程监控需要打开jmx,下面内容会提到。

     其默认页面为:

左侧分为本地和远程。双击本地中VisualVM线程,可以看到如下监控内容:

具体的介绍参看:

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

二、准备模拟内存泄漏demo

      1、定义静态变量HashMap

      2、分段循环创建对象,并加入HashMap

      代码如下:

[java] view plain copy print?

  1. import java.util.HashMap;  
  2. import java.util.Map;  
  3. public class CyclicDependencies {  
  4.     //声明缓存对象  
  5.     private static final Map map = new HashMap();  
  6.     public static void main(String args[]){  
  7.         try {  
  8.             Thread.sleep(10000);//给打开visualvm时间  
  9.         } catch (InterruptedException e) {  
  10.             e.printStackTrace();  
  11.         }  
  12.         //循环添加对象到缓存  
  13.         for(int i=0; i<1000000;i++){  
  14.             TestMemory t = new TestMemory();  
  15.             map.put("key"+i,t);  
  16.         }  
  17.         System.out.println("first");  
  18.         //为dump出堆提供时间  
  19.         try {  
  20.             Thread.sleep(10000);  
  21.         } catch (InterruptedException e) {  
  22.             e.printStackTrace();  
  23.         }  
  24.         for(int i=0; i<1000000;i++){  
  25.             TestMemory t = new TestMemory();  
  26.             map.put("key"+i,t);  
  27.         }  
  28.         System.out.println("second");  
  29.         try {  
  30.             Thread.sleep(10000);  
  31.         } catch (InterruptedException e) {  
  32.             e.printStackTrace();  
  33.         }  
  34.         for(int i=0; i<3000000;i++){  
  35.             TestMemory t = new TestMemory();  
  36.             map.put("key"+i,t);  
  37.         }  
  38.         System.out.println("third");  
  39.         try {  
  40.             Thread.sleep(10000);  
  41.         } catch (InterruptedException e) {  
  42.             e.printStackTrace();  
  43.         }  
  44.         for(int i=0; i<4000000;i++){  
  45.             TestMemory t = new TestMemory();  
  46.             map.put("key"+i,t);  
  47.         }  
  48.         System.out.println("forth");  
  49.         try {  
  50.             Thread.sleep(Integer.MAX_VALUE);  
  51.         } catch (InterruptedException e) {  
  52.             e.printStackTrace();  
  53.         }  
  54.         System.out.println("qqqq");  
  55.     }  
  56. }  
import java.util.HashMap;
import java.util.Map;
public class CyclicDependencies {
    //声明缓存对象
    private static final Map map = new HashMap();
    public static void main(String args[]){
        try {
            Thread.sleep(10000);//给打开visualvm时间
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //循环添加对象到缓存
        for(int i=0; i<1000000;i++){
            TestMemory t = new TestMemory();
            map.put("key"+i,t);
        }
        System.out.println("first");
        //为dump出堆提供时间
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for(int i=0; i<1000000;i++){
            TestMemory t = new TestMemory();
            map.put("key"+i,t);
        }
        System.out.println("second");
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for(int i=0; i<3000000;i++){
            TestMemory t = new TestMemory();
            map.put("key"+i,t);
        }
        System.out.println("third");
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for(int i=0; i<4000000;i++){
            TestMemory t = new TestMemory();
            map.put("key"+i,t);
        }
        System.out.println("forth");
        try {
            Thread.sleep(Integer.MAX_VALUE);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("qqqq");
    }
}

     3、配置jvm参数如下:

[plain] view plain copy print?

  1. -Xms512m  
  2. -Xmx512m  
  3.  -XX:-UseGCOverheadLimit  
  4.  -XX:MaxPermSize=50m  
         -Xms512m
         -Xmx512m
          -XX:-UseGCOverheadLimit
          -XX:MaxPermSize=50m

     4、运行程序并打卡visualvm监控

三、使用jVisualvm分析内存泄漏

      1、查看Visual GC标签,内容如下,这是输出first的截图

            

这是输出forth的截图:

通过2张图对比发现:

老生代一直在gc,当程序继续运行可以发现老生代gc还在继续:

增加到了7次,但是老生代的内存并没有减少。说明存在无法被回收的对象,可能是内存泄漏了。

如何分析是那个对象泄漏了呢?打开抽样器标签:点击后如下图:

按照程序输出进行堆dump,当输出second时,dump一次,当输出forth时dump一次。

进入最后dump出来的堆标签,点击类:

点击右上角:“与另一个堆存储对比”。如图选择第一次导出的dump内容比较:

比较结果如下:

可以看出在两次间隔时间内TestMemory对象实例一直在增加并且多了,说明该对象引用的方法可能存在内存泄漏。

如何查看对象引用关系呢?

右键选择类TestMemory,选择“在实例视图中显示”,如下所示:

左侧是创建的实例总数,右侧上部为该实例的结构,下面为引用说明,从图中可以看出在类CyclicDependencies里面被引用了,并且被HashMap引用。

如此可以确定泄漏的位置,进而根据实际情况进行分析解决。

四、jVisualvm远程监控tomcat

     1、修改远程tomcat的catalina.sh配置文件,在其中增加:

         

[plain] view plain copy print?

  1. JAVA_OPTS="$JAVA_OPTS  
  2.           -Djava.rmi.server.hostname=192.168.122.128  
  3.           -Dcom.sun.management.jmxremote.port=18999  
  4. -Dcom.sun.management.jmxremote.ssl=false  
  5. -Dcom.sun.management.jmxremote.authenticate=false"  
JAVA_OPTS="$JAVA_OPTS
          -Djava.rmi.server.hostname=192.168.122.128
          -Dcom.sun.management.jmxremote.port=18999
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=false"

这次配置先不走权限校验。只是打开jmx端口。

    2、打开jvisualvm,右键远程,选择添加远程主机:

          

   3、输入主机的名称,直接写ip,如下:

          

        右键新建的主机,选择添加JMX连接,输入在tomcat中配置的端口即可。

   4、双击打开。完毕!

五、扩展知识

线程死锁侦测

jvm优化建议

     本质上是减少GC的次数。

如果是频繁创建对象的应用,可以适当增加新生代大小。常量较多可以增加持久代大小。对于单例较多的对象可以增加老生代大小。比如spring应用中。

GC选择,在JDK5.0以后,JVM会根据当前系统配置进行判断。一般执行-Server命令便可以。gc包括三种策略:串行,并行,并发。

吞吐量大大应用,一般采用并行收集,开启多个线程,加快gc的是否。

响应速度高的应用,一般采用并发收集,比如应用服务器。

     年老代建议配置为并发收集器,由于并发收集器不会压缩和整理磁盘碎片,因此建议配置:

[plain] view plain copy print?

  1. -XX:+UseConcMarkSweepGC  #并发收集年老代  
  2.  -XX:CMSInitiatingOccupancyFraction=80 # 表示年老代空间到80%时就开始执行CMS  
  3.  -XX:+UseCMSCompactAtFullCollection # 打开对年老代的压缩。可能会影响性能,但是可以消除内存碎片。   
  4.  -XX:CMSFullGCsBeforeCompaction=10 # 由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此参数设置运行次  
    -XX:+UseConcMarkSweepGC  #并发收集年老代
     -XX:CMSInitiatingOccupancyFraction=80 # 表示年老代空间到80%时就开始执行CMS
     -XX:+UseCMSCompactAtFullCollection # 打开对年老代的压缩。可能会影响性能,但是可以消除内存碎片。 
     -XX:CMSFullGCsBeforeCompaction=10 # 由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此参数设置运行次

FullGC以后对内存空间进行压缩、整理。 

直接运行linux上的jvisualvm

     下载X-Manager,可以将试图展现在本地机器上。

不受此jvm支持

     保证jvisualvm所属jdk版本和linux上一致。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值