前言

本文将简要介绍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堆转储文件(内存快照)。

可通过以下几种方式生成堆转储文件:

  1. 在JVM启动参数里增加-XX:+HeapDumpOnOutOfMemoryError参数,系统发生OOM时会自动在工作目录(user.dir)生成java_pid<pid>.hprof转储文件。还可通过JVM参数-XX:HeapDumpPath=<path>显式指定堆转储文件的存放路径。
  2. 如果不想等到发生OOM错误时才获得堆转储文件,可添加JVM参数-XX:+HeapDumpOnCtrlBreak,以便在控制台使用Ctrl+Break(Pause)键来按需获取堆转储文件。
  3. 若环境上Jmap工具可用,则可通过jmap -dump:live,format=b,file=heap.bin <pid>命令获得转储文件。

    其中,pid为进程ID,live选项会在转储前强制触发一次full GC(以减小文件体积),file可指定产生文件的目录和名称。

    类似地,VisualVM、Jconsole等JDK工具也可用来生成堆转储文件。
  4. 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操作步骤如下:

  1. 加载第一个堆转储文件,并打开Histogram视图。
  2. 打开Window -> Navigation History视图,在histogram右键选择Add to Compare Basket。

  3. 加载第二个堆转储文件,也添加到Compare Basket中。
  4. 打开Window -> Compare Basket视图,点击Compare the Results(右上角的红色叹号)。

  5. 在Compared Tables里分析对比结果。



    例如,图中#1是使用String.intern()存储密码后的内存信息,比#0创建的String对象要少(这由OQL结果也可证明)。

通过这种方式可快速定位到操作前后所持有的对象增量,从而进一步定位出导致内存泄露的具体元凶。

总结

本文简要介绍了Java线程与内存分析工具VisualVM和MAT的使用,进一步的学习可参考官网或工具帮助(例如MAT:Help -> Welcome -> Tutorials),并在实际工作中融会贯通。真是无话可说了……

【Java线程与内存分析工具】VisualVM与MAT简明教程的更多相关文章

  1. (转载)Java多线程的监控分析工具(VisualVM)

    原文链接:http://blog.csdn.net/chendc201/article/details/22905511 在Java多线程程序运行时,多数情况下我们不知道到底发生了什么,只有出了错误的 ...

  2. Android内存分析工具DDMS heap + MAT 安装和使用

    一  Java内存分析工具扫盲 如果像我一样一点都不了解,可以先进行内存分析工具扫盲   MAT介绍:     Eclipse Memory Analyzer(MAT)一个功能丰富的 JAVA 堆转储 ...

  3. jvm堆内存模型原理分析及堆内存分析工具jhat和MAT的使用超详细教程

  4. Java内存分析工具MAT

    MAT是一个强大的内存分析工具,可以快捷.有效地帮助我们找到内存泄露,减少内存消耗分析工具.内存中堆的使用情况是应用性能监测的重点,而对于堆的快照,可以dump出来进一步分析,总的来说,一般我们对于堆 ...

  5. Java内存分析工具

    内存分析工具 IDEA插件(VisualVM Launcher) 执行main函数的时候,同时启动jvisualvm,实时查看资源消耗情况.如图效果: Eclipse Memory Analyzer ...

  6. Eclipse MAT内存分析工具(Memory Analyzer Tool)

    MAT内存分析工具 MAT是Memory Analyzer的简称,它是一款功能强大的Java堆内存分析器.可以用于查找内存泄露以及查看内存消耗情况.MAT是基于Eclipse开发的,是一款免费的性能分 ...

  7. Java应用常用性能分析工具

    Java应用常用性能分析工具 好的工具有能有效改善和提高工作效率或加速分析问题的进度,笔者将从事Java工作中常用的性能工具和大家分享下,如果感觉有用记得投一票哦,如果你有好的工具也可以分享给我 工具 ...

  8. 内存分析工具-MAT(Memory Analyzer Tool)

    内存分析工具-MAT(Memory Analyzer Tool) 首先查看如下代码,main函数中有一个成员变量map,map里被循环放入对象Hanson,hanson持有姓名和age还有friend ...

  9. android--------Android内存分析工具的使用

    内存分析(in-memory analytics)是我们编写速度快.效率高的代码必不可少的知识.如果自己编写的代码在内存的分配一无所知,我想这样的程序让你去优化,应该是无从下手的.那么内存分析是什么? ...

随机推荐

  1. openstack_dashboard无法获取nova

    问题描述: 今天打开openstack的dashboard准备创建实例,结果计算节点每一项展开都无法获取nova 之前已经把nova搞好了并没有什么问题,怎么突然就服务也起不了了 查看了一下nova服 ...

  2. react官方脚手架添加less配置

    装两个包 npm install --save less less-loader 在node-modules/react-scripts/config/webpack.config.js中 在大概58 ...

  3. 使用matplotlib.pyplot中scatter()绘制散点图

    1.二维散点图 二维散点图的函数原型: matplotlib.pyplot.scatter(x, y, s=None, c=None, marker=None, cmap=None, norm=Non ...

  4. Linux系统:Centos7下搭建ClickHouse列式存储数据库

    本文源码:GitHub·点这里 || GitEE·点这里 一.ClickHouse简介 1.基础简介 Yandex开源的数据分析的数据库,名字叫做ClickHouse,适合流式或批次入库的时序数据.C ...

  5. ubuntu16搭建文件服务器

    这篇记录,如何在ubuntu16 安装 FastDFS 文件服务器,详细步骤 环境依赖 apt-get install make apt-get install unzip apt-get insta ...

  6. js格式化JSON数据

    前言: 最近做的项目中遇到个需要在前端页面中将某个设备需要的数据格式展示出来,方便用户配置.一开始单纯的将数据格式写入到pre标签中, 但是通过pre标签写入的数据格式在代码可视化上不是很优雅.由于上 ...

  7. 学习强国docker文件用法

    学习强国docker用法 docker文件地址   https://github.com/fuck-xuexiqiangguo/docker 构建  docker  docker build -t D ...

  8. ASP.NET Core 2.2 WebApi 系列【二】使用EF CodeFirst创建数据库

    Code First模式 Code First是指"代码优先"或"代码先行". Code First模式将会基于编写的类和配置,自动创建模型和数据库. 一.准备 ...

  9. Add an Item to the New Action 在新建按钮中增加一个条目

    In this lesson, you will learn how to add an item to the New Action (NewObjectViewController.NewObje ...

  10. 高强度学习训练第十四天总结:HashMap

    HashMap 简介 HashMap 主要用来存放键值对,它基于哈希表的Map接口实现,是常用的Java集合之一. JDK1.8 之前 HashMap 由 数组+链表 组成的,数组是 HashMap ...