Java内存溢出异常实例源码堆方法区虚拟机栈



Java内存溢出异常实例源码堆方法区虚拟机栈,JVM内存几个重要区域:堆,方法区,虚拟机栈,本地方法栈,程序计数器。出了程序计数器之外其他区域都有可能找出OutOfMemoryError,下面简称OOM。

  • 堆溢出,堆用来存放Java类实例,当类实例不断创建,而垃圾由于种种原因无法进行类实例的垃圾回收,在这种情况下,堆内存就很有可能会突破其上限,这个时候就会报OOM。单元测试代码如下:
1
2
3
4
5
6
7
@Test
public void testHeapOutOfMemoryError() {
    List list = new ArrayList<>();
    for (;;) {
        list.add("test");
    }
}
  • 栈深度溢出,增加此方法栈中本地变量表的长度,一般出现在递归调用中,递归调用深度过大造成,这也是使用递归的潜在风险。结果会抛出StackOverflowError。下面的测试用例在windows下会造成系统假死,请谨慎运行。
1
2
3
4
5
6
7
8
9
    @Test
public void testStackException() {
test(0, 1);
}
public int test(int i ,int j) {
j = i + j;
return test(i, j);
}
  • 栈内存溢出,栈内存=2G – 堆内存 – 方法区内存。对于每一个线程,栈内存都是独有的,所以,如果线程数越多或者或者当个线程栈内存分配越大,都会造成栈内存溢出。
1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void testStackOutOfMemoryError() {
    while (true) {
        new Thread(new Runnable() {
            public void run() {
                while (true) {
                }
            }
        }).start();
    }
}
  • 方法区溢出,方法区大小是程序运行前指定的,如果程序载入的class,jar和动态代理或者字节码增强造成程序运行时载入超过指定的大小,就会造成OOM。次例子使用CGLib直接操作字节码运行时,生成大量的动态类。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Test
public void testMethdoAreaOutOfMemoryError() {
    while(true) {
        Enhancer enhance = new Enhancer();
        enhance.setSuperclass(OOMObject.class);
        enhance.setUseCache(false);
        enhance.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object obj, Method arg1, Object[] args, MethodProxy proxy) throws Throwable {
                return proxy.invokeSuper(obj, args);
            }
        });
        enhance.create();
    }
}
static class OOMObject {
}
  • 本机直接内存溢出,在JDK1.4之后,Java引入了NIO,其中Channel和Buffer的实现,采用了直接内存的方法,以提高IO速度,所以使用DirectByteBuffer分配内存就有可能会抛出OOM;本例子绕过了DirectByteBuffer,直接使用Unsafe分配内存。
1
2
3
4
5
6
7
8
9
@Test
public void testDirectOutOfMemoryError() throws IllegalArgumentException, IllegalAccessException {
    int _1MB = 1024 *1024;
    Field unsafeField = Unsafe.class.getDeclaredFields()[0];
    Unsafe unsafe = (Unsafe) unsafeField.get(null);
    while(true) {
        unsafe.allocateMemory(_1MB);
    }
}
  • 运行时常量池溢出,如果要想运行时常量池添加内容,最简单的方式是使用String,inter()这个Native方法。该方法的作用是:如果常量池中已经包括一个等于此String对象的字符串,则返回代表池中的这个字符串的String对象;否则,将此对象包含的字符串增加到常量池中。
1
2
3
4
5
6
7
8
@Test
public void testRuningConstantOutOfMemoryError() {
    List list = new ArrayList<>();
    int i = 0;
    while(true) {
        list.add(String.valueOf(i++).intern());
    }
}