前言
在日常的开发工作中,遇到生产环境报OOM的问题时,你首先会想到采用哪些方式并使用什么样的工具对OOM问题进行分析,定位和解决呢?
实际现场环境无非有这么两种,第一种,如果项目所在的生产服务器不允许导出日志或者数据之类的,那就只好依靠线上操作相关的JVM命令进行分析排查;第二种,如果条件允许,则可以外接JVM相关的排查工具,直接连接生产的项目进程,进行实时分析
第二种方式下,通常可利用JDK自带的一些工具,比如jconsole,jmap等工具连接进程,但更多的场景是,问题已经发生了,也就是犯罪现场出现了,又不允许随便破坏环境,更通常的做法是,导出日志,利用第三方工具进行排查
本篇将针对这一点,简单介绍下一款强大的dump日志分析工具,Eclipse Memory Analyzer,也称作MAT
MAT是什么
MAT工具是一款强大的Java堆内存分析工具,可用于查找内存泄露以及查看内存消耗情况,便于开发或运维人员快速定位内存溢出或内存泄露问题
MAT基于eclipse开发,可以单独使用,也可以以插件形式嵌入到开发工具中,是一款免费的性能分析工具,使用起来很方便,官网下载地址:https://projects.eclipse.org/projects/tools.mat
MAT下载后,无需安装,解压之后,双击下面的exe文件即可打开,
打开之后,是一个非常简洁干净的界面
以上是使用MAT工具分析dump文件之前的准备工作,下面来具体介绍下开发中的常用功能
1、MAT导入dump文件
使用下面这段程序,通过在启动参数中配置JVM的指令,这里主要是指定该类的运行时内存,
代码语言:javascript复制public class OomTest1 {
public static void main(String[] args) {
ArrayList list = new ArrayList();
while(true){
try{
Thread.sleep(10);
}catch (Exception e){
e.printStackTrace();
}
list.add(new Picture(new Random().nextInt(100 * 50)));
}
}
}
class Picture {
private byte[] pixls;
public Picture(int length) {
this.pixls = new byte[length];
}
}
然后配置类的运行参数
运行上面的程序,然后,在本地的CMD窗口中,通过导出dump文件的命令导出程序运行时的hprof文件
代码语言:javascript复制jmap -dump:format=b,file=D:logsdumpsoomtest5.hprof 17148
使用: File ——> Open File ,导入刚刚生成的oomtest5这个dump文件,
导入成功之后首先来到上面这个界面,我们先不着急如何分析问题,先快速了解下面板上各个组件的用法,方便后面的学习
2、MAT基本功能说明
OverView
OverView功能是导入了dump文件之后,对可能出现的问题做了一个整体性的分析概述,首先呈现在眼前的是以一个形状图的方式,快速展现了该文件中dump文件大小,以及类、对象和类加载器的数量
当光标移动到蓝色区域时候,会呈现该日志文件的主要线程,类加载器,深堆,浅堆信息
分析报告,点击此处,可以查看dump文件的各类参数报告
以Heap Dump Overview为例,以表格的形式呈现出当前的dump文件中,已创建的对象个数,class的格式,类加载器的个数等,反应的是一个概览信息
在该菜单下,另一个比较好用的就是这个Leak Suspects功能,MAT分析工具会根据你导入的dump文件,快速生成一份怀疑报告,将可能出现内存泄露的点展示出来,便于开发或运维人员进行问题定位
Histogram 列举类的实例信息
当点击进去之后,为我们呈现出dump文件中,已经创建的主要的对象信息,默认按照对象的个数进行排序,而这个排序,多少也反映出在当前的dump文件中,那些排在前面的数量最多的对象可能是我们分析问题的关键入口
顶部的可以支持对象名称的模糊匹配,比如搜索Picture,可以快速找到我们需要的对象名称
在该菜单下,还提供了分组功能,方便快速定位类的信息
如果还能根据字段的名称进行排序,也是快速定位到需要查找到的对象的一种方式
在该栏,另外有2个参数值得注意,就是列举出来的Shallow Heap的数量和Retained Heap的数量,即浅堆和深堆的数量,通常来说,对某个具体的对象来说,深堆的数量是超过浅堆的数量的,这个和JVM对象的底层原理有一定关系,
由于这里是分析OOM时候的一个关键点,这里就对浅堆和深堆做一点补充解释
浅堆:指的是一个对象所消耗的内存(不包括内部引用的对象大小)
我们知道,对象在创建出来时,需要JVM在堆内存中开辟一定的空间存储实际对象,然后访问时候,通过指针找到对象,但是在真实的场景中,一个对象可能引用(持有)了其他对象,或者是该对象被其他对象引用,或者通过更深的层级引用等,这就造成了,当这个对象的生命周期的结束之后,垃圾回收期要回收该对象时,不得不去找到这个对象的完整引用关系,这个涉及到底层的GC就不进一步展开了
要说明的是,在JVM中,存在一个叫做保留集的概念,对某个特定的对象来说,该对象的保留集指的是当该对象被垃圾回收后,可以释放的所有对象集合(包括该对象自身),即对象的保留集可认为是只能通过该对象被直接或者间接访问到的所有对象集合,通俗来说,就是该对象所持有的所有对象集合
深堆:某对象的保留集中所有对象的浅堆之和
这就解释了为什么深堆对象的个数要比浅堆多了
分析这个的意义在于,实际开发过程中,可能因为编码的习惯不好,导致某些类中,对象的引用链条特别长,层级也很深,最后甚至连自己都不一定能搞清楚那些对象是实际在使用的,假如正好有那么一些对象实际上并没有使用,但是在某些循环中大量创建,尤其是大对象,在这种情况下,很容易造成GC过程的失败最终引发OOM,从MAT中的这个展现的数据来看,对于快速定位那些数量较多的对象还是很有帮助的
最后再对该菜单功能中的2个实用的点做下补充,如下所示,右键某个对象的List objects这一栏,显示出两个功能标签,outgoing references 和 incoming references
outgoing references :表示的是当前对象引用的外部对象集合
incoming references: 引用当前对象的外部对象的集合
上面的2个指标可以分析当前对象的对象引用链
排除该对象的弱引用,虚引用,通过点击这里,只筛选出当前对象被那些对象进行了强引用,在OOM的分析中,可以对此判断是哪些对象对于该对象的引用造成了对象无法回收的情况,也是定位问题的一个很好的点
那么点击完毕之后,可以发现Picture对象被一个集合给持有着,如果该集合长期无法被垃圾回收的话,最后一定会造成OOM
Thread Overview (线程分析概览)
点击该菜单,展示出当前dump文件中,所有的线程信息,如上图所示,展示出了所有的线程,主线程,以及各个线程中的浅堆和深堆占用的大小,类加载器,是否守护线程等信息
以main线程为例,列举出了里面的成员变量的信息,比如在当前的main线程中,有2个成员变量,其中一个集合里面包括了很多Picture对象
从开始的leak suspects中,也给出了初步的判断,报告说的是在main线程中,有一个对象的大小占据了堆内存的大小的93%,说明在当前这一时刻,某个对象有可能成为OOM的原因
Domainator Tree (支配树)
MAT工具提供了一个称为支配树的对象图,支配树体现了对象实例之间的支配关系,在对象引用关系图中,所有指向对象B的路径都经过对象A,则认为对象A支配对象B,这个涉及到算法中的图论,不做过多展开
理解支配树的目的是,在我们分析dump文件时,可以通过支配树,清楚的知道某个对象引用的对象情况(对象支配的其他对象),了解这一层,在定位当前对象是否能进行GC以及为何不能进行GC时,就有了前提,来看下面这段代码
代码语言:javascript复制/**
* -XX: HeapDumpBeforeFullGC -XX:HeapDumpPath=D:logsdumpsoomtest2.hprof
* -Xms100m -Xmx100m -XX:SurvivorRatio=8
*/
public class OomTest2 {
static List webpages = new ArrayList();
public static void createWebpage(){
for(int i=0;i history = new ArrayList();
public Student(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List getHistory() {
return history;
}
public void setHistory(List history) {
this.history = history;
}
public void visit(WebPage webPage){
if(webPage != null){
history.add(webPage);
}
}
}
class WebPage{
private String url;
private String content;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
将顶部注释中的参数配置在启动参数中,让程序运行完毕之后,生成一个dump文件
然后使用MAT打开该文件,从Thread Overview中,可以清楚的看到,目前存在的3个对象,Tom,Jerry和Lily
以Tom为例,线程Overview中,Tom对象中包含了24个元素,这个数量包括2部分,一部分是能被3除的那些对象,而另一部分则是还能被5和7除的那些对象,
我们不妨再看下Jerry中的集合里面的对象个数,数量显然要少一些
有兴趣的同学可以再看一下Lily的,数量就更少了,这个说明的一个问题就是,3个对象的集合中,有一些对象是被共同引用着
而对象支配树中,可以将各对象持有的真实对象,以及引用对象都列举出来,就能很方便的查看各自对象的真实引用情况,从而定位分析那些有可能存在的被多个对象引用但实际并没有使用的对象,造成的无法被垃圾回收的情况
比如从支配树中,通过Thread定位到Tom,这时候发现集合中的对象个数只有23个,比上面少了2个,这个就是在支配树中,该对象真实支配的对象个数,多出来的2个被Jerry或Lily对象引用了
当Tom这个对象在进行垃圾回收的时候,实际上能被回收的只有23个,另外2个由于被其他对象引用,而无法被回收
现实的情况是,如果类似的情况,很多个对象都共同持有着对一部分对象的引用,在高峰期时,一旦JVM的内存比较吃紧的时候,可能会成为OOM发生的因素,需要格外注意
限于篇幅原因,本篇到这里就要结束了,本篇主要从使用上介绍了MAT这款工具的基本使用,以及分析OOM时的几个技巧,希望对看到的同学有用,本篇到此结束,最后感谢观看!
本文为从大数据到人工智能博主「bajiebajie2333」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://cloud.tencent.com/developer/article/2109322