1、简介
MAT(Memory Analyzer Tool)工具是一款功能强大的Java堆内存分析器。可以用于查找内存泄露以及查看内存消耗情况。MAT是基于Eclipse开发的,不仅可以单独使用,还可以作为插件的形式嵌入在Eclipse中使用。是一款免费的性能分析工具,使用起来非常方便。
MAT可以分析heap dump文件。在进行内存分析时,只要获得了反映当前设备内存映像的hprof文件,通过MAT打开就可以直观地看到当前的内存信息。一般来说,这些内存信息包含:
- 所有的对象信息,包括对象实例、成员变量、存储于栈中的基本类型值和存储于堆中的其他对象的引用值。
- 所有的类信息,包括classloader、类名称、父类、静态变量等
- GCRoot到所有的这些对象的引用路径
- 线程信息,包括线程的调用栈及此线程的线程局部变量。(TLS)
MAT不是一个万能工具,它并不能处理所有类型的对存储文件。但是比较主流的厂家和格式。例如Sun,HP,SAP所采用的HPROF二进制堆存储文件,以及IBM的PHD堆存储文件等都能被很好的解析。
最吸引人的还是能够快速为开发人员生成内存泄露报表,方便定位问题和分析问题。虽然MAT有如此强大的功能,但是内存分析也没有简单到一键完成的程度,很多内存问题还是需要我们从MAT展现给我们的信息当中通过经验和直觉来判断才能发现。
官方地址:https://www.eclipse.org/mat/downloads.php
点击上面的exe文件:
2、获取dump文件方式
2.1、下面我们先使用jmap生成dump文件
测试程序如下:
package com.kgf.kgfjavalearning2021.jvm.visualvm; import java.util.ArrayList; import java.util.List; import java.util.Random; /*** * -Xms600m -Xmx600m -XX:SurvivorRatio=8 */ public class VisualVMTest { public static void main(String[] args) { List<Object> list = new ArrayList<>(); while (true){ try { Thread.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } list.add(new Picture(new Random().nextInt(100*50))); } } } class Picture{ private byte[] pixels; public Picture(int length) { this.pixels = new byte[length]; } }
在idea中添加vm环境变量:
启动程序使用jmap命令:
使用eclipse MAT打开:
2.2、分析堆dump文件
histogram:
展示了各个类的实例数目以及这些实例的Shallow heap或 者Retained heap的总和
thread overview:查看系统中的Java线程 查看局部变量的信息
获得对象互相引用的关系:with outgoing references
with incoming references :
3、浅堆与深堆
3.1、浅堆(shallow heap)
对象头代表根据类创建的对象的对象头,还有对象的大小不是可 能向8字节对齐,而是就向8字节对齐.
3.2、保留集(retained heap),也就是所谓的深堆
注意: 当前深堆大小 = 当前对象的浅堆大小 + 对象中所包含对象的深堆大小
3.3、补充:对象实际大小
4、练习
5、案例分析:StudentTrace
代码如下:
package com.kgf.kgfjavalearning2021.jvm.mat; import java.util.ArrayList; import java.util.List; /** * 有一个学生浏览网页的记录程序,它将记录 每个学生访问过的网站地址。 * 它由三个部分组成:Student、WebPage和StudentTrace三个类 * * -XX:+HeapDumpBeforeFullGC -XX:HeapDumpPath=F:\mat_log\student.hprof * */ public class StudentTrace { static List<WebPage> webpages = new ArrayList<WebPage>(); public static void createWebPages() { for (int i = 0; i < 100; i++) { WebPage wp = new WebPage(); wp.setUrl("http://www." + Integer.toString(i) + ".com"); wp.setContent(Integer.toString(i)); webpages.add(wp); } } public static void main(String[] args) { createWebPages();//创建了100个网页 //创建3个学生对象 Student st3 = new Student(3, "Tom"); Student st5 = new Student(5, "Jerry"); Student st7 = new Student(7, "Lily"); for (int i = 0; i < webpages.size(); i++) { if (i % st3.getId() == 0) st3.visit(webpages.get(i)); if (i % st5.getId() == 0) st5.visit(webpages.get(i)); if (i % st7.getId() == 0) st7.visit(webpages.get(i)); } webpages.clear(); System.gc(); } } class Student { private int id; private String name; private List<WebPage> history = new ArrayList<>(); public Student(int id, String name) { super(); 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<WebPage> getHistory() { return history; } public void setHistory(List<WebPage> history) { this.history = history; } public void visit(WebPage wp) { if (wp != null) { history.add(wp); } } } 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; } }
在idea中配置环境变量:
启动程序生成dump文件,使用MAT打开:
结论:
elementData数组的浅堆是80个字节,而elementData数组中的所有WebPage对象的深堆之和是1208个字节,所以加在一起就是elementData数组的深堆之和,也就是1288个字节
解释:
我说“elementData数组的浅堆是80个字节”,其中15个对象一共是60个字节,对象头8个字节,数组对象本身4个字节,这些的和是72个字节,然后总和要是8的倍数,“elementData数组的浅堆是80个字节”我说“WebPage对象的深堆之和是1208个字节”,一共有15个对象,
其中0、21、42、63、84、35、70不仅仅是7的倍数,还是3或者5的倍数,所以这几个数值对应的i不能计算在深堆之内,这15个对象中大多数的深堆是152个字节,但是i是0和7的那两个深堆是144个字节,所以(13*152+144*2)-(6*152+144)=1208,所以这也印证了我上面的话,即“WebPage对象的深堆之和是1208个字节”因此“elementData数组的浅堆80个字节”加上“WebPage对象的深堆之和1208个字节”,正好是1288个字节,说明“elementData数组的浅堆1288个字节”