本文通过几个实例来验证JVM运行时数据区发生OutOfMemoryError(OOM)异常的场景,顺便介绍几个内存相关的基本的虚拟机参数。
1.堆内存溢出
1.1.相关参数
- -Xms 堆的最小值
- -Xmx 堆的最大值(Xms和Xmx设置一致,可以避免堆自动扩展)
- -Xmn 堆中新生代大小
- -XX:+HeapDumpOnOutOfMemoryError 可以在内存溢出时,dump出当前内存堆转储为快照文件以供事后进行分析
- -XX:+PrintGCDetails 在启动脚本可以自动开启-XX:+PrintGC , 如果在命令行使用jinfo开启的话,不会自动开启-XX:+PrintGC
- -XX:SurvivorRatio 设置Eden和Survivor区域大小比例,如果设置为8,则Eden和两个Survivor区比例为8:1:1
1.2.测试代码
package com.glt.oom;
import java.util.ArrayList;
import java.util.List;
/**
* JVM args:
* -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+HeapDumpOnOutOfMemoryError
*/
public class HeapOOM {
static class OOMObject{
}
public static void main(String[] args) {
List<OOMObject> list = new ArrayList<OOMObject>();
while (true){
list.add(new OOMObject());
}
}
}
输入如下:
2.虚拟机栈和本地方法栈溢出
栈溢出存在两种情况:
- SOF异常(StackOverflowError)
线程请求的栈深度大于虚拟机所允许的最大深度,则抛出此异常 - OOM异常(OutOfMemoryError)
虚拟机在扩展栈时无法申请到足够的内存空间,则抛出此异常
2.1.相关参数
- -Xss 栈容量
2.2.测试栈SOF异常
package com.glt.oom;
/**
* JVM args:
* -Xss128k -XX:+PrintGCDetails
*/
public class StackSOF {
private int stackLen = 1;
public void stackLeak() {
stackLen++;
stackLeak();
}
public static void main(String[] args) throws Exception{
StackSOF stackSOF = new StackSOF();
try {
stackSOF.stackLeak();
} catch (Exception e) {
System.out.println("stack 深度为:" + stackSOF.stackLen);
e.printStackTrace();
throw e;
}
}
}
输出如下:
2.3.测试栈OOM异常
此处会造成windows系统假死,请谨慎操作!!!
package com.glt.oom;
/**
* JVM args:
* -Xss2M -XX:+PrintGCDetails
*/
public class StackOOM {
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) {
StackOOM stackOOM = new StackOOM();
stackOOM.stackLeakByThread();
}
}
3.方法区溢出
3.1.相关参数
- -XX:PermSize 非堆(方法区)区域初始内存大小
- -XX:MaxPermSize 非堆(方法区)区域分配的最大内存
3.2.测试方法区内存溢出(OOM)
方法区用于存放class类信息,如类名,访问修饰符,常量池,字段描述,方法描述等,测试溢出需要使用大量类填满方法区,此处使用cgLib动态创建大量类测试。
package com.glt.oom;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* JVM args
* -XX:PermSize=10M -XX:MaxPermSize=10M -XX:+PrintGCDetails
*/
public class MethodAreaOOM {
public static void main(String[] args) {
while (true) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(ObjectOOM.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
return methodProxy.invokeSuper(o, objects);
}
});
enhancer.create();
}
}
static class ObjectOOM {
}
}
输出如下:
3.3.测试运行时常量池溢出(OOM)
通过限制方法区大小来限制运行时常量池大小
package com.glt.oom;
import java.util.ArrayList;
import java.util.List;
/**
* JVM args:
* -XX:PermSize=10M -XX:MaxPermSize=10M -XX:+PrintGCDetails
*/
public class RuntimeConstPoolOOM {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
int i = 0;
while (true) {
list.add(String.valueOf(i++).intern());
}
}
}
输出如下:
4.直接内存溢出
4.1.相关参数
- -XX:MaxDirectMemorySize 来指定最大直接内存
4.2.测试直接内存溢出(OOM)
直接内存不是运行时数据区部分,但这部分内存也被频繁的使用,而且也会导致OutOfMemoryError异常的出现。
package com.glt.oom;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
/**
* JVM args
* -Xmx20M -XX:MaxDirectMemorySize=10M -XX:+PrintGCDetails
*/
public class DirectMemoryOOM {
private static final int _1M = 1024 * 1024;
public static void main(String[] args) throws Exception {
Field unsafeField = Unsafe.class.getDeclaredFields()[0];
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);
while (true) {
unsafe.allocateMemory(_1M);
}
}
}
输出如下: