OutOfMemoryError示例

2016年07月23日

一、说明

内存区域 共享/隔离 存放数据 异常情况 JVM参数
Java虚拟机栈 线程隔离 编译期可知的各种基本类型、对象引用 StackOverflowError、
OutOfMemoryError
-Xss
Java堆 线程共享 对象实例+数组 OutOfMemoryError -Xmx、-Xms
方法区 线程共享 已经被虚拟机加载的类信息、运行时常量等 OutOfMemoryError -XX:MaxPermSize ,
-XX:PermSize

二、示例

1. Java堆溢出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 代码
public class HeapOOM {
    static class OOMObject {

    }

    // JVM Args : -Xms100M -Xmx100M -XX:+HeapDumpOnOutOfMemoryError
    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<OOMObject>();
        while (true) {
            list.add(new OOMObject());
        }
    }
}

# 运行结果
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

1.1 通过MAT工具分析

Class Name Objects Shallow Heap
org.zhongmingmao.HeapOOM$OOMObject 3,392,918 54,286,688
java.lang.Object[] 1 13,571,688
Total: 2 entries 3,392,919 67,858,376

2. Java虚拟机栈溢出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 代码
public class JavaVMStackSOF {
    private int stackDepth = 1;

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

    @Override
    public String toString() {
        return "JavaVMStackSOF{" +
                "stackDepth=" + stackDepth +
                '}';
    }

    // JVM Args : -Xss160k
    public static void main(String[] args) throws Throwable {
        JavaVMStackSOF sof = new JavaVMStackSOF();
        try {
            sof.stackLeak();
        } catch (Throwable e) {
            System.out.println(e);
            throw e;
        }
    }
}

# 运行结果
JavaVMStackSOF{stackDepth=750}
Exception in thread "main" java.lang.StackOverflowError

3. 方法区溢出

3.1 String.intern()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// JRE 6 
1. 把首次遇到的字符串实例复制到永久代(JDK6,常量池分配在永久代中)中,返回的也是永久代中这个字符串实例的引用,
2. StringBuilder创建的字符串实例在Java堆上,所以永远是false
public static void main(String[] args) {
    String str1 = new StringBuilder("计算机").append("软件").toString();
    System.out.println(str1.intern() == str1); // false

    String str2 = new StringBuilder("ja").append("va").toString();
    System.out.println(str2.intern() == str2); // false
}

// JRE 7
1. 首次遇到字符时,不会再复制实例,只是在常量池中记录首次出现的实例引用(在堆上),所以str1.intern() == str1
2. "java"这个字符串的引用早就在字符串常量池中,不是首次出现,所以返回false
public static void main(String[] args) {
    String str1 = new StringBuilder("计算机").append("软件").toString();
    System.out.println(str1.intern() == str1); // true

    String str2 = new StringBuilder("ja").append("va").toString();
    System.out.println(str2.intern() == str2); // false
}
3.2 运行时常量池溢出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# 1.1 代码
# =====
# JDK6:首次字符,拷贝到永久代中常量池(影响区域:堆+方法区)
# =====
public class RuntimeConstantPoolOOM {
    public static void main(String[] args) throws Throwable {
        // JRE 6
        // JVM Args : -Xms30m -Xmx30m -XX:PermSize=10m -XX:MaxPermSize=10m
        List<String> list = new ArrayList<String>();
        int i = 0;
        try {
            while (true) {
                list.add(String.valueOf(++i).intern());
            }
        } catch (Throwable e) {
            System.out.println(i);
            throw e;
        }
    }
}
# 1.2 运行结果
86761
Exception in thread "main" java.lang.OutOfMemoryError: PermGen space



# 2.1 代码
# =====
# JDK6:首次字符,不拷贝,仅标记(影响区域:堆)
# =====
public class RuntimeConstantPoolOOM {
    public static void main(String[] args) throws Throwable {
        // JRE 7
        // JVM Args : -Xms30m -Xmx30m -XX:PermSize=10m -XX:MaxPermSize=10m
        List<String> list = new ArrayList<String>();
        int i = 0;
        try {
            while (true) {
                list.add(String.valueOf(++i).intern());
            }
        } catch (Throwable e) {
            System.out.println(i);
            throw e;
        }
    }
}

# 2.2 运行结果
473916
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
3.3 方法区(类加载)溢出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 代码
public class JavaMethodAreaOOM {
    static class OOMObject {

    }

    // JVM Args : -XX:PermSize=100m -XX:MaxPermSize=100m
    public static void main(String[] args) throws Throwable {
        try {
            // 等待Java VisualVm连接
            Thread.sleep(15 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        int loadedClazzs = 0;
        try {
            while (true) {
                Enhancer enhancer = new Enhancer();
                enhancer.setSuperclass(OOMObject.class);
                enhancer.setUseCache(false);
                enhancer.setCallback(new MethodInterceptor() {
                    @Override
                    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                        return methodProxy.invokeSuper(o, objects);
                    }
                });
                enhancer.create();
                ++loadedClazzs;
            }
        } catch (Throwable e) {
            System.out.println(loadedClazzs);
            throw e;
        }
    }
}

# 运行结果
9098
Exception in thread "main" net.sf.cglib.core.CodeGenerationException: 
  ... more
Caused by: java.lang.OutOfMemoryError: PermGen space

图片失效

三、参考资料