在虚拟机规范中,除了程序计数器,其他区域都有OutOfMemoryError异常的能,下面就实践一下这些异常。
Java堆溢出
Java堆用于存储对象实例,只要不断地创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除对象,那么在对象数量达到最大堆的容量限制后就会产生内存溢出异常。这是一个堆内存溢出的例子:
package outofmemoryerror;
import java.util.ArrayList;
import java.util.List;
/**
* @author Zhang
* @date 2018/8/16
* @Description -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
*/
public class HeapOOM {
static class OOMObject{}
public static void main(String[] args) {
List<OOMObject> list = new ArrayList<>();
while (true){
list.add(new OOMObject());
}
}
}
运行结果:
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid8480.hprof ...
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
Heap dump file created [28211988 bytes in 0.180 secs]
异常处理
首先通过内存映像分析工具对Dump出来的堆转储快照进行分析,确认内存中的对象是否是有必要的,也就是要先分清楚到底是出现了内存泄漏还是内存溢出。
- 内存泄漏(memory leak) :是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出。
- 2、内存溢出 (out of memory) :指程序申请内存时,没有足够的内存供申请者使用,或者说,给了你一块存储int类型数据的存储空间,但是你却存储long类型的数据,那么结果就是内存不够用,此时就会报错OOM,即所谓的内存溢出。
下面是用JProfiler对上述例子dump出来的快照进行分析的结果:
下图是内存使用情况,可以看出,内存一直在上升,没有下降的趋势,说明没有进行垃圾回收,发生了内存泄漏:
如果没有发生内存泄漏,那就应当检查-Xmx和-Xms参数,尝试调大。也可以检查代码,看是否存在某些对象生命周期过长、持有状态时间过长的情况。
虚拟机栈和本地方法栈溢出
栈容量由-Xss参数设定,下面是通过减小栈内存容量产生StackOverflow异常的例子:
package outofmemoryerror;
/**
* @author Zhang
* @date 2018/8/16
* @Description -Xss128k
*/
public class JavaVMStackSOF {
private int stackLength = 1;
public void stackLeak(){
stackLength++;
stackLeak();
}
public static void main(String[] args) {
JavaVMStackSOF oom = new JavaVMStackSOF();
try {
oom.stackLeak();
}catch (Throwable e){
System.out.println("Stack length:"+oom.stackLength);
throw e;
}
}
}
运行结果:
Stack length:1527
Exception in thread "main" java.lang.StackOverflowError
at outofmemoryerror.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:13)
结果表明在栈帧过大或栈容量太小,会抛出StackOverflowError异常。
方法区和运行时常量池溢出
方法区用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。可以在运行时产生大量的类填满方法区直到溢出。1.8以后方法区在元空间,当类加载过多后,元空间会自动扩展大小。可以设置参数-XX:MaxMetaspaceSize和-XX:MetaspaceSize控制元空间大小。
本机内存直接溢出
由DirectMemory导致的内存溢出,一个明显得特征是在HeapDump文件中不会看见明显得异常,如果Dump文件很小,程序中直接或间接使用了NIO,就可以考虑这个方面的原因。DirectMemory容量由-XX:MaxDirectMemorySize指定。下面是一个OOM异常的例子:
package outofmemoryerror;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
/**
* @author Zhang
* @date 2018/8/16
* @Description
*/
public class DirectMemoryOOM {
public static final int _1MB = 1024*1024;
public static void main(String[] args) throws Exception{
Field unsafeField = Unsafe.class.getDeclaredFields()[0];
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);
while (true){
unsafe.allocateMemory(_1MB);
}
}
}
分配堆外内存是通过DirectByteBuffer实现的,DirectByteBuffer在申请内存时,通过计算得知无法分配内存时,会抛出异常,真正分配内存的方法是Unsafe的allocataMemory分配内存,它是单例的,只有引导类加载器才能返回实例,运行结果如下所示:
Exception in thread "main" java.lang.OutOfMemoryError
at sun.misc.Unsafe.allocateMemory(Native Method)
at outofmemoryerror.DirectMemoryOOM.main(DirectMemoryOOM.java:21)