OutOfMemoryError异常

/ Jvm / 没有评论 / 243浏览

我们知道在Java虚拟机内存中,除了程序计数器外,其它的内存区域都可能会发生OutOfMemoryError异常。本文将用具体的事例来演示在什么情况下会出现OutOfMemoryError异常,并以此来演示一下相关的虚拟机参数。

我们知道Java堆是用来存储对象实例的,只要我们不断的创建对象,并保证它们不被Java垃圾回收器回收,当存储的对象数量超过Java堆中最大的容量时,就会抛出OutOfMemoryError异常。在Java虚拟机中可以用-Xms参数和-Xmx参数设置Java堆的容量大小。

-Xms // 设置堆的最小值
-Xmx // 设置堆的最大值

当-Xms和-Xmx参数不一致时,如果存储的对象超过-Xms时,Java堆将进行自动扩展。下面我们将-Xms和-Xmx设置为一致,以避免Java堆的自动扩展,方便我们演示Java堆的溢出。

public class HeapOOM {
static class OOMObject {
}

public static void main(String[] args) {
List<OOMObject> list = new ArrayList<OOMObject>();
while (true) {
list.add(new OOMObject());
}
}
}
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid295192.hprof ...
Heap dump file created [28138436 bytes in 0.162 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at java.util.Arrays.copyOf(Arrays.java:3210)
	at java.util.Arrays.copyOf(Arrays.java:3181)
	at java.util.ArrayList.grow(ArrayList.java:261)
	at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235)
	at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227)
	at java.util.ArrayList.add(ArrayList.java:458)
	at HeapOOM.main(HeapOOM.java:17)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)

QQ截图20170406071710.png

栈的容量由参数-Xss设置。在Java栈中有两种情况可能会抛出异常

  1. 如果线程请求栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常
  2. 如果虚拟机在扩展栈时无法获得足够的内存空间时,则抛出OutOfMemoryError异常。

下面我们通过两个例子来演示上述的异常情况。

public class StackOOM {
private int stackLength = 1;

public void stackLeak() {
stackLength++;
stackLeak();
}

public static void main(String[] args) {
StackOOM stackOOM = new StackOOM();
stackOOM.stackLeak();
}
}
Exception in thread "main" java.lang.StackOverflowError
	at StackOOM.stackLeak(StackOOM.java:11)
	at StackOOM.stackLeak(StackOOM.java:12)
	at StackOOM.stackLeak(StackOOM.java:12)
	at StackOOM.stackLeak(StackOOM.java:12)
	at StackOOM.stackLeak(StackOOM.java:12)
	at StackOOM.stackLeak(StackOOM.java:12)
	at StackOOM.stackLeak(StackOOM.java:12)

由此可见,在单线程的情况下,无论栈的大小是多少,当内存无法分配时,虚拟机都会抛出StackOverflowError异常。那如何才能抛出OutOfMemoryError异常呢?我们先看下面的例子,然后我们在做详细解释。

public class StackOOM {
public void dontStop() {
while (true) {
}
}

public void stackLeakByThread() {
while (true) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
dontStop();
}
});
thread.start();
}
}

public static void main(String[] args) {
StackOOM stackOOM = new StackOOM();
stackOOM.stackLeakByThread();
}

}

上述的代码不但会抛出OutOfMemoryError异常,还会导致系统卡死,所以要慎重执行上面代码。那为什么重复创建线程就会导致虚拟机栈内存的溢出呢?这是因为在其它文章中我们已经介绍过了,线程都有自己的独立内存空间,并且每个线程的内存空间大小是有限制的,如果创建的线程空间大小超过了系统内存时,如果继续创建线程,虚拟机无法为栈分配空间了,所以就会抛出OutOfMemoryError异常。

我们可以用下面的参数来设置方法区的大小

-XX:PermSize // 方法区设置的最小值
-XX:MaxPermSize // 方法区设置的最大值

我们可以用下面参数指定本机容量,如果不设置默认与堆的最大值一样。

-XX:MaxDirectMemorySize