制造OOM
首先手动创造一下 OOM 场景
- 将 Java 进程的内存分的小一些,让进程尽快 OOM
- 设置堆转储(存储为hprof文件)
- 设置 hprof 文件保存目录
产生 OOM 的代码附在最后,我们先分析再看代码。
分析OOM原因
导入hprof文件
File > Open Heap Dump… > 选择 hprof 文件,在这里主要记录红框内的五个按钮的作用及使用,基本上足够我们去发现内存溢出的位置和原因。
两个直观的主要属性
Shallow Heap:浅堆。即实例自身占用的堆内存
Retained Heap:保留堆。即实例相关内容(引用内容)的堆内存
概览
导入文件之后我们看到的第一个界面就是概览
- 在 Detials 中我们可以看到在产生 OOM 时,jvm 所占的内存、类的数量、实例的数量以及类加载器的种类信息。
- 在下面的饼状图中显示了最大的实例所占的比例,左下角的描述也表现了最大实例的信息。
在我这个例子里面,最大的实例是 …testclass.OOMCreater ,自身大小 8B ,但是相关的内容(引用)却达到了 93.8 MB,在这里我们就可以猜测,应该是 OOMCreater 这个实例内部有东西在不断增加。
直方图
点击直方图,按照 Retained Heap 倒序排列:
找到占用内存最大的类型, 右键 > list objects > with income refrences,然后再按照 Retained Heap 倒序:
可以看到第一行的实例占用了 大概 93MB 的内存。
展开引用层级后发现:
- Object 实例被 ArrayList 引用,实例名称是 elementData
- 列表又被 …OOMCreater 类的实例引用,实例名称是 test
- …OOMCreater 类的实例又被 …Test 类的实例引用,实例名称是 creater
由此可以推断出,…Test 中 …OOMCreater 的实例中的列表元素 test 不断填充导致了内存溢出。
支配树
我们也可以通过支配树直接定位到出现异常的类型:
直接定位到了 OOMCreater 中的 test 列表中的 elementData 属性,以及列表中的全部内容和内存占用。
OQL
可以像 SQL 一样去查询指定类的信息:
然后 右键 > list objects > with income refrences
我们看到这个类在两个地方被引用:
一个就是我们前面找到的 …Test 类
另一个是 ConcurrentHashMap,是 Sping 的 Bean 集合,其中 singletonObjetcs 表示这是一个单例 Bean。
在 table 行,我们 右键 > Java Colelctions > Hash Entries,可以看到容器中所有的 bean 实例:
线程概览
出现了 OOM ,我们还要知道他当时在进行什么操作。
点击齿轮按钮,找到产生 OOM 的线程:
- Spring项目启动
- 项目启动后,调用了 callRunner() 方法,并执行了操作。 表示有类继承了 SpringApplicationRunner接口,实现 Spring 启动后自动执行某块逻辑。
- 逻辑中调用了 OOMCreater 中的 addUuidStr() 方法。
- addUuidStr() 方法会创建 UUID ,在创建 UUID 的过程中出现了内存泄露,即已经没有足够的堆内存。
再根据我们前面获取到的信息,OOMCreater 的属性 test 中有很多的UUID 最终导致了内存泄露,可以总结出,应该是在 …Test 中不断调用 …OOMCreater 实例的 addUuidStr() 方法造成的后果。
导致内存溢出的代码
package com.dm.cloud.testclass;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.Configuration;
@Configuration
public class Test implements ApplicationRunner {
@Autowired
private OOMCreater creater;
@Override
public void run(ApplicationArguments args) throws Exception {
for(;;) {
creater.addUuidStr();
}
}
}
package com.dm.cloud.testclass;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* 制造OOM
*/
@Component
public class OOMCreater {
private List<String> test= new ArrayList<>();
public void addUuidStr(){
String str = UUID.randomUUID().toString();
test.add(str);
}
}