如果生产环境出现了OOM问题如何定位解决呢?
对于排查 OOM 问题、分析程序堆内存使用情况,最好的方式就是分析堆转储,堆转储,包含了堆现场全貌和线程栈信息。这节就来看看如何使用MAT分析OOM问题。
MAT 分析OOM问题的思路
对于线上运行的程序,如果我们不能通过日志快速定位出OOM的根源,一般就可以使用MAT来分析OOM的问题。
使用 MAT 分析 OOM 问题,一般可以按照以下思路进行:
- 通过支配树功能或直方图功能查看消耗内存最大的类型,来分析内存泄露的大概原因;
- 查看那些消耗内存最大的类型、详细的对象明细列表,以及它们的引用链,来定位内存泄露的具体点;
- 配合查看对象属性的功能,可以脱离源码看到对象的各种属性的值和依赖关系,帮助我们理清程序逻辑和参数;
- 辅助使用查看线程栈来看 OOM 问题是否和过多线程有关,甚至可以在线程栈看到 OOM 最后一刻出现异常的线程。
如果dump出来的内存快照很大,比如有几个G,务必在启动MAT之前,先在配置文件(MemoryAnalyzer.ini)里给MAT本身设置—下堆内存大小(默认为1024m),比如设置为4个G,或者8个G。
总览图 — 快速分析OOM问题
使用MAT打开堆转储文件 dump.hprof,打开后先进入的是概览信息界面:
从饼图可以看出,明显有对象占用了大量内存,然后再看 Problem Suspect1,已经说明了 main 线程通过局部变量占据了 99.42% 内存的对象,而且是 java.lang.Object[] 数组占据了大量内存。
点击 Details 进去查看详细的说明,从 “Accumulated Objects in Dominator Tree” 支配树可以看出,main 线程引用了 OomService 对象,OomService 引用了一个 ArrayList 对象,然后 ArrayList 存储了大量 string 对象。这里基本上就能分析出OOM的根源了。
再点击 See stacktrace 看看线程栈基本就能定位到问题代码了。
直方图 — 定位根源
工具栏的第二个按钮可以打开直方图,直方图按照类型进行分组,列出了每个类有多少个实例,以及占用的内存。
可以看到,char[] 字节数组占用内存最多,对象数量也很多,第二位的 String 对象数量也非常多,有 9791 个,从这大概可以猜出应该是创建了大量的 String 对象。
在 char[] 上点击右键,选择 List objects -> with incoming references,就可以列出所有的 char[] 实例,以及每个 char[] 的整个引用关系链:
随机展开一个 char[],如下图所示:
右侧框中可以看到整个引用链,左侧的框可以查看每一个实例的内部属性。
通过这个引用链可以发现是 String 对象引用了 byte[] 数组(String 的内部结构就是一个 byte[] 数组),说明创建了大量的 String 对象;然后 String 对象又被 ArrayList 的 Object[] 数组引用着,说明是大量 String 对象放入了 ArrayList 中。到这里就定位出了引发OOM的类了。
Retained Heap(深堆) 代表对象本身和对象关联的对象占用的内存,Shallow Heap(浅堆) 代表对象本身占用的内存。比如,OOMTest 中的这个 ArrayList 对象本身只有 24 字节,但是其所有关联的对象占用了大概5MB 内存。
如果希望看到完整内容的话,可以右键选择 Copy->Value,把值复制到剪贴板或保存到文件中:
线程栈 — 分析代码
可以点击工具栏的第五个按钮,打开线程视图来分析 OOMTest 执行什么逻辑。