1. 前言
熟練掌握 MAT 是 JAVA 高手的必備能力,但實踐時大家往往需面對眾多功能,眼花繚亂不知如何下手,小編也沒有找到一篇完善的教學素材,所以整理本文幫大家系統(tǒng)掌握 MAT 分析工具。
本文詳細講解 MAT 眾多內存分析工具功能,這些功能組合使用異常強大,熟練使用幾乎可以解決所有的堆內存離線分析的問題。我們將功能劃分為4類:內存分布詳情、對象間依賴、對象狀態(tài)詳情、按條件檢索。每大類有多個功能點,本文會逐一講解各功能的場景及用法。此外,添加了原創(chuàng)或引用案例加強理解和掌握。
如圖所示:

為減少對眼花繚亂的菜單的迷茫,可以通過下圖先整體熟悉下各功能使用入口,后續(xù)都會講到。

2. 內存分布詳解及實戰(zhàn)
2.1 全局信息概覽
功能:展現(xiàn)堆內存大小、對象數(shù)量、class 數(shù)量、class loader 數(shù)量、GC Root 數(shù)量、環(huán)境變量、線程概況等全局統(tǒng)計信息。
使用入口:MAT 主界面 → Heap Dump Overview。
舉例:下面是對象數(shù)量、class loader 數(shù)量、GC Root 數(shù)量,可以看出 class loader 存在異常。

舉例:下圖是線程概況,可以查看每個線程名、線程的 Retained Heap、daemon 屬性等。

使用場景 全局概覽呈現(xiàn)全局統(tǒng)計信息,重點查看整體是否有異常數(shù)據,所以有效信息有限,下面幾種場景有一定幫助:
- 方法區(qū)溢出時(Java 8后不使用方法區(qū),對應堆溢出),查看 class 數(shù)量異常多,可以考慮是否為動態(tài)代理類異常載入過多或類被反復重復加載。
- 方法區(qū)溢出時,查看 class loader 數(shù)量過多,可以考慮是否為自定義 class loader 被異常循環(huán)使用。
- GC Root 過多,可以查看 GC Root 分布,理論上這種情況極少會遇到,筆者只在 JNI 使用一個存在 BUG 的庫時遇到過。
- 線程數(shù)過多,一般是頻繁創(chuàng)建線程但無法執(zhí)行結束,從概覽可以了解異常表象,具體原因可以參考本文線程分析部分內容,此處不展開。
2.2 Dominator tree
注:筆者使用頻率的 Top1,是高效分析 Dump 必看的功能。
功能
- 展現(xiàn)對象的支配關系圖,并給出對象支配內存的大?。ㄖ鋬却娴韧?Retained Heap,即其被 GC 回收可釋放的內存大?。?/li>
- 支持排序、支持按 package、class loader、super class、class 聚類統(tǒng)計
使用入口:全局支配樹: MAT 主界面 → Dominator tree。
舉例: 下圖中通過查看 Dominator tree,了解到內存主要是由 ThreadAndListHolder-thread 及 main 兩個線程支配(后面第2.6節(jié)會給出整體案例)。

使用場景
- 開始 Dump 分析時,首先應使用 Dominator tree 了解各支配樹起點對象所支配內存的大小,進而了解哪幾個起點對象是 GC 無法釋放大內存的原因。
- 當個別對象支配樹的 Retained Heap 很大存在明顯傾斜時,可以重點分析占比高的對象支配關系,展開子樹進一步定位到問題根因,如下圖中可看出最終是 SameContentWrApperContainer 對象持有的 ArrayList 過大。

- 在 Dominator tree 中展開樹狀圖,可以查看支配關系路徑(與 outgoing reference 的區(qū)別是:如果 X 支配 Y,則 X 釋放后 Y必然可釋放;如果僅僅是 X 引用 Y,可能仍有其他對象引用 Y,X 釋放后 Y 仍不能釋放,所以 Dominator tree 去除了 incoming reference 中大量的冗余信息)。
- 有些情況下可能并沒有支配起點對象的 Retained Heap 占用很大內存(比如 class X 有100個對象,每個對象的 Retained Heap 是10M,則 class X 所有對象實際支配的內存是 1G,但可能 Dominator tree 的前20個都是其他class 的對象),這時可以按 class、package、class loader 做聚合,進而定位目標。
- 下圖中各 GC Roots 所支配的內存均不大,這時需要聚合定位爆發(fā)點。

在 Dominator tree 展現(xiàn)后按 class 聚合,如下圖:

可以定位到是 SomeEntry 對象支配內存較多,然后結合代碼進一步分析具體原因。

在一些操作后定位到異常持有 Retained Heap 對象后(如從代碼看對象應該被回收),可以獲取對象的直接支配者,操作方式如下。


2.3 Histogram 直方圖
注:筆者使用頻率 Top2
功能
- 羅列每個類實例的數(shù)量、類實例累計內存占比,包括自身內存占用量(Shallow Heap)及支配對象的內存占用量(Retain Heap)。
- 支持按對象數(shù)量、Retained Heap、Shallow Heap(默認排序)等指標排序;支持按正則過濾;支持按 package、class loader、super class、class 聚類統(tǒng)計,
使用入口:MAT 主界面 → Histogram;注意 Histogram 默認不展現(xiàn) Retained Heap,可以使用計算器圖標計算,如下圖所示。

使用場景
- 有些情況 Dominator tree 無法展現(xiàn)出熱點對象(上文提到 Dominator tree 支配內存排名前20的占比均不高,或者按 class 聚合也無明顯熱點對象,此時 Dominator tree 很難做關聯(lián)分析判斷哪類對象占比高),這時可以使用 Histogram 查看所有對象所屬類的分布,快速定位占據 Retained Heap 大頭的類。
使用技巧
- Integer,String 和 Object[] 一般不直接導致內存問題。為更好的組織視圖,可以通過 class loader 或 package 分組進一步聚焦,如下圖。

Histogram 支持使用正則表達式來過濾。例如,我們可以只展示那些匹配com.q.*的類。

可以在 Histogram 的某個類繼續(xù)使用 outgoing reference 查看對象分布,進而定位哪些對象是大頭


2.4 Leak Suspects
功能:具備自動檢測內存泄漏功能,羅列可能存在內存泄漏的問題點。
使用入口:一般當存在明顯的內存泄漏時,分析完Dump文件后就會展現(xiàn),也可以如下圖在 MAT 主頁 → Leak Suspects。
使用場景:需要查看引用鏈條上占用內存較多的可疑對象。這個功能可解決一些基礎問題,但復雜的問題往往幫助有限。
舉例
- 下圖中 Leak Suspects 視圖展現(xiàn)了兩個線程支配了絕大部分內存。

下圖是點擊上圖中 Keywords 中 "Details" ,獲取實例到 GC Root 的最短路徑、dominator 路徑的細信息。

2.5 Top Consumers
功能:最大對象報告,可以展現(xiàn)哪些類、哪些 class loader、哪些 package 占用最高比例的內存,其功能 Histogram 及 Dominator tree 也都支持。
使用場景:應用程序發(fā)生內存泄漏時,查看哪些泄漏的對象通常在 Dump 快照中會占很大的比重。因此,對簡單的問題具有較高的價值。
2.6 綜合案例一
使用工具項:Heap dump overview、Dominator tree、Histogram、Class Loader Explorer(見3.4節(jié))、incoming references(見3.1節(jié))
程序代碼
package com.q.mat;
import java.util.*;
import org.objectweb.asm.*;
public class ClassLoaderOOMOps extends ClassLoader implements Opcodes {
public static void main(final String args[]) throws Exception {
new ThreadAndListHolder(); // ThreadAndListHolder 類中會加載大對象
List<ClassLoader> classLoaders = new ArrayList<ClassLoader>();
final String className = "ClassLoaderOOMExample";
final byte[] code = geneDynamicClassBytes(className);
// 循環(huán)創(chuàng)建自定義 class loader,并加載 ClassLoaderOOMExample
while (true) {
ClassLoaderOOMOps loader = new ClassLoaderOOMOps();
Class<?> exampleClass = loader.defineClass(className, code, 0, code.length); //將二進制流加載到內存中
classLoaders.add(loader);
// exampleClass.getMethods()[0].invoke(null, new Object[]{null}); // 執(zhí)行自動加載類的方法,通過反射調用main
}
}
private static byte[] geneDynamicClassBytes(String className) throws Exception {
ClassWriter cw = new ClassWriter(0);
cw.visit(V1_1, ACC_PUBLIC, className, null, "java/lang/Object", null);
//生成默認構造方法
MethodVisitor mw = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
//生成構造方法的字節(jié)碼指令
mw.visitVarInsn(ALOAD, 0);
mw.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
mw.visitInsn(RETURN);
mw.visitMaxs(1, 1);
mw.visitEnd();
//生成main方法
mw = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
//生成main方法中的字節(jié)碼指令
mw.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mw.visitLdcInsn("Hello world!");
mw.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
mw.visitInsn(RETURN);
mw.visitMaxs(2, 2);
mw.visitEnd(); //字節(jié)碼生成完成
return cw.toByteArray(); // 獲取生成的class文件對應的二進制流
}
}
package com.q.mat;
import java.util.*;
import org.objectweb.asm.*;
public class ThreadAndListHolder extends ClassLoader implements Opcodes {
private static Thread innerThread1;
private static Thread innerThread2;
private static final SameContentWrapperContainerProxy sameContentWrapperContainerProxy = new SameContentWrapperContainerProxy();
static {
// 啟用兩個線程作為 GC Roots
innerThread1 = new Thread(new Runnable() {
public void run() {
SameContentWrapperContainerProxy proxy = sameContentWrapperContainerProxy;
try {
Thread.sleep(60 * 60 * 1000);
} catch (Exception e) {
System.exit(1);
}
}
});
innerThread1.setName("ThreadAndListHolder-thread-1");
innerThread1.start();
innerThread2 = new Thread(new Runnable() {
public void run() {
SameContentWrapperContainerProxy proxy = proxy = sameContentWrapperContainerProxy;
try {
Thread.sleep(60 * 60 * 1000);
} catch (Exception e) {
System.exit(1);
}
}
});
innerThread2.setName("ThreadAndListHolder-thread-2");
innerThread2.start();
}
}
class IntArrayListWrapper {
private ArrayList<Integer> list;
private String name;
public IntArrayListWrapper(ArrayList<Integer> list, String name) {
this.list = list;
this.name = name;
}
}
class SameContentWrapperContainer {
// 2個Wrapper內部指向同一個 ArrayList,方便學習 Dominator tree
IntArrayListWrapper intArrayListWrapper1;
IntArrayListWrapper intArrayListWrapper2;
public void init() {
// 線程直接支配 arrayList,兩個 IntArrayListWrapper 均不支配 arrayList,只能線程運行完回收
ArrayList<Integer> arrayList = generateSeqIntList(10 * 1000 * 1000, 0);
intArrayListWrapper1 = new IntArrayListWrapper(arrayList, "IntArrayListWrapper-1");
intArrayListWrapper2 = new IntArrayListWrapper(arrayList, "IntArrayListWrapper-2");
}
private static ArrayList<Integer> generateSeqIntList(int size, int startValue) {
ArrayList<Integer> list = new ArrayList<Integer>(size);
for (int i = startValue; i < startValue + size; i++) {
list.add(i);
}
return list;
}
}
class SameContentWrapperContainerProxy {
SameContentWrapperContainer sameContentWrapperContainer;
public SameContentWrapperContainerProxy() {
SameContentWrapperContainer container = new SameContentWrapperContainer();
container.init();
sameContentWrapperContainer = container;
}
}
啟動參數(shù):-Xmx512m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/Users/gjd/Desktop/dump/heapdump.hprof
-XX:-UseCompressedClassPointers -XX:-UseCompressedOops
引用關系圖

分析過程
- 首先進入 Dominator tree,可以看出是 SameContentWrapperContainerProxy 對象與 main 線程兩者持有99%內存不能釋放導致 OOM。


先來看方向一,在 Heap Dump Overview中可以快速定位到 Number of class loaders 數(shù)達50萬以上,這種基本屬于異常情況,如下圖所示。

使用 Class Loader Explorer 分析工具,此時會展現(xiàn)類加載詳情,可以看到有524061個 class loader。我們的案例中僅有ClassLoaderOOMOps 這樣的自定義類加載器,所以很快可以定位到問題。


如果類加載器較多,不能確定是哪個引發(fā)問題,則可以將所有的 class loader對象按類做聚類,如下圖所示。

Histogram 會根據 class 聚合,并展現(xiàn)對象數(shù)量及其 Shallow Heap 及 Retained Heap(如Retained Heap項目為空,可以點擊下圖中計算機的圖標并計算 Retained Heap),可以看到 ClassLoaderOOMOps 有524044個對象,其 Retain Heap 占據了370M以上(上述代碼是100M左右)。

使用 incoming references,可以找到創(chuàng)建的代碼位置。

再來看方向二,同樣在占據319M內存的 Obejct 數(shù)組采用 incoming references 查看引用路徑,也很容易定位到具體代碼位置。并且從下圖中我們看出,Dominator tree 的起點并不一定是 GC根,且通過 Dominator tree 可能無法獲取到最開始的創(chuàng)建路徑,但 incoming references 是可以的。

3. 對象間依賴詳解及實戰(zhàn)
3.1 References
注:筆者使用頻率 Top2
功能:在對象引用圖中查看某個特定對象的所有引用關系(提供對象對其他對象或基本類型的引用關系,以及被外部其他對象的引用關系)。通過任一對象的直接引用及間接引用詳情(主要是屬性值及內存占用),提供完善的依賴鏈路詳情。
使用入口:目標域右鍵 → List objects → with outgoing references/with incoming references.
使用場景
- outgoing reference:查看對象所引用的對象,并支持鏈式傳遞操作。如查看一個大對象持有哪些內容,當一個復雜對象的 Retained Heap 較大時,通過 outgoing reference 可以查看由哪個屬性引發(fā)的。下圖中 A 支配 F,且 F 占據大量內存,但優(yōu)化時 F 的直接支配對象 A 無法修改。可通過 outgoing reference 看關系鏈上 D、B、E、C,并結合業(yè)務邏輯優(yōu)化中間環(huán)節(jié),這依托 dominator tree 是做不到的。
- incoming reference:查看對象被哪些對象引用,并支持鏈式傳遞操作。如查看一個大對象都被哪些對象引用,下圖中 K 占內存大,所以 J 的 Retained Heap 較大,目標是從 GC Roots 摘除 J 引用,但在 Dominator tree 上 J 是樹根,無法獲取其被引用路徑,可通過 incoming reference 查看關系鏈上的 H、X、Y ,并結合業(yè)務邏輯將 J 從 GC Root 鏈摘除。

3.2 Thread overview
功能:展現(xiàn)轉儲 dump 文件是線程執(zhí)行棧、線程棧引用的對象等詳細狀態(tài),也提供各線程的 Retained Heap 等關聯(lián)內存信息。
使用入口:MAT 主頁 → Thread overview
使用場景
- 查看不同線程持有的內存占比,定位高內存消耗線程(開發(fā)技巧:不要直接使用 Thread 或 Executor 默認線程名避免全部混合在一起,使用線程盡量自命名方便識別,如下圖中 ThreadAndListHolder-thread 是自定義線程名,可以很容易定位到具體代碼)
- 查看線程的執(zhí)行棧及變量,結合業(yè)務代碼了解線程阻塞在什么地方,以及無法繼續(xù)運行釋放內存,如下圖中 ThreadAndListHolder-thread 阻塞在 sleep 方法。

3.3 Path To GC Roots
功能:提供任一對象到 GC Root 的路徑詳情。
使用入口:目標域右鍵 → Path To GC Roots
使用場景:有時你確信已經處理了大的對象集合但依然無法回收,該功能能快速定位異常對象不能被 GC 回收的原因,直擊異常對象到 GC Root 的引用路徑。比 incoming reference 的優(yōu)勢是屏蔽掉很多不需關注的引用關系,比 Dominator tree 的優(yōu)勢是可以得到更全面的信息。
小技巧:在排查內存泄漏時,建議選擇 exclude all phantom/weak/soft etc.references 排除虛引用/弱引用/軟引用等的引用鏈,因為被虛引用/弱引用/軟引用的對象可以直接被 GC 給回收,聚焦在對象是否還存在 Strong 引用鏈即可。

3.4 class loader 分析
功能
- 查看堆中所有 class loader 的使用情況(入口:MAT 主頁菜單藍色桶圖標 → Java Basics → Class Loader Explorer)。
- 查看堆中被不同class loader 重復加載的類(入口:MAT 主頁菜單藍色桶圖標 → Java Basics → Duplicated Classes)。
使用場景
- 當從 Heap dump overview 了解到系統(tǒng)中 class loader 過多,導致占用內存異常時進入更細致的分析定位根因時使用。
- 解決 NoClassDefFoundError 問題或檢測 jar 包是否被重復加載
具體使用方法在 2.6 及 3.5 兩節(jié)的案例中有介紹。
3.5 綜合案例二
使用工具項:class loader(重復類檢測)、inspector、正則檢索。
異?,F(xiàn)象 :運行時報 NoClassDefFoundError,在 classpath 中有兩個不同版本的同名類。
分析過程
- 進入 MAT 已加載的重復類檢測功能,方式如下圖。

可以看到所有重復的類,以及相關的類加載器,如下圖。

- 根據類名,在<Regex>框中輸入類名可以過濾無效信息。
- 選中目標類,通過Inspector視圖,可以看到被加載的類具體是在哪個jar包里。(本例中重復的類是被 URLClassloader 加載的,右鍵點擊 “_context” 屬性,最后點擊 “Go Into”,在彈出的窗口中的屬性 “_war” 值是被加載類的具體包位置)


4. 對象狀態(tài)詳解及實戰(zhàn)
4.1 inspector
功能:MAT 通過 inspector 面板展現(xiàn)對象的詳情信息,如靜態(tài)屬性值及實例屬性值、內存地址、類繼承關系、package、class loader、GC Roots 等詳情數(shù)據。
使用場景
- 當內存使用量與業(yè)務邏輯有較強關聯(lián)的場景,通過 inspector 可以通過查看對象具體屬性值。比如:社交場景中某個用戶對象的好友列表異常,其 List 長度達到幾億,通過 inspector 面板獲取到異常用戶 ID,進而從業(yè)務視角繼續(xù)排查屬于哪個用戶,本里可能有系統(tǒng)賬號,與所有用戶是好友。
- 集合等類型的使用會較多,如查看 ArrayList 的 size 屬性也就了解其大小。
舉例:下圖中左邊的 Inspector 窗口展現(xiàn)了地址 0x125754cf8 的 ArrayList 實例詳情,包括 modCount 等并不會在 outgoing references 展現(xiàn)的基本屬性。

4.2 集合狀態(tài)
功能:幫助更直觀的了解系統(tǒng)的內存使用情況,查找浪費的內存空間。
使用入口:MAT 主頁 → Java Collections → 填充率/Hash沖突等功能。

使用場景
- 通過對 ArrayList 或數(shù)組等集合類對象按填充率聚類,定位稀疏或空集合類對象造成的內存浪費。
- 通過 HashMap 沖突率判定 hash 策略是否合理。
具體使用方法在 4.3 節(jié)案例詳細介紹。
4.3 綜合案例三
使用工具項:Dominator tree、Histogram、集合 ratio。
異常現(xiàn)象 :程序 OOM,且 Dominator tree 無大對象,通過 Histogram 了解到多個 ArrayList 占據大量內存,期望通過減少 ArrayList 優(yōu)化程序。
程序代碼
package com.q.mat;
import java.util.ArrayList;
import java.util.List;
public class ListRatioDemo {
public static void main(String[] args) {
for(int i=0;i<10000;i++){
Thread thread = new Thread(new Runnable() {
public void run() {
HolderContainer holderContainer1 = new HolderContainer();
try {
Thread.sleep(1000 * 1000 * 60);
} catch (Exception e) {
System.exit(1);
}
}
});
thread.setName("inner-thread-" + i);
thread.start();
}
}
}
class HolderContainer {
ListHolder listHolder1 = new ListHolder().init();
ListHolder listHolder2 = new ListHolder().init();
}
class ListHolder {
static final int LIST_SIZE = 100 * 1000;
List<String> list1 = new ArrayList(LIST_SIZE); // 5%填充
List<String> list2 = new ArrayList(LIST_SIZE); // 5%填充
List<String> list3 = new ArrayList(LIST_SIZE); // 15%填充
List<String> list4 = new ArrayList(LIST_SIZE); // 30%填充
public ListHolder init() {
for (int i = 0; i < LIST_SIZE; i++) {
if (i < 0.05 * LIST_SIZE) {
list1.add("" + i);
list2.add("" + i);
}
if (i < 0.15 * LIST_SIZE) {
list3.add("" + i);
}
if (i < 0.3 * LIST_SIZE) {
list4.add("" + i);
}
}
return this;
}
}
分析過程
- 使用 Dominator tree 查看并無高占比起點。

使用 Histogram 定位到 ListHolder 及 ArrayList 占比過高,經過業(yè)務分析很多 List 填充率很低,不會浪費內存。

查看 ArrayList 的填充率,MAT 首頁 → Java Collections → Collection Fill Ratio。

查看類型填寫 java.util.ArrayList。

從結果可以看出絕大部分 ArrayList 初始申請長度過大。

5. 按條件檢索詳解及實戰(zhàn)
5.1 OQL
功能:提供一種類似于SQL的對象(類)級別統(tǒng)一結構化查詢語言,根據條件對堆中對象進行篩選。
語法
SELECT * FROM [ INSTANCEOF ] <class_name> [ WHERE <filter-expression> ]
- Select 子句可以使用“*”,查看結果對象的引用實例(相當于 outgoing references);可以指定具體的內容,如 Select OBJECTS v.elementData from xx 是返回的結果是完整的對象,而不是簡單的對象描述信息);可以使用 Distinct 關鍵詞去重。
- From 指定查詢范圍,一般指定類名、正則表達式、對象地址。
- Where 用來指定篩選條件。
- 全部語法詳見:OQL 語法
- 未支持的核心功能:group by value,如果有需求可以先導出結果到 csv 中,再使用 awk 等腳本工具分析即可。
例子:查找 size=0 且未使用過的 ArrayList:select * from java.util.ArrayList where size=0 and modCount=0。
使用場景
- 一般比較復雜的問題會使用 OQL,而且這類問題往往與業(yè)務邏輯有較大關系。比如大量的小對象整體占用內存高,但預期小對象應該不會過多(比如達到百萬個),一個一個看又不現(xiàn)實,可以采用 OQL 查詢導出數(shù)據排查。
例如:微服務的分布式鏈路追蹤系統(tǒng),采集各服務所有接口名,共計200個服務卻采集到了200萬個接口名(一個服務不會有1萬個接口),這時直接在 List 中一個個查看很難定位,可以直接用 OQL 導出,定位哪個服務接口名收集異常(如把 URL 中 ID 也統(tǒng)計到接口中了)
5.2 檢索及篩選
功能:本文第二章內存分布,第三章對象間依賴的眾多功能,均支持按字符串檢索、按正則檢索等操作。
使用場景:在使用 Histogram、Thread overview 等功能時,可以進一步添加字符串匹配、正則匹配條件過濾縮小排查范圍。
5.3 按地址尋址
功能:根據對象的虛擬內存十六進制地址查找對象。
使用場景:僅知道地址并希望快速查看對象做后續(xù)分析時使用,其余可以直接使用 outgoing reference 了解對象信息。
5.4 綜合案例四
使用工具項:OQL、Histogram、incoming references
異常現(xiàn)象及目的 :程序占用內存高,存在默認初始化較長的 ArrayList,需分析 ArrayList 被使用的占比,通過數(shù)據支撐是否采用懶加載模式,并分析具體哪塊代碼創(chuàng)建了空 ArrayList。
程序代碼
public class EmptyListDemo {
public static void main(String[] args) {
EmptyValueContainerList emptyValueContainerList = new EmptyValueContainerList();
FilledValueContainerList filledValueContainerList = new FilledValueContainerList();
System.out.println("start sleep...");
try {
Thread.sleep(50 * 1000 * 1000);
} catch (Exception e) {
System.exit(1);
}
}
}
class EmptyValueContainer {
List<Integer> value1 = new ArrayList(10);
List<Integer> value2 = new ArrayList(10);
List<Integer> value3 = new ArrayList(10);
}
class EmptyValueContainerList {
List<EmptyValueContainer> list = new ArrayList(500 * 1000);
public EmptyValueContainerList() {
for (int i = 0; i < 500 * 1000; i++) {
list.add(new EmptyValueContainer());
}
}
}
class FilledValueContainer {
List<Integer> value1 = new ArrayList(10);
List<Integer> value2 = new ArrayList(10);
List<Integer> value3 = new ArrayList(10);
public FilledValueContainer init() {
value1.addAll(Arrays.asList(1, 3, 5, 7, 9));
value2.addAll(Arrays.asList(2, 4, 6, 8, 10));
value1.addAll(Arrays.asList(1, 1, 1, 1, 1, 1, 1, 1, 1, 1));
return this;
}
}
class FilledValueContainerList {
List<FilledValueContainer> list = new ArrayList(500);
public FilledValueContainerList() {
for (int i = 0; i < 500; i++) {
list.add(new FilledValueContainer().init());
}
}
}
分析過程
- 內存中有50萬個 capacity = 10 的空 ArrayList 實例。我們分析下這些對象的占用內存總大小及對象創(chuàng)建位置,以便分析延遲初始化(即直到使用這些對象的時候才將之實例化,否則一直為null)是否有必要。
- 使用 OQL 查詢出初始化后未被使用的 ArrayList(size=0 且 modCount=0),語句如下圖??梢钥闯龉?150 萬個空 ArrayList,這些對象屬于浪費內存。我們接下來計算下總計占用多少內存,并根據結果看是否需要優(yōu)化。

計算 150萬 ArrayList占內存總量,直接點擊右上方帶黃色箭頭的 Histogram 圖標,這個圖標是在選定的結果再用直方圖展示,總計支配了120M 左右內存(所以這里點擊結果,不包含 modCount 或 size 大于0的 ArrayList 對象)。這類在選定結果繼續(xù)分析很多功能都支持,如正則檢索、Histogram、Dominator tree等等。

查看下圖 ArrayList 的具體來源,可用 incoming references,下圖中顯示了清晰的對象創(chuàng)建路徑。

總結
至此本文講解了 MAT 各項工具的功能、使用方法、適用場景,也穿插了4個實戰(zhàn)案例,熟練掌握對分析 JVM 內存問題大有裨益,尤其是各種功能的組合使用。
作者:Q的博客
來源:https://juejin.cn/post/6911624328472133646