java中如何使用mat分析java堆
java中如何使用mat分析java堆,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。
MAT
是 memory analyzer
的简称,它是一款功能强大的java堆内存分析器,可以用来查找内存泄露,以及查看内存消耗的情况,可以在MAT官网进行下载。
1. 初识
1.1 导出堆应用快照
jmap
、jconsole
、jvisualvm
等工具可以导出java应用程序的堆快照文件,MAT
也有该功能,如图所示:
点击"Accquire Heap Dump"菜单后,会弹出当前java应用程序列表,选择要分析的应用程序即可,如图所示:
1.2 打开堆快照文件
除了直接在MAT
中导出应用程序的堆快照外,也可以通过"Open Heap Dump"来打开一个已有的堆快照文件。
如图所示,显示了正常打开堆快照文件后MAT
的界面:
在右侧界面中,显示了堆快照文件的大小、类、实例和ClassLoader的总数。
在饼图中,显示了当前堆快照中最大的对象。
将鼠标悬停在饼图中,可以在左侧的Inspector界面中,查看该对象的相应信息。
在饼图中,单击某对象,可以对选中的对象进行更多操作。
1.3 查看所有类的内存使用情况
如图所示,在工具栏上单击柱状图,可以显示系统中所有类的内存使用情况:
1.4 查看java线程
MAT
也可以查看java线程,如图所示:
当然,这里查看java层面的应用线程,虚拟机的系统线程是无法显示的。通过线程的堆栈,还可以查看局部变量的信息。如下图所示,带有
标记的为当前帧栈的局部变量,这部分信息可能存在缺失。
除此之外,MAT
也可以查看详细的线程堆栈信息:
由此打开thread_detail
标签页,可以清晰地看到线程堆栈信息:
1.4 查看对象的引用
MAT
的另外一个常用功能,是在各个对象的引用列表中交叉查看。对于一个给定对象,通过MAT
可以找到引用当前对象的对象,即入引用(Incomming References
),以及当前对象引用的对象,即出引用(Outgoing References
),如图所示:
2. 浅堆与深堆
浅堆(Shallow Heap)和深堆(Retained Heap)是两个非常重要的概念,它们分别表示一个对象结构所占用的内存大小和一个对象被GC回收后,可以真实释放的内存大小。
浅堆是指一个对象所消耗的内存。在32位系统中,一个对象引用会占据4字节,一个int类型变量会占据4字节,一个long类型变量会占8字节,每个对象头需要占8字节。根据堆快照格式不同,对象的大小可能会向8字节对齐。
根据堆快照格式不同,对象的大小可能会向8字节进行对齐。以String对象为例,如下图所示,显示了String对象的几个属性。
String
value:char[]
offset:int
count:int
hash:int
3个int值共占12字节,对象引用占用4字节,对象头8字节,合计24字节。浅堆的大小只与对象的结构有关,与对象的实际内容无关。也就是说,无论字符串的长度有多少,内容是什么,浅堆的大小始终是24字节。
深堆(Retained Heap)的概念略微复杂。要理解深堆,首先需要了解保留集(Retained Set)。对象A的保留集指当对象A被垃圾回收后,可以被释放的所有的对象集合(包括对象A本身),即对象A的保留集可以被认为是只能通过对象A被直接或间接访问到的所有对象的集合。通俗地说,就是指仅被对象A所持有的对象的集合。深堆是指对象的保留集中所有的对象的浅堆大小之和。
注:浅堆指对象本身占用的内存,不包括其内部引用对象的大小。一个对象的深堆指只能通过该对象访问到的(直接或间接)所有对象的浅堆之和,即对象被回收后,可以释放的真实空间。
另外一个常用的概念是对象的实际大小。这里,对象的实际大小定义为一个对象所能触及的所有对象的浅堆大小之和,也就是通常意义上我们说的对象大小。与深堆相比,似乎这个在日常开发中更为直观和被人接受,但实际上,这个概念和垃圾回收无关。
如图所示,显示了一个简单的对象引用关系图,对象A引用了C和D,对象B引用了C和E。那么对象A的浅堆大小只是A本身,不含C和D,而A的实际大小为A、C、D三者之和。而A的深堆大小为A与D之和,由于对象C还可以通过对象B访问到,因此不在对象A的深堆范围内。
3 支配树
MAT
提供了一个称为支配树(Dominator Tree
)的对象图。支配树体现了对象实例间的支配关系。在对象引用图中,所有指向对象B的路径都经过对象A,则认为对象A支配对象B.如果对象A是离对象B最近的一个支配对象,则认为对象A为对象B的直接支配者。支配树是基于对象间的引用图所建立的,它有以下基本性质:
对象A的子树(所有被对象A支配的对象集合)表示对象A的保留集(Reatined Set),即深堆。
如果对象A支配对象B,那么对象A的直接支配者也支配对象B.
支配树的边与对象引用图的边不直接对应。
如图所示,左图表示对象引用图,右图表示左图所对应的支配树。对象A和B由根对象直接支配,由于在到对象C的路径中,可以经过A,也可以经过B,因此对象C的直接支配者也是根对象。对象F与对象D相互引用,因为到对象F的所有路径必然经过对象D,因此,对象D是对象F的直接支配者。而到对象D的所有路径中,必然经过对象C,即使是从对象F到对象D的引用,从根节点出发,也是经过对象C的,所以,对象D的直接支配者为对象C。
在MAT中,单击工具栏上的对象支配树按钮,可以打开对象支配树视图:
注:在对象支配树中,某个对象的子树表示在该对象被回收后也将被回收的对象的集合。
4. MAT
堆分析案例解析
首先准备案例代码:
package jvm.chapter07;import java.util.List;import java.util.Vector;/** * {这里添加描述} * * @author chengyan * @date 2019-11-15 11:01 下午 */public class Demo04 { private static ListwebPages = new Vector<>(); public static void createWebPages() { for(int i = 0; i < 100; i++) { WebPage webPage = new WebPage(); webPage.setUrl("http:www." + Integer.toString(i) + ".com"); webPage.setContent(Integer.toString(i)); webPages.add(webPage); } } public static void main(String[] args) { createWebPages(); Student student3 = new Student(3, "billy"); Student student5 = new Student(5, "alice"); Student student7 = new Student(7, "taotao"); for(int i = 0; i < webPages.size(); i++) { if(i % student3.getId() == 0) { student3.visit(webPages.get(i)); } if(i % student5.getId() == 0) { student5.visit(webPages.get(i)); } if(i % student7.getId() == 0) { student7.visit(webPages.get(i)); } } webPages.clear(); System.gc(); }}class Student { private int id; private String name; private List history = new Vector<>(); public void visit(WebPage webPage) { history.add(webPage); } 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; }}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; }}
可以看到,在 Demo04.java
类中,首先创建了100个网址,为了阅读方便,这里的网址均以数字作为域名,分别为0~99.之后,程序创建了3名学生:billy、alice和taotao.他们分别浏览了能被3、5、7整除的网址。在程序运行后,3名学生的history中应该保存他们各自访问过的网址。现在,希望在程序退出前,得到系统的堆信息并加以分析,查看每个学生实际访问的网址。
使用如下参数运行程序:
-XX:+HeapDumpBeforeFullGC -XX:HeapDumpPath=./stu.hprof
4.1 使用MAT
打开堆文件
使用MAT
打开产生的stu.hprof文件,如图所示:
右侧界面中,显示了堆快照文件的大小、类、实例和ClassLoader的总数。在右侧的饼图中,显示了当前堆快照中最大的对象。将鼠标悬停在饼图中,可以在左侧的Inspector界面中,查看该对象的相应信息。在饼图中单击某对象,可以对选中的对象进行更多的操作。
4.2 查看线程
在线程视图中可以通过main线程
找到3名学生的引用,如图所示,可以清晰看到三个对象的学生名。除了对象名称,MAT
还给出了浅堆大小和深堆大小。可以看到,所有的Student类的浅堆统一为24字节,和他们持有的内容无关。而深堆大小各不相同,这和每名学生访问的网址有关。
当然,这里查看Java层面的应用线程,对于虚拟机的系统线程是无法显示的。通过线程的堆栈,还可以查看局部变量的信息。带有
标记的,就为当前帧栈的局部变量,这部分信息可能存在缺失。
4.3 查看对象的出引用
为了获得taotao同学访问过的网址,可以在taotao的记录中通过"出引用"(Outgoing References
)查找,可以找到由taotao可以触及的对象,也就是他们访问过的网址,如图所示:
访问过的网址如下:
可以看到堆中完整显示了所有taobao同学的history中的网址。
4.4 查看入引用
如果现在希望查看哪些同学访问了"http://www.0.com",则可以在对应的WebPage对象中通过"入引用"(Incoming References
)查找:
显然,这个网址被3名学生都访问过了。
4.5 查看支配树
如图所示,显示了Main Thread
的对象支配树。被Student
对象直接支配的对象,均放在该对象下的history
中,即当Student
对象被回收时,也会一并回收该对象所支配的所有对象。
另外,还有一部分WebPage
的父节点是Thread
,这表明这部分的WebPage
同时被多个Student
对象持有,例如,84能同时被3和7整除,这表明该对象会同时被billy
与taobao
持有,当billy
或taobao
其中之一并回收时,该对象并不会回收,因此不会单独显示在billy
或taobao
的history
中。
4.6 查看所有类的内存使用情况
在工具栏上单击柱状图,可以显示系统中所有类的内存使用情况。图为系统内所有类的统计信息,包含类的实例数量和占用的空间。
为了方便查看,柱状图还提供了根据Class Loader和包对类进行排序。如下图是按照包排序的柱状图输出。
看完上述内容,你们掌握java中如何使用mat分析java堆的方法了吗?如果还想学到更多技能或想了解更多相关内容,欢迎关注行业资讯频道,感谢各位的阅读!