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

转载:https://blog.csdn.net/lmb55/article/details/79267277java

1、概述算法

开发大型 Java 应用程序的过程当中不免遇到内存泄露、性能瓶颈等问题,好比文件、网络、数据库的链接未释放,未优化的算法等。随着应用程序的持续运行,可能会形成整个系统运行效率降低,严重的则会形成系统崩溃。为了找出程序中隐藏的这些问题,在项目开发后期每每会使用性能分析工具来对应用程序的性能进行分析和优化。sql

VisualVM 是一款免费的性能分析工具。它经过 jvmstat、JMX、SA(Serviceability Agent)以及 Attach API 等多种方式从程序运行时得到实时数据,从而进行动态的性能分析。同时,它能自动选择更快更轻量级的技术尽可能减小性能分析对应用程序形成的影响,提升性能分析的精度。数据库

本文将对 VisualVM 的主要功能逐一介绍并探讨如何利用得到的数据进行性能分析及调优。IBM开发文档缓存

2、背景知识:性能分析的主要方式tomcat

1.监视:监视是一种用来查看应用程序运行时行为的通常方法。一般会有多个视图(View)分别实时地显示 CPU 使用状况、内存使用状况、线程状态以及其余一些有用的信息,以便用户能很快地发现问题的关键所在。安全

2.转储:性能分析工具从内存中得到当前状态数据并存储到文件用于静态的性能分析。Java 程序是经过在启动 Java 程序时添加适当的条件参数来触发转储操做的。它包括如下三种:ruby

系统转储:JVM 生成的本地系统的转储,又称做核心转储。通常的,系统转储数据量大,须要平台相关的工具去分析,如 Windows 上的 windbg 和 Linux 上的 gdb.

Java 转储:JVM 内部生成的格式化后的数据,包括线程信息,类的加载信息以及堆的统计数据。一般也用于检测死锁。

堆转储:JVM 将全部对象的堆内容存储到文件。服务器

3.快照:应用程序启动后,性能分析工具开始收集各类运行时数据,其中一些数据直接显示在监视视图中,而另外大部分数据被保存在内部,直到用户要求获取快照,基于这些保存的数据的统计信息才被显示出来。快照包含了应用程序在一段时间内的执行信息,一般有 CPU 快照和内存快照两种类型。网络

CPU 快照:主要包含了应用程序中函数的调用关系及运行时间,这些信息一般能够在 CPU 快照视图中进行查看。

内存快照:主要包含了内存的分配和使用状况、载入的全部类、存在的对象信息及对象间的引用关系等。这些信息一般能够在内存快照视图中进行查看。

4.性能分析:性能分析是经过收集程序运行时的执行数据来帮助开发人员定位程序须要被优化的部分,从而提升程序的运行速度或是内存使用效率,主要有如下三个方面:

CPU 性能分析:CPU 性能分析的主要目的是统计函数的调用状况及执行时间,或者更简单的状况就是统计应用程序的 CPU 使用状况。一般有 CPU 监视和 CPU 快照两种方式来显示 CPU 性能分析结果。

内存性能分析:内存性能分析的主要目的是经过统计内存使用状况检测可能存在的内存泄露问题及肯定优化内存使用的方向。一般有内存监视和内存快照两种方式来显示内存性能分析结果。

线程性能分析:线程性能分析主要用于在多线程应用程序中肯定内存的问题所在。通常包括线程的状态变化状况,死锁状况和某个线程在线程生命期内状态的分布状况等

3、VisualVM 安装

一、VisualVM 安装

VisualVM 是一个性能分析工具,自从 JDK 6 Update 7 之后已经做为 Oracle JDK 的一部分,位于 JDK 根目录的 bin 文件夹下。VisualVM 自身要在 JDK6 以上的版本上运行,可是它可以监控 JDK1.4 以上版本的应用程序。

VisualVM可使用JDK自带的jvisualvm(bin目录下)也能够单独下载,通过实验,发现安装后的JDK中自带的jvisualvm包含的插件比较少大概有五六个左右,单独下载的插件包含的比较多大概有24个左右。它也能够监控1.6之前的JDK,可是对某些模块支持的并非很好,没法显示。

二、安装 VisualVM 上的插件

VisualVM 插件中心提供不少插件以供安装向 VisualVM 添加功能。能够经过 VisualVM 应用程序安装,或者从 VisualVM 插件中心手动下载插件,而后离线安装。另外,用户还能够经过下载插件分发文件 (.nbm 文件 ) 安装第三方插件为 VisualVM 添加功能。

从 VisualVM 插件中心安装插件安装步骤 :

一、从主菜单中选择“工具”>“插件”。 
二、在“可用插件”标签中,选中该插件的“安装”复选框。单击“安装”。 
三、逐步完成插件安装程序

这里写图片描述

根据 .nbm 文件安装第三方插件安装步骤 :

一、从主菜单中选择“工具”>“插件”。 
二、在“已下载”标签中,点击”添加插件”按钮,选择已下载的插件分发文件 (.nbm) 并打开。 
三、选中打开的插件分发文件,并单击”安装”按钮,逐步完成插件安装程序

这里写图片描述

4、功能介绍

下面咱们将介绍性能分析的几种常见方式以及如何使用 VisualVM 性能分析工具进行分析。

一、内存分析 
VisualVM 经过检测 JVM 中加载的类和对象信息等帮助咱们分析内存使用状况,咱们能够经过 VisualVM 的监视标签和 Profiler 标签对应用程序进行内存分析。 
在监视标签内,咱们能够看到实时的应用程序内存堆以及永久保留区域的使用状况。 
内存堆使用状况 
这里写图片描述

永久保留区域使用状况 
这里写图片描述

此外,咱们也能够经过 Applications 窗口右击应用程序节点来启用“在出现 OOME 时生成堆 Dump”功能,当应用程序出现 OutOfMemory 例外时,VisualVM 将自动生成一个堆转储。 
开启“在出现 OOME 时生成堆”功能 
这里写图片描述 
在 Profiler 标签,点击“内存”按钮将启动一个内存分析会话,等 VisualVM 收集和统计完相关性能数据信息,将会显示在性能分析结果。经过内存性能分析结果,咱们能够查看哪些对象占用了较多的内存,存活的时间比较长等,以便作进一步的优化。 
此外,咱们能够经过性能分析结果下方的类名过滤器对分析结果进行过滤。 
内存分析结果 
这里写图片描述

二、CPU 分析 
VisualVM 可以监控应用程序在一段时间的 CPU 的使用状况,显示 CPU 的使用率、方法的执行效率和频率等相关数据帮助咱们发现应用程序的性能瓶颈。咱们能够经过 VisualVM 的监视标签和 Profiler 标签对应用程序进行 CPU 性能分析。

在监视标签内,咱们能够查看 CPU 的使用率以及垃圾回收活动对性能的影响。太高的 CPU 使用率多是因为咱们的项目中存在低效的代码,能够经过 Profiler 标签的 CPU 性能分析功能进行详细的分析。若是垃圾回收活动过于频繁,占用了较高的 CPU 资源,多是由内存不足或者是新生代和旧生代分配不合理致使的等。 
CPU 使用状况 
这里写图片描述 
在 Profiler 标签,点击“CPU”按钮启动一个 CPU 性能分析会话 ,VisualVM 会检测应用程序全部的被调用的方法。当进入一个方法时,线程会发出一个“method entry”的事件,当退出方法时一样会发出一个“method exit”的事件,这些事件都包含了时间戳。而后 VisualVM 会把每一个被调用方法的总的执行时间和调用的次数按照运行时长展现出来。

此外,咱们也能够经过性能分析结果下方的方法名过滤器对分析结果进行过滤。 
CPU 性能分析结果 
这里写图片描述

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

在 VisualVM 的监视标签内,咱们能够查看当前应用程序中全部活动线程和守护线程的数量等实时信息。 
活跃线程状况 
这里写图片描述 
VisualVM 的线程标签提供了三种视图,默认会以时间线的方式展示。另外两种视图分别是表视图和详细信息视图。

时间线视图上方的工具栏提供了缩小,放大和自适应三个按钮,以及一个下拉框,咱们能够选择将全部线程、活动线程或者完成的线程显示在视图中。 
线程时间线视图 
这里写图片描述

线程表视图 
这里写图片描述 
咱们在详细信息视图中不但能够查看全部线程、活动线程和结束的线程的详细数据,并且也能够查看某个线程的详细状况。 
线程详细视图 
这里写图片描述

5、快照功能

咱们可使用 VisualVM 的快照功能生成任意个性能分析快照并保存到本地来辅助咱们进行性能分析。快照为捕获应用程序性能分析数据提供了一个很便捷的方式由于快照一旦生成能够在任什么时候候离线打开和查看,也能够相互传阅。

VisualVM 提供了两种类型的快照: 
一、Profiler 快照:当有一个性能分析会话(内存或者 CPU)正在进行时,咱们能够经过性能分析结果工具栏的“快照”按钮生成 Profiler 快照捕获当时的性能分析数据。 
Profiler 快照 
这里写图片描述

二、应用程序快照:咱们能够右键点击左侧 Applications 窗口中应用程序节点,选择“应用程序快照”为生成一个应用程序快照。应用程序快照会收集某一时刻的堆转储,线程转储和 Profiler 快照,同时也会捕获 JVM 的一些基本信息。 
应用程序快照 
这里写图片描述

6、转储功能

一、线程转储的生成与分析

VisualVM 可以对正在运行的本地应用程序生成线程转储,把活动线程的堆栈踪影打印出来,帮助咱们有效了解线程运行的状况,诊断死锁、应用程序瘫痪等问题。 
线程标签及线程转储功能 
这里写图片描述

当 VisualVM 统计完应用程序内线程的相关数据,会把这些信息显示新的线程转储标签。 
线程转储结果 
这里写图片描述

二、堆转储的生成与分析

VisualVM 可以生成堆转储,统计某一特定时刻 JVM 中的对象信息,帮助咱们分析对象的引用关系、是否有内存泄漏状况的发生等。 
监视标签及堆转储功能 
这里写图片描述

当 VisualVM 统计完堆内对象数据后,会把堆转储信息显示在新的堆转储标签内,咱们能够看到摘要、类、实例数等信息以及经过 OQL 控制台执行查询语句功能。 
堆转储的摘要包括转储的文件大小、路径等基本信息,运行的系统环境信息,也能够显示全部的线程信息。 
堆转储的摘要视图 
这里写图片描述

从类视图能够得到各个类的实例数和占用堆大小数,分析出内存空间的使用状况,找出内存的瓶颈,避免内存的过分使用。 
堆转储的类视图 
这里写图片描述

经过实例数视图能够得到每一个实例内部各成员变量的值以及该实例被引用的位置。首先须要在类视图选择须要查看实例的类。 
选择查询实例数的类 
这里写图片描述 
实例数视图 
这里写图片描述

此外,还能对两个堆转储文件进行比较。经过比较咱们可以分析出两个时间点哪些对象被大量建立或销毁。 
堆转储的比较 
这里写图片描述 
堆转储的比较结果 
这里写图片描述 
线程转储和堆转储都可以另存成文件,以便进行离线分析。 
转储文件的导出 
这里写图片描述

7、实战分析

一、简要说明

打开jdk自带的jvisualvm(bin目录下),程序运行后会自动监控本机运行的java程序(Local标签下,远程服务器上的java程序须要另行配置),Local标签下的第一个VisualVM为jvisualvm对自身的监控,能够看到消耗的资源仍是不多的,第二个为本机的eclipse。 
这里写图片描述

监控项总共分为Overview,Monitor,Threads和一个Sampler。 
(1)Overview(jvm启动参数,系统参数) 
这里写图片描述 
能够看到eclipse的启动参数 
这里写图片描述 
(经过这些启动参数,能够判断程序是否有内存溢出)

(2)Monitor 
这里写图片描述
左上:cpu利用率,gc状态的监控 
右上:堆利用率,永久内存区的利用率 
左下:类的监控 
右下:线程的监控 
performGC:gc的详细运行状态 
HeapDump:堆的详细状态(能够看到堆的概况,里面全部的类,还能点进具体的一个类查看这个类的状态)

(3)Threads 
这里写图片描述
可以显示线程的名称和运行的状态,在调试多线程时必不可少,并且能够点进一个线程查看这个线程的详细运行状况

二、监控服务器上的tomcat

tomcat的配置文件catalina.sh中增长:

JAVA_OPTS="-Dcom.sun.management.jmxremote.port=9998   
    -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Djava.rmi.server.hostname=192.168.58.164" 参数说明: 指定了JMX启动的代理端口,这个端口就是visualvm要链接的端口(9998端口不能被别的程序使用netstat -an|gerp 9998) Dcom.sun.management.jmxremote.port=9998 指定了JMX是否启用ssl Dcom.sun.management.jmxremote.authenticate=false 指定了JMX是否启用鉴权(须要用户名,密码鉴权) Dcom.sun.management.jmxremote.authenticate=false 指定了服务器主机名 Djava.rmi.server.hostname=192.168.58.164 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

这里写图片描述 
填写主机名: 
这里写图片描述 
右键建立一个jmx链接,填写上ip:port便可: 
这里写图片描述

三、监控服务器上的java程序

相较于监控tomcat要麻烦不少,要预先启动jstatd服务(${java_home}/bin目录下) 
jstatd是一个监控JVM从建立到销毁过程当中资源占用状况并提供远程监控接口的RMI(Remote Method Invocation,远程方法调用)服务器程序,它是一个Daemon程序(后台进程),要保证远程监控软件链接到本地的话须要jstatd始终保持运行。

jstatd运行须要经过-J-Djava.security.policy=*指定安全策略,所以咱们须要在服务器上创建一个指定安全策略的文件jstatd.all.policy(我放在了${java_home}/bin目录下),文件内容以下:

grant codebase "file:/home/123/123/jdk1.5.0_15/lib/tools.jar" { permission java.security.AllPermission; }; 
  • 1
  • 2
  • 3

而后使用这个策略文件启动jstatd服务

[sys@sys bin]$ pwd  
/home/sys/jdk1.5.0_15/bin [sys@123 sys]$ ./jstatd -J-Djava.security.policy=./jstatd.all.policy & 
  • 1
  • 2
  • 3

由于监控的过程当中须要jstatd服务一直运行,因此加上了&,若是须要日志也可以使用:

./jstatd -J-Djava.security.policy=./jstatd.all.policy -J-Djava.rmi.server.logCalls=true 
  • 1

接下来就能够在jvisualvm中配置监控该服务器上运行的java程序了,和在jvisualvm中配置监控tomcat服务器的操做过程是同样的。须要特别注意的是,有时在配置远程监控java程序的时候jvisualvm会报一个错误 
点击查看错误详情: 
这里写图片描述 
connection refused to host:127.0.0.1初步判断和主机名有关系。

[sys@sys bin]# hostname -i 127.0.0.1 [sys@sys bin]# hostname 192.168.58.168 
  • 1
  • 2
  • 3
  • 4

修改完重启jstatd服务(网上不少人说要修改主机的/etc/hosts文件,可是我本身测试修改/etc/hosts文件是没有效果的,必需要修改主机名),Add Rempte Host,填写主机名,以后这里要选择添加一个jstatd链接: 
这里写图片描述 
直接选择默认配置便可(默认使用1099端口): 
这里写图片描述 
点击ok后,168上的全部java程序就会自动列出: 
这里写图片描述

注意:推荐一个很是好用的插件VisualGC,tool -> plugin ->aviable plugin: 
这里写图片描述
安装完这个插件后,将会增长新的监控条目Visual GC,能够看到虚拟机内存各个区的使用状况: 
这里写图片描述

四、模拟内存泄漏

import java.util.HashMap;  
import java.util.Map;  
public class MemoryLeckTest { //声明缓存对象 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"); } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57

配置的JVM参数以下:

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

使用jVisualvm分析内存泄漏:

查看Visual GC标签,内容以下,这是输出first的截图 
这里写图片描述
这是输出forth的截图: 
这里写图片描述
经过2张图对比发现: 
这里写图片描述 
老生代一直在gc,当程序继续运行能够发现老生代gc还在继续: 
这里写图片描述 
增长到了7次,可是老生代的内存并无减小。说明存在没法被回收的对象,多是内存泄漏了。 
如何分析是那个对象泄漏了呢?打开抽样器标签:点击后以下图: 
这里写图片描述
按照程序输出进行堆dump,当输出second时,dump一次,当输出forth时dump一次。 
进入最后dump出来的堆标签,点击类: 
这里写图片描述 
点击右上角:“与另外一个堆存储对比”。如图选择第一次导出的dump内容比较: 
这里写图片描述 
比较结果以下: 
这里写图片描述 
能够看出在两次间隔时间内TestMemory对象实例一直在增长而且多了,说明该对象引用的方法可能存在内存泄漏。 
如何查看对象引用关系呢? 
右键选择类TestMemory,选择“在实例视图中显示”,以下所示: 
这里写图片描述左侧是建立的实例总数,右侧上部为该实例的结构,下面为引用说明,从图中能够看出在类CyclicDependencies里面被引用了,而且被HashMap引用。如此能够肯定泄漏的位置,进而根据实际状况进行分析解决。

相关文章
相关标签/搜索