文章目录
- 版权声明
- 解决内存溢出的思路
- 诊断 – 内存快照
- MAT内存泄漏检测原理
- 基础知识
- 支配树
- 深堆和浅堆
- string案例分析
- MAT内存泄漏检测原理
- 导出运行中系统内存快照
- 分析超大堆的内存快照
版权声明
- 本博客的内容基于我个人学习黑马程序员课程的学习笔记整理而成。我特此声明,所有版权属于黑马程序员或相关权利人所有。本博客的目的仅为个人学习和交流之用,并非商业用途。
- 我在整理学习笔记的过程中尽力确保准确性,但无法保证内容的完整性和时效性。本博客的内容可能会随着时间的推移而过时或需要更新。
- 若您是黑马程序员或相关权利人,如有任何侵犯版权的地方,请您及时联系我,我将立即予以删除或进行必要的修改。
- 对于其他读者,请在阅读本博客内容时保持遵守相关法律法规和道德准则,谨慎参考,并自行承担因此产生的风险和责任。
- 本博客中的部分观点和意见仅代表我个人,不代表黑马程序员的立场。
解决内存溢出的思路
- 解决内存溢出的步骤总共分为四个步骤,其中前两个步骤是最核心
诊断 – 内存快照
- 当堆内存溢出时,需要在堆内存溢出时将整个堆内存保存下来,生成内存快照(Heap Profile )文件。
- 生成内存快照的Java虚拟机参数:
-XX:+HeapDumpOnOutOfMemoryError //发生OutOfMemoryError错误时,自动生成hprof内存快照文件。 -XX:HeapDumpPath=<path> //指定hprof文件的输出路径
- 使用MAT打开hprof文件,并选择内存泄漏检测功能,MAT会自行根据内存快照中保存的数据分析内存泄漏的根源。
-
打开保存到的dump文件
-
选择内存泄露检测
-
即可查看检测的结果
-
点击details即可查看到详细的内容
-
截取当前线程栈分析问题
MAT内存泄漏检测原理
基础知识
支配树
- MAT提供支配树(Dominator Tree)的对象图。支配树展示的是对象实例间的支配关系。在对象引用图中,所有指向对象B的路径都经过对象A,则认为对象A支配对象B
深堆和浅堆
- 浅堆(Shallow Heap):支配树中对象本身占用的空间
- 对象的深堆(Retained Heap)[保留集( Retained Set )]:支配树中对象的子树就是所有被该对象支配的内容。深堆的大小表示该对象如果可以被回收,能释放多大的内存空间
string案例分析
- 使用代码生成内存快照,并分析TestClass对象的深堆和浅堆。
- 在不内存溢出情况下生成堆内存快照:
-XX:+HeapDumpBeforeFullGC
可以在FullGC之前就生成内存快照
import java.util.ArrayList;
import java.util.List;
//-XX:+HeapDumpBeforeFullGC -XX:HeapDumpPath=D:/jvm/dump/mattest.hprof
public class HeapDemo {
public static void main(String[] args) {
TestClass a1 = new TestClass();
TestClass a2 = new TestClass();
TestClass a3 = new TestClass();
String s1 = "itheima1";
String s2 = "itheima2";
String s3 = "itheima3";
a1.list.add(s1);
a2.list.add(s1);
a2.list.add(s2);
a3.list.add(s3);
//System.out.print(ClassLayout.parseClass(TestClass.class).toPrintable());
s1 = null;
s2 = null;
s3 = null;
System.gc();
}
}
class TestClass {
public List<String> list = new ArrayList<>(10);
}
- 打开支配树
- 关于ithmeima1 string对象浅堆,占用24字节的解释
- 类的对象的大小主要由成员变量组成,通过查看源码,可以看到string类中的成员变量主要有
vales[]
和int组成,只需占用8个字节,为什么ithmeima1 string对象占用了24个字节呢?- 引入框架依赖, 打印整个对象的组成
<dependency> <groupId>org.openjdk.jol</groupId> <artifactId>jol-core</artifactId> <version>0.9</version> </dependency>
- 使用框架解析和打印对象的组成
System.out.print(ClassLayout.parseClass(TestClass.class).toPrintable());
- 输出结果如下:包含对象头(12字节)、字符串(4字节),string对象的hash值(4字节),用于对8字节整除对象而填充的4字节
java.lang.String object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 12 (object header) N/A 12 4 char[] String.value N/A 16 4 int String.hash N/A 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
MAT内存泄漏检测原理
- MAT根据支配树,从叶子节点向根节点遍历,如果发现深堆的大小超过整个堆内存的一定比例阈值,就会将其标记成内存泄漏的“嫌疑对象”。
导出运行中系统内存快照
- 如果希望尽快通过内存快照分析增长的原因,由于并未产生内存溢出所以不能通过HeapDumpOnOutOfMemoryError参数生成内存快照。
- 导出运行中系统的内存快照,比较简单的方式有两种,注意只需要导出标记为存活的对象:
- 通过JDK自带的jmap命令导出,格式为:
jmap -dump:live,format=b,file=文件路径和文件名 进程ID
- 通过arthas的heapdump命令导出,格式为:
heapdump --live 文件路径和文件名
分析超大堆的内存快照
- MAT打开堆内存快照时,需要将所有的内容读入到内存,一般分析机器需要内存大小的堆内存快照大小的1.2~1.5倍。
- 在程序员开发用的机器内存范围之内的快照文件,直接使用MAT打开分析即可。但是经常会遇到服务器上的程序占用的内存达到10G以上,开发机无法正常打开此类内存快照,此时需要下载服务器操作系统对应的MAT
- 通过MAT中的脚本生成分析报告
./ParseHeapDump.sh 快照文件路径 org.eclipse.mat.api:suspects org.eclipse.mat.api:overview org.eclipse.mat.api:top_components
- 默认MAT分析时只使用了1G的堆内存,如果快照文件超过1G,需要修改MAT目录下的MemoryAnalyzer.ini配置文件调整最大堆内存