1. MAT(Memory Analyzer Tool)的介绍
官方介绍
The Eclipse Memory Analyzer is a fast and feature-rich Java heap analyzer that helps you find memory leaks and reduce memory consumption. Use the Memory Analyzer to analyze productive heap dumps with hundreds of millions of objects, quickly calculate the retained sizes of objects, see who is preventing the Garbage Collector from collecting objects, run a report to automatically extract leak suspects.
简要说明:MAT用来分析jvm的内存信息,内存信息包含两方面
- 所有的对象信息:包括对象实例、成员变量、存储于栈中的基本类型值和存储于堆中的其他对象的引用值
- 所有的类信息: 包括classloader、类名称、父类、静态变量 GCRoot到所有的这些对象的引用路径线程信息,包括线程的调用栈及此线程的线程局部变量(TLS)
2. MAT安装:略
3. MAT使用场景
- 内存溢出,JVM堆区或方法区放不下存活及待申请的对象。如:高峰期系统出现 OOM(Out of Memory)异常,需定位内存瓶颈点来指导优化。
- 内存泄漏,不会再使用的对象无法被垃圾回收器回收。如:系统运行一段时间后出现 Full GC,甚至周期性 OOM 后需人工重启解决。
- 内存占用高。如:系统频繁 GC ,需定位影响服务实时性、稳定性、吞吐能力的原因。
4. MAT 的工作原理以及核心功能
对 dump 文件建立多种索引,并基于索引来实现内存分布、对象间依赖、对象状态、条件检索这四大核心功能,并通过可视化展现辅助 Developer 精细化了解 JVM 堆内存全貌。
5. MAT 核心产品能力概括如下:
5.1 内存分布
- 全局概览信息:堆内存大小、对象个数、类的个数、类加载器的个数、GC root 个数、线程概况等全局统计信息。
- Histogram:罗列每个类实例的内存占比,包括自身内存占用量(Shallow Heap)及支配对象的内存占用量(Retain Heap),支持按 package、class loader、super class、class 聚类统计,最常用的功能之一。
- Dominator tree:按对象的 Retain Heap 排序,也支持按多个维度聚类统计,最常用的功能之一。
- Leak Suspects:直击引用链条上占用内存较多的可疑对象,可解决一些基础问题,但复杂的问题往往帮助有限。
- Top Consumers:展现哪些类、哪些 class loader、哪些 package 占用最高比例的内存。
5.2 对象间依赖
- References:提供对象的外部引用关系、被引用关系。通过任一对象的直接引用及间接引用详情(主要是属性值及内存占用),进而提供完善的依赖链路详情。
- Dominator tree:支持按对象的 Retain Heap 排序,并提供详细的支配关系,结合 references 可以实现大对象快速关联分析;
- Thread overview:展现转储 dump 文件时线程栈帧等详细状态,也提供各线程的 Retain Heap 等关联内存信息。
- Path To GC Roots:提供任一对象到 GC Root 的链路详情,帮助了解不能被 GC 回收的原因。
5.3 对象状态
- 最核心的是通过 inspector 面板提供对象的属性信息、类继承关系信息等数据,协助分析内存占用高与业务逻辑的关系。
- 集合状态的检测,如:通过 ArrayList 或数组的填充率定位空集合空数组造成的内存浪费、通过 HashMap 冲突率判定 hash 策略是否合理等。
5.4 条件检索
- OQL:提供一种类似于SQL的对象(类)级别统一结构化查询语言。(举例,查找 size=0 且未使用过的 ArrayList:select * from java.util.ArrayList where size=0 and modCount=0;查找所有的String的length属性的:select s.length from instanceof String s)
- 内存分布及对象间依赖的众多功能,均支持按字符串检索、按正则检索等操作。
- 按虚拟内存地址寻址,根据对象的十六进制地址查找对象。
6. 模拟OOM,dump堆文件
public class Th {
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
List<OOMObject> list = new ArrayList<>();
while (true) {
System.out.println("....................");
list.add(new OOMObject());
}
}).start();
while (true) {
System.out.println(Thread.currentThread().getName() + " continuing...");
Thread.sleep(1000L);
}
}
}
class OOMObject {
}
分析堆文件
6.1总览
- 全局概览信息,堆内存大小、类数量、实例数量、Class Loader数量。
- Unreachable Object Histogram,展现转储快照时可被回收的对象信息(一般不需要关注,除非 GC 频繁影响实时性的场景分析才用到)
- Biggest Objects by Retained Size,展现经过统计过的哪几个实例所关联的对象占内存总和较高,以及具体占用的内存大小,一般相关代码比较简单情况下,往往可以直接分析具体的引用关系异常,如内存泄漏等。此外也包含了最大对象和链接支持继续深入分析。
- GC Root:GC Root 代表通过可达性分析来判定 JVM 对象是否存活的起始集合。JVM 采用追踪式垃圾回收(Tracing GC)模式,从所有 GC Roots 出发通过引用关系可以关联的对象就是存活的(所以不可回收),其余的不可达的对象(Unreachable object:如果无法从 GC Root 找到一条引用路径能到达某对象,则该对象为Unreachable object)可以回收。一般是未执行完的线程自身,或运行线程的调用栈上的对象(如局部变量、方法参数)、System class loader 加载的类、native code 保留的活动对象等。
6.2 直方图
MAT的直方图和jmap的-histo命令一样,都能够展示各个类的实例数目以及这些实例的Shallowheap总和
6.3 thread overview
with outgoing references:此对象引用了哪些对象,
with incoming references:此对象被谁引用,
6.4 Shallow Heap 和 Retained Heap
- Shallow Heap:其自身在内存中的大小
- Retained Heap:指的就是在垃圾回收特定对象时将释放的内存量
当前深堆大小 = 当前对象的浅堆大小 + 对象中所包含对象的深堆大小,如果对象包括的对象还有对象的话,也要算最里层的对象的大小 - 参考:https://cloud.tencent.com/developer/article/1530224
代码示例:
import java.util.ArrayList;
import java.util.List;
/**
* 有一个学生浏览网页的记录程序,它将记录 每个学生访问过的网站地址。
* 它由三个部分组成:Student、WebPage和StudentTrace三个类
* -XX:+HeapDumpBeforeFullGC -XX:HeapDumpPath=C:\Users\zishi\Desktop\oomdump\abc\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();
}
}
package com.zishi.jvm;
import java.util.ArrayList;
import java.util.List;
public 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);
}
}
}
public 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;
}
}
main方法启动参数:
-XX:+HeapDumpBeforeFullGC -XX:HeapDumpPath=C:\Users\zishi\Desktop\oomdump\abc\student.hprof
6.5 Dominator Tree(需进一步完善)
6.6 OQL(需进一步完善)
OQL:提供一种类似于SQL的对象(类)级别统一结构化查询语言
参考帮助文档
6.6 常见内存分析工具对比
注 1: Dump 文件包含快照被转储时刻的 Java 对象 在堆内存中的分布情况,但快照只是瞬间的记录,所以不包含对象在何时、在哪个方法中被分配这类信息。
注 2: 一般堆外内存溢出排查可结合 gperftools 与 btrace 排查,再开一篇介绍