故障原因
OutOfMemoryError
内存溢出错误是因为内存空间不足导致分配内存空间失败而抛出的异常。可能导致该问题的原因有两个:
- 内存泄漏,存在对象资源未被回收,导致内存空间不足,需要分析内存当中的对象资源占用情况。
- JVM设置的内存空间不足,需要考虑提高内存空间。
故障代码
内存溢出错误极容易重现,只要写一个死循环即可。
package com.example.demo;
import java.util.ArrayList;
import java.util.List;
public class App {
static class OOMObject {}
public static void main(String[] args) {
List<OOMObject> list = new ArrayList<App.OOMObject>();
while(true) {
list.add(new OOMObject());
}
}
}
考虑到内存耗尽时容易造成死机,在测试重现该问题时,建议通过添加JVM启动参数
限制内存的分配上限。如下:
-Xms2M -Xmn1M
这里设置了堆内存为2M,新生代内存为1M。
内存分析
当系统出现内存溢出故障时,需要生成内存快照,以此作为内存分析的依据。通过添加JVM参数,即可在系统抛出内存溢出错误时,生成当时的内存快照。
-XX:+HeapDumpOnOutOfMemoryError
最后运行程序,在系统抛出OutOfMemoryError异常后,可在项目根目录下发现文件java_pid9104.hprof。内存快照的分析,需要借助内存分析工具:比较常用的有这几款:
JProfiler
,这一款是商用软件,应该不赖,但是作为学习之用就不推荐了。Eclipse Memory Analizer
,这是一款开源的内存分析工具,但是由于生成的快照文件比较大(3.23G),用这个工具根本打不开,哪怕调整了启动参数也不行,放弃。VisualVM
,如果用的是JDK8
,自带了这款分析工具;如果是JDK9
及其以上,需要单独下载使用。
因为我是通过JDK9生成的内存快照,故单独下载安装了VisualVM,进行内存快照的分析。
通过Summary,可以看到几个需要着重关键的点:
- 抛出内存溢出异常的是哪个线程
- 哪个类和实例内存占用比较多
- 占用内存较多的类或者实例,其对应的
GC Root
是什么
通过上面几个如果,可以点击view all
查看详情,就会对故障的位置有个大致的猜想。当然,演示的例子比较简单,正式环境就需要多花一些时间去检查和验证了。