【Java线程与内存分析工具】VisualVM与MAT简明教程
前言
本文将简要介绍Java线程与内存分析工具VisualVM和MAT的使用,进一步的学习可参考官网或工具帮助(例如MAT:Help -> Welcome -> Tutorials),并在实际工作中融会贯通。
VisualVM
Java VisualVM是JDK1.6后自带的可视化工具,提供图形界面以实时监控应用程序的线程状态、CPU和内存资源消耗情况,并且可以保存快照以便脱机分析程序的性能瓶颈。
安装与配置
JDK1.6之后已自带VisualVM工具(jvisualvm.exe)。若使用非Oracle JDK,可自行登录官网下载VisualVM并安装。
工具下载后,需要在visualvm_143\etc\visualvm.conf
里手工配置JDK路径(visualvm_jdkhome)。
VisualVM可监控本地或远程的Java程序。使用远程监控时需要在服务端启动JMX服务。首先,在远程程序的启动参数中增加如下JVM参数:
-Djava.rmi.server.hostname=10.186.189.98(远程服务器IP地址) -Dcom.sun.management.jmxremote.port=8090(JMX远程监听端口) -Dcom.sun.management.jmxremote.ssl=false(禁用SSL) -Dcom.sun.management.jmxremote.authenticate=false(不启用用户认证)
然后重启远程程序。此时,通过netstat -ano | findstr 8090
(Windows)或netstat -anlp | grep 8090
(Linux)查看端口已处于Listening状态,表明可以进行远程JMX连接。
除单独使用VisualVM工具外,也可在IDEA中集成VisualVM launcher插件。通过File-> Setting-> Plugins -> Browers Repositrories搜索VisualVM Launcher安装并重启IDEA后,会出现菜单和按钮两种启动方式:
点击按钮后会出现选择VisualVM路径,选择VisualVM可执行文件即可。此后,点击启动会打开一个VisualVM窗口。
本地使用
本节结合代码示例介绍VisualVM的界面功能。示例代码如下:
package thread;
public class InfiniteLoop {
public static void main(String[] args) {
Thread t1 = new Thread(new ImplicitLoop(), "ImplicitLoop");
Thread t2 = new Thread(new ExplicitLoop(),"ExplicitLoop");
t1.start();
t2.start();
}
}
class ExplicitLoop extends Thread {
@Override
public void run() {
while (true) {
System.out.println("I work hard!");
}
}
}
class ImplicitLoop extends Thread {
@Override
public void run() {
for (byte i = 0; i < 150; i += 2) { //此处因数值溢出导致死循环
System.out.println("I've worked " + i + " hours!");
if (i >= 120) {
try {
System.out.println("I'll take a short break...");
Thread.sleep(20);
System.out.println("I wake up!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
启动VisualVM查看本地监控信息,界面如下:
左侧"本地(Local)"下列出包含InfiniteLoop示例在内的本地Java进程,右侧以概述(Overview)、监视(Monitor)、线程(Threads)、抽样器(Sampler)等页签展示出详细信息。
其中,概述页可查看进程的基本信息、JVM启动参数、系统属性(同jinfo -sysprops <pid>
)等信息。
监视页可查看CPU、内存(堆与元空间)、类和线程的的实时折线图。执行垃圾回收(Perform GC)按钮可以触发系统GC,堆Dump(Heap Dump)按钮可在指定目录生成堆转储(Dump)文件。
注意,本地监控时点击堆Dump(Heap Dump)按钮会自动加载打开生成的dump文件,而远程监控时需要将远程主机上生成的dump文件拷贝至本地再手工加载。此外,VisualVM加载分析内存Dump文件时非常缓慢,建议使用MAT来分析内存Dump。
线程页可详细查看每个线程的运行时间及状态。线程Dump(Thread Dump)按钮可生成线程dump文件(类jstack <pid>
)。
图中,时间线里展示活动线程的运行、休眠(sleep)、等待(o.wait)、驻留(空闲)和监视(同步阻塞)状态,并可通过缩放按钮更细致地观察线程状态。
Threads inspector
插件可展示单个或多个线程的堆栈。图中仅勾选了ImplicitLoop线程,由堆栈可知其阻塞在System.out.println("I've worked " + i + " hours!")
行——执行该方法会先加锁!通过Refresh按钮刷新堆栈,会发现ImplicitLoop线程有时会处于休眠状态。
抽样器页以一定的时间间隔对CPU、内存进行采样,可检查出占用CPU时间较多或占用内存空间较大的线程,有助于性能调优。对CPU采样时,该页提供CPU样例(CPU samples)和线程CPU时间(Thread CPU time)两个子页签,前者可用于分析调用链上的方法耗时,后者可用于比较线程CPU耗时。
VisualVM还提供不少有用的插件,例如Visual GC(查看垃圾回收的状态)。可通过工具(Tools) -> 插件(Plugins)下载插件。
远程监控
在VisualVM左侧点击远程(Remote) -> 添加远程主机(Add Remote Host),填写服务器IP地址。
然后点击远程主机,右键"添加JMX连接(Add JMX Connection)",填写JMX端口号并勾选"不要求SSL连接(Do not require SSL)"。
在添加的JMX连接上右键"打开(Open)"或直接双击,在界面右侧可看到监控面板。
MAT
MAT(Memory Analyzer Tool)是一个快速、功能丰富的JAVA堆转储文件分析工具,可帮助开发者发现内存泄漏和减少内存消耗。
使用场景
MAT常见的使用场景如下:
- OOM(OutOfMemoryError异常),原因通常有:
- 对象已死但无法通过垃圾收集器自动回收,内存不断泄露——需找到泄露的代码位置并加以修复
- 产生大量生命周期太长或持有状态时间过长的对象——除增大堆分配空间外,考虑优化存储结构或代码逻辑
- CPU负载冲高、线程死锁等(类似VisualVM)
- 窥探内存对象的内容,例如:
- 排障时环境不允许进行Debug调试或添加日志打印
- 扫描内存中是否存在常驻的明文口令等敏感信息
安装与配置
从官网下载单机版MAT工具,解压后直接运行MAT目录的MemoryAnalyzer.exe即可启动MAT。
若待分析的dump文件过大,可增大安装目录下MemoryAnalyzer.ini文件里的Xmx参数值(默认1G)。注意,Xmx取值不能大于运行环境的的系统内存,否则MAT启动时会报错Failed to create the Java Virtual Machine
。
获得堆转储文件
MAT是一个静态堆分析工具,需要预先抓取Java堆转储文件(内存快照)。
可通过以下几种方式生成堆转储文件:
- 在JVM启动参数里增加
-XX:+HeapDumpOnOutOfMemoryError
参数,系统发生OOM时会自动在工作目录(user.dir)生成java_pid<pid>.hprof
转储文件。还可通过JVM参数-XX:HeapDumpPath=<path>
显式指定堆转储文件的存放路径。 - 如果不想等到发生OOM错误时才获得堆转储文件,可添加JVM参数
-XX:+HeapDumpOnCtrlBreak
,以便在控制台使用Ctrl+Break(Pause)键来按需获取堆转储文件。 - 若环境上Jmap工具可用,则可通过
jmap -dump:live,format=b,file=heap.bin <pid>
命令获得转储文件。
其中,pid为进程ID,live选项会在转储前强制触发一次full GC(以减小文件体积),file可指定产生文件的目录和名称。
类似地,VisualVM、Jconsole等JDK工具也可用来生成堆转储文件。 - MAT本身也可获取堆转储文件,即File -> Acquire Heap Dump菜单。
分析堆转储文件
本节亦结合代码示例介绍MAT常见的界面功能。示例代码如下:
package thread;
import java.util.*;
public class JavaHeapDump {
private static List<String> smallArray = new ArrayList<>();
private static List<byte[]> largeArray = new ArrayList<>();
public static String getPassword() {
char[] pw = {'A', 'd', 'm', 'i', 'n', '1', '2', '3'};
return new String(pw);
}
public static void makeHeapOom() {
for (int i = 0; i < 1000; i++) {
smallArray.add(getPassword()); //smallArray.add(getPassword().intern());
byte[] elems = new byte[1024 * 1024];
Arrays.fill(elems, (byte)101);
largeArray.add(elems);
//largeArray.add(new byte[1024 * 1024]);
}
}
public static void main(String[] args) {
makeHeapOom();
}
}
编译代码后以-Xms20m -Xmx20m
(限制堆空间以尽快OOM)等JVM参数运行,得到如下输出:
D:\xywang\target\classes>java -Xms20m -Xmx20m -Xmn2m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=E:\dump\heapDump.bin thread.JavaHeapDum
p
java.lang.OutOfMemoryError: Java heap space
Dumping heap to E:\dump\heapDump.bin ...
Heap dump file created [21458899 bytes in 0.805 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at thread.JavaHeapDump.makeHeapOom(JavaHeapDump.java:18)
at thread.JavaHeapDump.main(JavaHeapDump.java:26)
可知,很快就出现内存溢出(java.lang.OutOfMemoryError: Java heap space
),并在E盘下生成heapDump.bin转储文件。
此时启动MAT,选择菜单项File -> Open Heap Dump来加载待分析的堆转储文件。加载完文件后,在弹出的向导页面选择按照内存泄漏模式分析。
Leak Suspect Report是默认生成的可能存在潜在内存泄露的分析报告,在饼图中描述了各种问题占用内存的比例,饼图下方则是关于潜在问题的细节分析。
点击"Details"链接,可看到引起内存溢出可能的最大元凶确实为largeArray
!此处,"Shortest Paths To the Accumulation Point
"展示由于和哪个GC Root相连导致当前Retained Heap占用相当大的对象无法被回收。
概览页签提供了Heap Dump的概览,包括堆的饼图以及Actions/Reports/Step by Step等快速访问功能区。
其中,Histogram(堆直方图)提供按类分组的对象的内存占用统计列表,默认按照某个类的shallow heap从大到小排序。Dominator Tree(支配树)显示按照Object/Class保留内存大小排序的结果,可用于排查哪些对象导致其他对象无法被垃圾收集器回收。Top Consumers是Dominator Tree数据的图形统计,分别按照Object、Class,ClassLoader,Package等维度做的内存占用统计。Top Components列出占用堆空间较多的组件,并给出可以减少内存消耗的建议。
以最常用的Dominator Tree界面为例:
堆中有两个ArrayList,且其中一个占用了96.57%的内存。以下简要介绍图中主要字段的含义:
Shallow Heap:对象自身所占用的内存大小,不含其引用的对象所占的内存大小。数组对象的Shallow Heap是数组元素大小的总和,非数组对象的Shallow Heap是对象所有成员变量大小的总和。
Retained Heap:当前对象大小 + 当前对象可直接或间接引用到的对象的大小总和,即当前对象被GC后从Heap上总共能释放掉的内存。
incoming references:当前类被哪些类引用,或当前对象被哪些对象引用。
outgoing references:当前类的所有实例,或当前对象所引用的对象。
选中Dominator Tree中占用内存最大的对象,通过with incoming references
查看持有其引用的外部对象。
可见,占用大量内容的元凶正是largeArray
。对于集合对象,可右键选择Java Collections的子菜单做各种排序和查看。例如,图中选择Extract List Values
查看largeArray
的内容,结果如下所示:
窥探对象内存值
JavaHeapDump示例代码中有意使用到密码,真实业务中可通过OQL(Object Query Language)排查内存中是否存在此类敏感信息。
OQL是一种基于javascript表达式的语言,它将类当作表、该类的实例对象当作记录行、对象中的成员变量当作表中的字段,可以用类似SQL语句的方式查询Java堆中的对象。OQL语法结构如下:
select <JavaScript expression to select>
from [ instanceof ] <class name="name">
[ where <JavaScript boolean expression to filter> ]
更多OOL的语法,请在OOL页面上按F1键查看帮助信息。
在MAT工具栏中点击OQL按钮,打开OQL编辑器窗口,输入查询命令后点击红色感叹号按钮进行查询,结果如下:
注意,查询语句中"Admin123"后面的".*"相当于SQL通配符"%"。查询结果中赫然可见"Admin123"这样的明文密码!
通过Merge Shortest Path to GC Roots
查看这些密码对象到GC Roots是否可达:
若该对象为unreachable则说明密码不是常驻内存,可见图中的密码均常驻内存。
堆转储文件对比分析
实际业务场景中堆中内存对象可能非常多,定位内存泄露时,通常需要抓取和对比先后两个时刻的堆转储文件。MAT操作步骤如下:
- 加载第一个堆转储文件,并打开Histogram视图。
- 打开Window -> Navigation History视图,在histogram右键选择Add to Compare Basket。
- 加载第二个堆转储文件,也添加到Compare Basket中。
- 打开Window -> Compare Basket视图,点击Compare the Results(右上角的红色叹号)。
- 在Compared Tables里分析对比结果。
例如,图中#1是使用String.intern()存储密码后的内存信息,比#0创建的String对象要少(这由OQL结果也可证明)。
通过这种方式可快速定位到操作前后所持有的对象增量,从而进一步定位出导致内存泄露的具体元凶。
总结
本文简要介绍了Java线程与内存分析工具VisualVM和MAT的使用,进一步的学习可参考官网或工具帮助(例如MAT:Help -> Welcome -> Tutorials),并在实际工作中融会贯通。真是无话可说了……
【Java线程与内存分析工具】VisualVM与MAT简明教程的更多相关文章
- (转载)Java多线程的监控分析工具(VisualVM)
原文链接:http://blog.csdn.net/chendc201/article/details/22905511 在Java多线程程序运行时,多数情况下我们不知道到底发生了什么,只有出了错误的 ...
- Android内存分析工具DDMS heap + MAT 安装和使用
一 Java内存分析工具扫盲 如果像我一样一点都不了解,可以先进行内存分析工具扫盲 MAT介绍: Eclipse Memory Analyzer(MAT)一个功能丰富的 JAVA 堆转储 ...
- jvm堆内存模型原理分析及堆内存分析工具jhat和MAT的使用超详细教程
- Java内存分析工具MAT
MAT是一个强大的内存分析工具,可以快捷.有效地帮助我们找到内存泄露,减少内存消耗分析工具.内存中堆的使用情况是应用性能监测的重点,而对于堆的快照,可以dump出来进一步分析,总的来说,一般我们对于堆 ...
- Java内存分析工具
内存分析工具 IDEA插件(VisualVM Launcher) 执行main函数的时候,同时启动jvisualvm,实时查看资源消耗情况.如图效果: Eclipse Memory Analyzer ...
- Eclipse MAT内存分析工具(Memory Analyzer Tool)
MAT内存分析工具 MAT是Memory Analyzer的简称,它是一款功能强大的Java堆内存分析器.可以用于查找内存泄露以及查看内存消耗情况.MAT是基于Eclipse开发的,是一款免费的性能分 ...
- Java应用常用性能分析工具
Java应用常用性能分析工具 好的工具有能有效改善和提高工作效率或加速分析问题的进度,笔者将从事Java工作中常用的性能工具和大家分享下,如果感觉有用记得投一票哦,如果你有好的工具也可以分享给我 工具 ...
- 内存分析工具-MAT(Memory Analyzer Tool)
内存分析工具-MAT(Memory Analyzer Tool) 首先查看如下代码,main函数中有一个成员变量map,map里被循环放入对象Hanson,hanson持有姓名和age还有friend ...
- android--------Android内存分析工具的使用
内存分析(in-memory analytics)是我们编写速度快.效率高的代码必不可少的知识.如果自己编写的代码在内存的分配一无所知,我想这样的程序让你去优化,应该是无从下手的.那么内存分析是什么? ...
随机推荐
- android 启动流程 相关2 init进程 属性服务
Init属性服务 系统属性服务 属性梳理 来源和读取时机 来源:内核参数 ro.kernel.* 代表有qemu内核参数才会设置(在虚拟机中) ro.boot.* 1.内核设备树相关的设备 ...
- nginx基础(3)
目录 HTTP首部 1.通用首部 2.请求首部 2.1 必有首部 2.2 条件请求首部 2.3 安全相关首部 3.响应首部 3.1 必有首部 3.2 协商首部 3.3 安全相关首部 4.实体首部 4. ...
- curl 带 body
curl -H "Content-Type:plain/text" -X POST -d '<?xml version="1.0" encoding=&q ...
- ETCD:词汇表
原文地址:词汇表 本文档定义了etcd文档,命令行和源代码中使用的各种术语. Alarm 每当集群需要操作员干预以保持可靠性时,etcd服务器都会发出警报. Authentication 身份验证管理 ...
- RocketMQ(二):producer客户端实践
MQ解耦了生产者和消费者,前提是有一个稳定强大的消息服务,我们只管与之通信即可. 所以,和MqServer通信是什么样的?难否? 0. 发送端demo /** * This class demonst ...
- Web前端基础(18):jQuery基础(五)
1. 事件 前面我们介绍完js的事件流的概念之后,相信大家对事件流也有所了解了.那么接下来我们看一下jquery的事件操作. 在说jquery的每个事件之前,我们先来看一下事件对象 . 1.1 事件对 ...
- js写个小时钟
原生js写个小时钟 一.代码 今天美化博客园自学的哈,分享一下 <!--标题变成时钟--> <div id="Header1_HeaderTitle">&l ...
- 关于scrapy中如何区分是接着发起请求还是开始保存文件
一.区分 根据yield迭代器生成的对象是request对象还是item对象 二.item 1.配置tem对象 在items.py文件中设置类 class MyscrapyItem(scrapy.It ...
- GO-&获取地址与*解引用
&变量 获取变量在内存空间的地址 *变量地址 获取变量的值 一.普通数据 package main import "fmt" func main(){ b :=1111 c ...
- View和ViewGroup
1.继承关系 2.组合关系 3.View 的绘制流程 3.1.创建R.attrs.styleable,申明需要用到的属性值,在使用时可以根据属性进行定义 3.2.extends View ,依次 ...