内存溢出复现

2020-10-29

开发与生产环境经常会遇到内存溢出的错误感觉都差不多,其实 JVM 每个内存区域发生 OOM 的原因还是差异的。
JVM 的内存区域中,一般只有程序计数器这个区域不会发生内存溢出,虚拟机栈、本地方法栈、堆、方法区、直接内存都会发生 OOM。

结合《深入理解Java虚拟机》里的示例,我在本地复现了各种 OOM。

 

操作系统:win 7
JDK:7 和 8 都有,都是 64 位,版本号分别为 1.7.3、1.8.141
默认 JAVA_HOME 是 JDK 1.8.141

 

1、虚拟机栈溢出 StackOverflowError

原因:方法递归层级太深

代码:

public class JavaVMStackSOF {

    private int stackLength = 1;

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

    public static void main(String[] args) throws Throwable {
        //递归调用 stackLeak 方法
        JavaVMStackSOF oom = new JavaVMStackSOF();
        try {
            oom.stackLeak();
        } catch (Throwable e) {
            System.out.println("stack length:" + oom.stackLength);
            throw e;
        }
    }
}

-Xss128k 配置栈容量,指令:

javac JavaVMStackSOF.java
java -Xss128k JavaVMStackSOF

异常信息:

stack length:991
Exception in thread "main" java.lang.StackOverflowError
    at constxiong.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:8)
    at constxiong.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:9)
    ...

 

 

2、虚拟机栈溢出 StackOverflowError

原因:方法递归层级太深的基础上,加大栈帧中的局部变量

代码:

public class JavaVMStackSOF2 {

    private static int stackLength = 0;

    public static void test() {
        long unused1, unused2, unused3, unused4, unused5,
        unused6, unused7, unused8, unused9, unused10,
        unused11, unused12, unused13, unused14, unused15,
        unused16, unused17, unused18, unused19, unused20,
        unused21, unused22, unused23, unused24, unused25,
        unused26, unused27, unused28, unused29, unused30,
        unused31, unused32, unused33, unused34, unused35,
        unused36, unused37, unused38, unused39, unused40,
        unused41, unused42, unused43, unused44, unused45,
        unused46, unused47, unused48, unused49, unused50,
        unused51, unused52, unused53, unused54, unused55,
        unused56, unused57, unused58, unused59, unused60,
        unused61, unused62, unused63, unused64, unused65,
        unused66, unused67, unused68, unused69, unused70,
        unused71, unused72, unused73, unused74, unused75,
        unused76, unused77, unused78, unused79, unused80,
        unused81, unused82, unused83, unused84, unused85,
        unused86, unused87, unused88, unused89, unused90,
        unused91, unused92, unused93, unused94, unused95,
        unused96, unused97, unused98, unused99, unused100;

        stackLength ++;
        test();

        unused1 = unused2 = unused3 = unused4 = unused5 =
        unused6 = unused7 = unused8 = unused9 = unused10 =
        unused11 = unused12 = unused13 = unused14 = unused15 =
        unused16 = unused17 = unused18 = unused19 = unused20 =
        unused21 = unused22 = unused23 = unused24 = unused25 =
        unused26 = unused27 = unused28 = unused29 = unused30 =
        unused31 = unused32 = unused33 = unused34 = unused35 =
        unused36 = unused37 = unused38 = unused39 = unused40 =
        unused41 = unused42 = unused43 = unused44 = unused45 =
        unused46 = unused47 = unused48 = unused49 = unused50 =
        unused51 = unused52 = unused53 = unused54 = unused55 =
        unused56 = unused57 = unused58 = unused59 = unused60 =
        unused61 = unused62 = unused63 = unused64 = unused65 =
        unused66 = unused67 = unused68 = unused69 = unused70 =
        unused71 = unused72 = unused73 = unused74 = unused75 =
        unused76 = unused77 = unused78 = unused79 = unused80 =
        unused81 = unused82 = unused83 = unused84 = unused85 =
        unused86 = unused87 = unused88 = unused89 = unused90 =
        unused91 = unused92 = unused93 = unused94 = unused95 =
        unused96 = unused97 = unused98 = unused99 = unused100 = 0;
    }

    public static void main(String[] args) {
        try {
            test();
        }catch (Error e){
            System.out.println("stack length:" + stackLength);
            throw e;
        }
    }
}

-Xss128k 配置栈容量,指令:

javac JavaVMStackSOF2.java
java -Xss128k JavaVMStackSOF2

异常信息:

Exception in thread "main" java.lang.StackOverflowError
    at constxiong.JavaVMStackSOF2.test(JavaVMStackSOF2.java:29)
    at constxiong.JavaVMStackSOF2.test(JavaVMStackSOF2.java:30)
    ...
    at constxiong.JavaVMStackSOF2.test(JavaVMStackSOF2.java:30)
    at constxiong.JavaVMStackSOF2.main(JavaVMStackSOF2.java:56)
stack length:51

很明显能看出,加多了局部变量,调用深度从示例 1 的 991 -> 本例中的 51

 

3、栈 OOM

复现条件需要借助限制单进程内存大小的操作系统,如 32 位 win7,本地无此环境。执行代码的情况是喜欢创建线程,电脑卡顿,但未出现 OOM。

进程内存 - 堆内存 - 方法区 - 程序计数器 - 直接内存 - JVM 执行本身的内存 = 虚拟机栈 + 本地方法栈

代码:

public class JavaVMStackOOM {

    private 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) throws Throwable {
        JavaVMStackOOM oom = new JavaVMStackOOM();
        oom.stackLeakByThread();
    }
}

指令:

javac JavaJVMStackOOM.java
java -Xss2M JavaVMStackOOM

 

4、堆 OOM

原因:设置堆的最大内存,不断创建强引用对象

代码:

import java.util.*;

public class HeapOOM {

    static class OOMObject {}

    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<OOMObject>();
        while (true) {
            list.add(new OOMObject());
        }
    }
}

-Xms20m -Xmx20m 设置堆的最小和最大内存,指令:

javac JavaJVMStackOOM.java
java -Xss2M JavaVMStackOOM

异常信息:

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:10)


5、常量池 OOM

ConstXiong 备案号:苏ICP备16009629号-3