本篇文章会尽力讲清楚的问题有:
1.dump文件oom文件分析,介绍mat工具的使用,常见分析字段。
2.介绍oom分析流程,如何使用mat。
使用MAT导入OOM时的dump文件。

会有一个overview视图,记录了整个堆在崩溃时的大小。
在下方有三个分栏。
Action中Histogram可以用来分析堆中各个对象大小的直方图。
Reports中Leak Suspects是MAT自动分析当前堆存在内存泄漏的报告。
拿到这个dump文件,我们着重分析,到底是什么原因导致了OOM。

直方图内容如上:
Class Name代表类名,
Objects代表该类在堆中对象的个数,
Shallow Heap代表这些对象除开自己引用的内容,本身占用的内存大小,
Retained Heap代表对象包含自身以引用的总占用的内存大小。
我们以图上BatchImportForwardOrderDTO为例:
BatchImportForwardOrderDTO
的内存组成
- 对象头(Object Header):
- Java 对象的头部包含对象的元数据,如哈希码、GC 标志、锁状态、类指针等。
- 这个部分的大小通常是 12 字节(32 位 JVM)或 16 字节(64 位 JVM,启用了对象指针压缩)。
- 实例字段(Instance Fields):
- BatchImportForwardOrderDTO的所有成员变量(基本类型和引用类型)都会占用一定的内存空间:
- 基本类型(如
int
、boolean
):直接在对象中分配并占用字节数。 - 引用类型(如
String
、数组、其他对象):占用 4 或 8 字节(根据 JVM 的设置和指针压缩),这些字段是指向实际对象的引用,不计算引用对象的大小。
- 基本类型(如
- BatchImportForwardOrderDTO的所有成员变量(基本类型和引用类型)都会占用一定的内存空间:
- 对齐填充(Padding):
- 为了满足内存对齐要求(通常是 8 字节对齐),对象的总大小会被填充到最近的 8 的倍数。
- 这个填充部分在
Shallow Heap
中计算。
例子:BatchImportForwardOrderDTO
内存布局
假设 BatchImportForwardOrderDTO
类定义如下:
1 | public class BatchImportForwardOrderDTO { |
- 对象头:16 字节(假设 64 位 JVM,指针压缩)。
- 字段内存 一共13字节 4+1+4+4
- 填充(Padding):对齐至 8 字节的倍数,因此可能会有额外的字节填充。
合计:16 + 4 + 1 + 4 + 4 = 29
字节,但因为对齐需求,总会填充至最近的 8 字节倍数,即 32 字节(Shallow Heap)。
所以一个BatchImportForwardOrderDTO的
Shallow Heap大概占用了29个字节。
所以通常Retained Heap通常都比Shallow Heap大。
回到上图,我们发现业务命名的对象,CustomerOrderDO和CustomerOrdersGoodsDO,BatchImportForwardOrderDTO占比都比较高,但是总得来说也才70MB还不足导致内存溢出。
我们注意到char[]占比特别高,已经到了231MB。
所说char[]这种基本类型很多地方用到,但是相对于String来说还是太大了。
所以我们推测应该有一个非常大的字符数组。(就算不是,也可以都看看哪里有异常)

右键需要找引用的对象,选择图上的按钮。

果然有一个字符数组高达160mb
通过后面的insert into语句,判断是一个sql。
了解到这个接口逻辑是一个类似于订单上传的接口,最大10w条。
通过代码排查发现,数据库创建订单没有做分批插入,导致10w个订单都在一个sql里插入,从而产生了一个巨大的char数组。
同理其他业务对象也是10w个,也可以都分批处理,从而减小内存占用。
- 本文作者: 宏
- 本文链接: http://sasuke.top/2024/09/20/2024-09-20/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!