《深入理解Java虚拟机》笔记-实战:OutOfMemoryError异常

时间:2022-12-27 23:20:16

通过配置Jvm参数结合代码重现OutOfMemoryError异常

1. Java堆溢出

Java虚拟机参数:-Xms20m -Xmx20m -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());
}
}
}
 

运行结果:

java.lang.OutOfMemoryError: Java heap space

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

Java虚拟机参数:-Xss128k
public class JavaVMStackSOF {
private int stackLength = 1;
public void stackLeak() {
stackLength++;
stackLeak();
}
public static void main(String[] args) throws Throwable {
JavaVMStackSOF oom = new JavaVMStackSOF();
try {
oom.stackLeak();
} catch (Throwable e) {
System.out.println("stack length:" + oom.stackLength);
throw e;
}
}

}

运行结果:

stack length:2402

Exception in thread "main" java.lang.*Error

然而在单线程下,使用-Xss参数减少栈内存容量和定义大量本地变量,都抛出*Error

在多线程下,通过不断创建线程,使内存分配耗尽,抛出OutOfMemoryError,代码如下(谨慎使用):

Java虚拟机参数:-Xss2M (这时候不妨设大些)
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();
       }

}

3. 方法区和运行时常量池溢出

String.intern()是一个Native方法,作用是:如果字符串常量池中已经包含一个等于此String对象的字符串,则返回代表池中这个字符串的String对象;否则,将此对象包含的字符串添加到常量池,并返回此String对象的引用。

Java虚拟机参数:-XX:PermSize=10M -XX:MaxPermSize=10M
public class RuntimeConstantPoolOOM {
public static void main(String[] args) {
// 使用List保持着常量池引用,避免Full GC回收常量池行为
List<String> list = new ArrayList<String>();
// 10MB的PermSize在integer范围内足够产生OOM了
int i = 0; 
while (true) {
list.add(String.valueOf(i++).intern());
}
}

}

运行结果:

Exception in thread "main" java.lang.OutOfMemoryError:PermGen space

方法区用于存放Class的相关信息,比如类名、访问修饰符、常量池、字段描述、方法描述等。

对于这些区域的测试,基本思路是运行时产生大量的类去填满方法区,直到溢出。下面的代码利用GCLib直接操作字节码在运行时生成了大量的动态类。

Java虚拟机参数: -XX:PermSize=10M -XX:MaxPermSize=10M
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() {
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
return proxy.invokeSuper(obj, args);
}
});
enhancer.create();
}
}
static class OOMObject {
}

}

运行结果:

Exception in thread "main" java.lang.OutOfMemoryError:PermGen space

4. 直接内存溢出

DirectMemory容量通过-XX:MaxDirectMemorySize指定,如果不指定,则默认与Java堆最大值(-Xmx指定)一样。下面的代码中,真正申请分配内存的方法是unsafe.allocateMemory()

 Java虚拟机参数:-Xmx20M -XX:MaxDirectMemorySize=10M
public class DirectMemoryOOM {
private static final int _1MB = 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(_1MB);
}
}

}

运行结果:

Exception in thread "main" java.lang.OutOfMemoryError