除了程序计数器外,虚拟机内存的其他几个运行时区域都有发生OOM的可能,如果能够区分根据报错区分出是哪些区域报出来的异常,会更便于定位问题,解决问题。
一、java堆溢出
java.lang.OutOfMemoryError:java heap space
原因:产生大量不会被垃圾回收机制清除的对象(GC Roots到对象之间有可达路径)
解决方案:先通过内存映像分析工具对Dump出来的堆转储快照进行分析,确认到底是发生了内存泄露(Memory Leak)还是内存溢出(Memory Overflow),如果是内存泄露,查看泄露对象到GC Roots的引用链,定位出泄露代码的位置。如果是内存溢出,那么就可以检查堆参数(-Xms 与 -Xmx),与机器物理内存对比看是否还可以调大,从代码上检查是否存在某些对象生命周期过长、持有状态时间过长的情况。
关于内存溢出与内存泄露的区别:
内存溢出是指申请的内存空间比剩余的内存空间要大,比如剩余1M的空间,但是实例化了一个2M的对象,就发生了内存溢出。内存泄露是指程序在申请了空间之后,无法释放已申请的空间,而这部分空间程序也访问不到,一次内存泄露可以忽视,但是内存泄露的堆积,最终也会造成内存溢出。(我的理解就是看内存中的对象是不是必要的,是必要的那就是内存溢出,不是必要的也没办法清除他的,就是内存泄露,需要找泄露的代码)
看这个例子:
public class HeapOOM {
static class OOMObject{
}
public static void main(String[] args) {
List<OOMObject> list = new ArrayList<>();
while(true){
list.add(new OOMObject());
}
}
}
jvm参数是:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=E:\JAVA\dump
因为堆内存就设置了20m,所以启动程序后很快能报OOM
打开jdk/bin/jvisualvm.exe,然后在这个可视化的界面中打开刚刚保存的内存堆转储快照,可以看到
HeapOOM类的内部类OOMObject的实例化对象几乎占满了整个堆内存,所以也就查到了堆内存溢出的原因,当然实际的没这么简单,这里只是简单的说明下。
,
二、虚拟机栈和本地方法栈溢出
由于Hot Spot虚拟机的实现是不区分两者的,所以可以通过-Xss参数来设定栈容量。
1、java.lang.StackOverflowError
原因:在单线程下,虚拟机栈容量太小或者栈帧太大,会抛出SO;
解决方法:增大虚拟机栈容量;看看是不是定义了大量的本地变量,进行优化;可能是递归没有出口导致方法的栈深度过大,给递归补上出口。
2、java.lang.OutOfMemoryError: unable to create new native thread
原因:在多线程下,大量创建新线程,会抛出OOM,每个线程的栈分配的内存越大,越容易产生;
解决方法:减少线程产生、减少最大堆、减少栈容量;
三、运行时常量池溢出
1、java.lang.OutOfMemoryError: PermGen space
2、java.lang.OutOfMemoryError: Java heap space
原因:在jdk1.7及以后的版本把常量池从方法区移到了堆中,代码运行时创建了大量的常量,造成了堆内存溢出
解决方法:通过修改-Xmx、-Xms来修改堆内存的大小。
四、方法区溢出
java.lang.OutOfMemoryError: PermGen space
at java.lang.ClassLoader.defineClass(Native Method)
原因:在运行时,ClassLoader动态加载了大量的Class信息,超出方法区上限;
解决方法:通过修改-XX:PermSize和-XX:MaxPermSize参数来修改方法区大小;
五、本机直接内存溢出
java.lang.OutOfMemoryError
at com.bckj.OOM.DirectMemoryOOM.main(DirectMemoryOOM.java:18)
直接内存溢出,一个明显的特征就是在Heap Dump文件中不会看见明显的异常,若发现OOM后Dump文件很小,而程序中又直接或间接的使用了NIO,就可以考虑下这个原因