通过代码模拟Java虚拟机规范中描述的各个运行时区域内存溢出的场景。
首先,虚拟机启动参数配置如下:
-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
1
输出:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:2245)
at java.util.Arrays.copyOf(Arrays.java:2219)
at java.util.ArrayList.grow(ArrayList.java:213)
at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:187)
at java.util.ArrayList.add(ArrayList.java:411)
at HeapOOM.main(HeapOOM.java:15)
Heap
def new generation total 9216K, used 8920K [0x32570000, 0x32f70000, 0x32f70000)
eden space 8192K, 100% used [0x32570000, 0x32d70000, 0x32d70000)
from space 1024K, 71% used [0x32d70000, 0x32e26040, 0x32e70000)
to space 1024K, 0% used [0x32e70000, 0x32e70000, 0x32f70000)
tenured generation total 10240K, used 5693K [0x32f70000, 0x33970000, 0x33970000)
the space 10240K, 55% used [0x32f70000, 0x334ff7f8, 0x334ff800, 0x33970000)
compacting perm gen total 12288K, used 135K [0x33970000, 0x34570000, 0x37970000)
the space 12288K, 1% used [0x33970000, 0x33991dd8, 0x33991e00, 0x34570000)
ro space 10240K, 45% used [0x37970000, 0x37df1888, 0x37df1a00, 0x38370000)
rw space 12288K, 54% used [0x38370000, 0x389f04f8, 0x389f0600, 0x38f70000)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

2. 虚拟机栈和本地方法栈溢出

2.1 StackOverflowError
虚拟机抛出StackOverflowError异常,输出:Exception in thread "main" java.lang.StackOverflowError
at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:7)
at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:8) ...
1
2
3
4
需要注意,为每个线程的栈分配的内存越大,反而越容易产生栈内存溢出异常。
这个不难理解,每个线程分配到栈容量越大,可以建立的线程数量自然就越少,建立线程时就越容易把剩下的内存耗尽。
因此,可以通过
“减少内存”
的手段来解决栈内存溢出问题。
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) {
JavaVMStackOOM oom = new JavaVMStackOOM();
oom.stackLeakByThread();
}
}
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

3. 方法区和运行时常量池溢出
JDK 1.7开始逐步“去永久代化”。在JDK 1.6及之前的版本,由于常量池分配在永久代,可以通过-XX:PermSize和-XX:MaxPermSize限制方法区大小,从而间接限制常量池容量。
import java.util.ArrayList; import java.util.List; public class RuntimeConstantPoolOOM { public static void main(String[] args) { // 使用List保持常量池引用,避免Full GC回收常量池行为 List list = new ArrayList(); // 10MB的PermSize在integer范围内足够产生OOM int i =0; while(true) {
list.add(String.valueOf(i++).intern());
}
}
}
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
在JDK 1.7及以上,while循环将一直进行下去。
方法区用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。对于这些区域的测试,基本的思想是运行时产生大量的类去填满方法去,直到溢出。
package com.suning; import java.lang.reflect.Method; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; public class JavaMethodAreaOOM { public static void main(String[] args) { while (true) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OOMObject.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { // TODO Auto-generated method stub return proxy.invokeSuper(obj, args);
}
});
enhancer.create();
}
} static class OOMObject {
}
}
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

4. 本机直接内存溢出
运行结果:
Exception in thread "main" java.lang.OutOfMemoryError at sun.misc.Unsafe.allocateMemory(Native Method)
at DirectMemoryOOM.main(DirectMemoryOOM.java:14)
1
2
3

小结
Java堆溢出
虚拟机栈和本地方法栈溢出
方法区和运行时常量池溢出
本机直接内存溢出