JVM监控及诊断工具-GUI篇
使用上一章命令行工具或组合能帮您获取目标Java应用性能相关的基础信息,但它们存在下列局限:
- 无法获取方法级别的分析数据,如方法间的调用关系、各方法的调用次数和调用时间等(这对定位应用性能瓶颈至关重要)。
- 要求用户登录到目标Java应用所在的宿主机上,使用起来不是很方便。
- 分析数据通过终端输出,结果展示不够直观。
为此,JDK提供了一些内存泄漏的分析工具, 如jconsole, jvisualvm等, 用于辅助开发人员定位问题,但是这些工具很多时候并不足以满足快速定位的需求。所以这里我们介绍的工具相对多一些、丰富一些。
JDK自带的:
-
jconsole: JDK自带的可视化监控工具。 查看Java应用程序的运行概况、监控堆信息、永久区(或元空间)使用情况、类加载情况等位置: jdk\bin\jconsole. exe
-
Visual VM:Visual VM是一个工具,它提供了一个可视界面,用于查看Java虚拟机上运行的基于Java技术的应用程序的详细信息。位置: jdk\bin\jvisualvm. exe
-
JMC:Java Mission Control,I内置Java Flight Recorder。能够以极低的性能开销收集Java虛拟机的性能数据。
第三方工具:
- MAT: MAT (Memory Analyzer Tool) 是基于Eclipse的内存分析工具,是一个快速、功能丰富的Java heap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗Eclipse的插件形式
- JProfiler:商业软件,需要付费。功能强大。与VisualVM类似
- Arthas:Alibaba开源的Java诊断工具。 深受开发者喜爱。
- Btrace:Java运行时追踪工具。可以在不停机的情况下,跟踪指定的方法调用、构造函数调用和系统内存等信息。
JConsole
cmd命令行直接输入 JConsole
或者
在 jdk 的bin 目录下点击 JConsole.exe
Visual VM
- VisualVM是一个功能强大的多合一故障诊断和性能监控的可视化工具。
- 它集成了多个JDK命令行工具,使用Visual VM可用于显示虚拟机进程及进程的配置和环境信息(jps,jinfo),监视应用程序的CPU、GC、堆、方法区及线程的信息(jstat、jstack)等,甚至代替JConsole。
在JDK 6 Update 7以后,Visual VM便作为JDK的一部分发布(VisualVM在JDK/bin目录下) 即:它完全免费。
此外,Visual VM也可以作为独立的软件安装:
首页: https://visualvm.github.io/index.html
启动方式
-
cmd 命令行使用 jvisualvm 命令启动
-
在IDEA 安装 jvisualvm 插件 启动
- 安装好后,设置 jvisualvm 路径
连接方式
基本功能
生成 dump 文件
- 点击进程右键 —— 生成 dump
- 保存 dump 文件
生成线程 dump 文件
在程序中出现死锁等情况,也会进行提示。
生成的 dump 文件显示线程信息
MAT
MAT可以分析heap dump文件。 在进行内存分析时,只要获得了反映当前设备内存映像的hprof文件,通过MAT打开就可以直观地看到当前的内存信息。、
一般说来,这些内存信息包含:
-
所有的对象信息,包括对象实例、成员变量、存储于栈中的基本类型值和存储于堆中的其他对象的引用值。
-
所有的类信息,包括classloader、类名称、父类、静态变量等
-
GCRoot到所有的这些对象的引用路径
-
线程信息,包括线程的调用栈及此线程的线程局部变量(TLS)
说明1:
MAT不是一个万能工具,它并不能处理所有类型的堆存储文件。但是比较主流的厂家和格式,例如 Sun,HP,SAP所采用的HPROF二进制堆存储文件,以及IBM的PHD堆存储文件等都能被很好的解析。
说明2:
最吸引人的还是能够快速为开发人员生成内存泄漏报表,方便定位问题和分析问题。虽然MAT有如此强大的功能,但是内存分析也没有简单到一键完成的程度,很多内存问题还是需要我们从MAT展现给我们的信息当中通过经验和直觉来判断才能发现。
生成 dump 的四种方式:
- 方法一: 通过前一章介绍的jmap工具生成,可以生成任意-个java进程的dump文件;
- 方法二: 通过配置JVM参数生成。
- 选项"-XX: +HeapDumpOnOutOfMemoryError" 或”-XX: +HeapDumpBeforeFullGC"
- 选项" -XX :HeapDumpPath"所代表的含义就是当程序出现0utofMemory时,将会在相应的目录下生成一份dump文件。如果不指定选项“-XX :HeapDumpPath”则在当前目录下生成dump文件。
对比:考虑到生产环境中几乎不可能在线对其进行分析,大都是采用离线分析,因此使用 jmap+MAT工
具是最常见的组合。
- 方法三: 使用VisualVM可以 导出堆dump文件
- 方法四: 使用MAT既可以打开一个已有的堆快照,也可以通过MAT直接从活动Java程序中导出堆快照。该功能将借助jps列出当前正在运行的Java 进程,以供选择并获取快照。
Histogram
可以看到程序中所有的类,以及所占用内存大小。
左侧有类的详细信息。
浅堆和深堆
浅堆(Shallow Heap)是指一个对象所消耗的内存。在32位系统中,一个对象引用会占据4个字节,一个int类型会占据4个字节,long型变量会占据8个字节,每个对象头需要占用8个字节。根据堆快照格式不同,对象的大小可能会向8字节进行对齐。
以String为例: 2个int值共占8字节, 对象引用占用4字节,对象头8字节,合计20字节,向8字节对齐,
故占24字节。(jdk7中)
int hash32
int hash
ref value C:\Users\Administratu
这24字节为String对象的浅堆大小。它与String的value实际取值无关,无论字符串长度如何,浅堆大小始终是24字节。
保留集(Retained Set)
对象A的保留集指当对象A被垃圾回收后,可以被释放的所有的对象集合(包括对象A本身),即对象A的保留集可以被认为是只能通过对象A被直接或间接访问到的所有对象的集合。通俗地说,就是指仅被对象A所持有的对象的集合。
深堆(Retained Heap)
深堆是指对象的保留集中所有的对象的浅堆大小之和。
注意: 浅堆指对象本身占用的内存,不包括其内部引用对象的大小。一个对象的深堆指只能通过该对象访问到的(直接或间接)所有对象的浅堆之和,即对象被回收后,可以释放的真实空间。
实际大小
另外一个常用的概念是对象的实际大小。这里,对象的实际大小定义为一个对象所能触及的所有对象的浅堆大小之和,也就是通常意义上我们说的对象大小。与深堆相比,似乎这个在日常开发中更为直观和被人接受,但实际上,这个概念和垃圾回收无关。
下图显示了一个简单的对象引用关系图,对象A引用了C和D,对象B引用了C和E。那么对象A的浅堆大小只是A本身,不含C和D,而A的实际大小为A、C、D三者之和。而A的深堆大小为A与D之和,由于对象C还可以通过对象B访问到,因此不在对象A的深堆范围内。
浅堆大小是指对象引用本身的大小
实际大小是指浅堆包括它所引用的对象大小之和
深堆大小是指只有浅堆指向的对象大小之和
支配树
支配树的概念源自图论
MAT提供了一个称为支配树(Dominator Tree)的对象图。支配树体现了对象实例间的支配关系。在对象引用图中,所有指向对象B的路径都经过对象A[则认为对象A支配对象B。如果对象A是离对象B最近的一个支配对象,则认为对象A为对象B的直接支配者。支配树是基于对象间的引用图所建立的,它有以下基本性质:
-
对象A的子树(所有被对象A支配的对象集合)表示对象A的保留集(retained set) ,即深堆。
-
如果对象A支配对象B,那么对象A的直接支配者也支配对象B。
-
支配树的边与对象引用图的边不直接对应。
如下图所示: 左图表示对象引用图,右图表示左图所对应的支配树。
对象A和B由根对象直接支配,由于在到对象C的路径中,可以经过A,也可以经过B,因此对象C的直接支配者也是根对象。对象F与对象D相互引用,因为到对象F的所有路径必然经过对象D,因此,对象D是对象F的直接支配者。而到对象D的所有路径中,必然经过对象C,即使是从对象F到对象D的引用,从根节点出发,也是经过对象C的,所以,对象D的直接支配者为对象C。
同理,对象E支配对象G。到达对象H的可以通过对象D,也可以通过对象E,因此对象D和E都不能支配对象H,而经过对象C既可以到达D也可以到达E,因此对象C为对象H的直接支配者。
再谈:内存泄漏
可达性分析算法来判断对象是否是不再使用的对象,本质都是判断一个对象是否还被引用。那么对于这种情况下,由干代码的实现不同就会出现很多种内存泄漏问题(让JVM误以为此对象还在引用中,无法回收,造成内存泄漏)。
内存泄漏(memory leak) 的理解
严格来说,只有对象不会再被程序用到了,但是GC又不能回收他们的情况,才叫内存泄漏。但实际情况很多时候一些不太好的实践(或疏忽)会导致对象的生命周期变得很长甚至导致0OM,也可以叫做宽泛意义上的“内存泄漏”
对象X引用对象Y,X的生命周期比Y的生命周期长;
那么当Y生命周期结束的时候,x依然引用着Y,这时候,垃圾回收期是不会回收对象Y的;
如果对象X还引用着生命周期比较短的A、B、C,对象A又引用着对象a、b、C,这样就可能造成大量无用的对象不能被回收,进而占据了内存资源,造成内存泄漏,直 到内存溢出。
泄漏的分类
- 经常发生:发生内存泄露的代码会被多次执行,每次执行,泄露一块内存;
- 偶然发生:在某些特定情况下才会发生;
- 一次性:发生内存泄露的方法只会执行次;
- 隐式泄漏:一直占着内存不释放,直到执行结束;严格的说这个不算内存泄漏,因为最终释放掉了,但是如果执行时间特别长,也可能会导致内存耗尽。
内存泄漏的八种情况
1、静态集合类,如HashMap、 LinkedList等等。 如果这些容器为静态的,那么它们的生命周期与JVM程序一致,则容器中的对象在程序结束之前将不能被释放,从而造成内存泄漏。简单而言,长生命周期的对象持有短生命周期对象的引用,尽管短生命周期的对象不再使用,但是因为长生命周期对象持有它的引用而导致不能被回收。
2、单例模式,和静态集合导致内存泄露的原因类似,因为单例的静态特性,它的生命周期和JVM的生命周期一样长,所以如果单例对象如果持有外部对象的引用,那么这个外部对象也不会被回收,那么就会造成内存泄漏。
3、内部类持有外部类,如果一个外部类的实例对象的方法返回了一个内部类的实例对象。这个内部类对象被长期引用了,即使那个外部类实例对象不再被使用,但由于内部类持有外部类的实例对象,这个外部类对象将不会被垃圾回收,这也会造成内存泄漏。
4、各种连接,如数据库连接、网络连接和I0连接等。
在对数据库进行操作的过程中,首先需要建立与数据库的连接,当不再使用时,需要调用close方法来释放与数据库的连接。只有连接被关闭后,垃圾回收器才会回收对应的对象。否则,如果在访问数据库的过程中,对Connection、Statement 或ResultSet不显性地关闭,将会造成大量的对象无法被回收,从而引起内存泄漏。
5、 变量不合理的作用域。-般而言,一个变量的定义的作用范围大于其使用范围,很有可能会造成内存泄漏。另一方面,如果没有及时地把对象设置为nu1l,很有可能导致内存泄漏的发生。
6、改变哈希值
7、缓存泄露
8、监听器和回调
OQL 语句
MAT支持一种类似 于SQL的查询语言OQL (Object Query Language)。OQL使用类SQL语法,可以在堆中进行对象的查找和筛选。
Select子句
在MAT中,Select 子句的格式与SQL基本一致,用于指定要显示的列。Select子句中可以使用“*”,查看结果对象的引用实例(相当于outgoing references) 。
SELECT * FROM java. util. Vector v
使用“OBJECTS"关键字,可以将返回结果集中的项以对象的形式显示。
SELECT objects v. elementData FROM java. util. Vector v
SELECT OBJECTS s.value FROM java.lang.String s
在Select子句中,使用“AS RETAINED SET” 关键字可以得到所得对象的保留集。
SELECT AS RETAINED SET * FROM com. atguigu . mat . Student
“DISTINCT”关键字用于在结果集中去除重复对象。
SELECT DISTINCT OBJECTS classof(s) FROM java.lang.String s
From子句
From子句用于指定查询范围,它可以指定类名、正则表达式或者对象地址。
SELECT * FROM java.lang.String s
下例使用正则表达式,限定搜索范围,输出所有com. atguigu包下所有类的实例
SELECT * FROM ”com.atguigu… *"
也可以直接使用类的地址进行搜索。使用类的地址的好处是可以区分被不同ClassLoader加载的同一种类型。
select * from 0x37 a0b4d
Where子句
Where子句用于指定OQL的查询条件。0QL查询将只返回满足Where子句指定条件的对象。
Where子句的格式与传统SQL极为相似。下例返回长度大于10的char数组。
SELECT * FROM char[] s WHERE s.@length>10
下例返回包含“java”子字符串的所有字符串,使用“LIKE”操作符,“LIKE” 操作符的操作参数为正则表达式。
SELECT * FROM java.lang.String s WHERE toString(s) LIKE “. java.”
下例返回所有value域不为nu11的字符串,使用“=”操作符。
SELECT * FROM java.lang.String s where s. value != nu11
Where子句支持多个条件的AND、OR运算。下 例返回数组长度大于15,并且深堆大于1000字节的所有Vector对象。
SELECT * FROM java. util.Vector v WHERE v. elementData. @1ength>15 AN D v . @retainedHeapSize>1000
内置对象和方法
OQL中可以访问堆内对象的属性,也可以访问堆内代理对象的属性。访问堆内对象的属性时,格式如下:
[ . ] . .
其中alias为对象名称。访问java. io. File对象的path属性,并进一步访问path的value属性:
SELECT toString(f .path. value) FROM java.io.File f
下例显示了String对象的内容、objectid和objectAddress
SELECT s.toString(), s.@objectId, s. @objectAddress FROM java.lang.String s
下例显示java. util. Vector内部数组的长度。
SELECT v.elementData. @1ength FROM java. util.Vector v
下例显示了所有的java. util. Vector对象及其子类型
select from INSTANCEOF java. util. Vector
Jprofiler
下载与安装
下载地址:https://plugins.jetbrains.com/plugin/253-jprofiler
在 IDEA 中下载 JProfiler 插件:
配置 JProfiler 的执行文件:
最终出现这俩个图标就 OK 了:
数据采集方式
JProfier数据采集方式分为两种: Sampling( 样本采集)和Instrumentation ( 重构模式)
Instrumentation : 这是JProfiler全功能模式。在class加载之 前,JProfier把相关功能代码写入到需要分析的class的bytecode中,对正在运行的jvm有一定影响。
- 优点:功能强大2T在此设置中,调用堆栈信息是准确的。
- 缺点:若要分析的class较多,则对应用的性能影响较大, CPU开销可能很高(取决于Filter的控制)。因此使用此模式一般配合Filter使用,只对特定的类或包进行分析。
Sampling:类似于样本统计: 每隔一定时间(5ms )将每个线程栈中方法栈中的信息统计出来。
- 优点:对CPU的开销非常低,对应用影响小(即使你不配置任何Filter)
- 缺点:一些数据/特性不能提供(例如:方法的调用次数、执行时间)
注: JProfiler本身没有指出数据的采集类型,这里的采集类型是针对方法调用的采集类型。因为JProfiler的绝大多数核心功能都依赖方法调用采集的数据,所以可以直接认为是JProfiler的数据采集类型。
arthas
Arthas (阿尔萨斯)是Alibaba开源的Java诊断工具, 深受开发者喜爱。在线排查问题,无需重启动态跟踪Java代码;实时监控JVM状态。
官网网站:arthas (aliyun.com)
从文档中下载:
下载解压后,在目录中打开 cmd:
提前启动程序,使用 java -jar 的方式启动,启动后选择对应的 pid 进行监控
基础指令
JVM 相关指令
- dashboard - 当前系统的实时数据面板
- -i : 多久打印一次,默认毫秒
- -n :总共打印几次
- getstatic - 查看类的静态属性
- heapdump - dump java heap, 类似 jmap 命令的 heap dump 功能
- jvm - 查看当前 JVM 的信息
- logger - 查看和修改 logger
- mbean - 查看 Mbean 的信息
- memory - 查看 JVM 的内存信息
- ognl - 执行 ognl 表达式
- perfcounter - 查看当前 JVM 的 Perf Counter 信息
- sysenv - 查看 JVM 的环境变量
- sysprop - 查看和修改 JVM 的系统属性
- thread - 查看当前 JVM 的线程堆栈信息
- vmoption - 查看和修改 JVM 里诊断相关的 option
- vmtool - 从 jvm 里查询对象,执行 forceGc
Class、ClassLoader 相关指令
- classloader - 查看 classloader 的继承树,urls,类加载信息,使用 classloader 去 getResource
- dump - dump 已加载类的 byte code 到特定目录
- jad - 反编译指定已加载类的源码
- mc - 内存编译器,内存编译
.java
文件为.class
文件 - redefine - 加载外部的
.class
文件,redefine 到 JVM 里 - retransform - 加载外部的
.class
文件,retransform 到 JVM 里 - sc - 查看 JVM 已加载的类信息
- sm - 查看已加载类的方法信息
monitor/watch/trace 相关指令
- monitor - 方法执行监控
- stack - 输出当前方法被调用的调用路径
- trace - 方法内部调用路径,并输出方法路径上的每个节点上耗时
- tt - 方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测
- watch - 方法执行数据观测
//arthas.aliyun.com/doc/sm.html) - 查看已加载类的方法信息
各位彭于晏,如有收获点个赞不过分吧…✌✌✌