import java.util.ArrayList;
import java.util.List;
/**
* VM Args:-Xms20M -Xmx20m -Xmn10M -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails
* -verbose:gc
* @author chen
*
*/
public class OutOfMemoryInHeap {
static class OOMObject{
}
public static void main(String[] args){
//使用List来保存循环创建的对象,使对象不会被GC回收
List list = new ArrayList();
while(true){
list.add(new OOMObject());
}
}
}
错误信息:
当Java堆中无法再为新创建的对象分配内存时,就会抛出OOM异常,之后的堆内存信息也体现了这一点。
排查堆内存异常的思路:1、判断是内存泄漏还是溢出。2、如果是泄露,进一步查看泄露的内存到GC ROOTS的引用链。3、如果没有泄露,就需要尝试是否能调整-Xms和-Xmx的值将堆内存调大,或者查看代码,是否可以让一些对象不存活那么久。
/**
* VM Args:-Xss128k(指定栈深度)
* @author chen
*
*/
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代表递归次数,也可以用来表示栈的深度
在栈中要出现OOM异常,应该是在多线程场景下,每个线程都会有私有的栈空间,所以有可能你设置的栈大小越大,就会导致在多线程场景中,就越可能出现OOM异常,因为每个线程都会被分配一块栈内存,如果虚拟机内存无法满足分配了,就会出现OOM。所以在建立线程过多发生OOM异常的时候,有两种解决办法是:1、缩小最大堆容量 2、缩小栈容量,这种用缩小来解决OOM的方法不常见。
代码:不断创建线程导致OOM
/**
*
* VM Args:-Xss2M(设置更大的栈容量,使多线程时更易发生OOM)
* @author chen
*
*/
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();
}
}
错误信息:(我没有截取到,等了十分钟,仍然没有报错...但是系统会变的很卡,有要尝试的可以把栈容量继续调大。注意在Windows系统会有系统卡顿或卡死情况) 如果报错应该是
import java.util.ArrayList;
import java.util.List;
/**
* VM Args: -Xmx20M
* -XX:-UseGCOverheadLimit(这个参数是为了让虚拟机自行溢出,而不是通过GC时间来预判溢出并抛出异常)
* 1.8之前设定常量池可以通过-XX:PermSize 和 -XX:MaxPermSize来设置。
* @author chen
*
*/
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());
}
}
}
错误信息:
可以看到发生OOM异常的区域,还是在Java Heap space中。
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
/**
* VM Args:-XX:MetaspaceSize=10M -XX:MaxMetaspaceSize=10M
* @author chen
*
*/
public class JavaMethodAreaOOM {
public static void main(String[] args) {
while(true) {
//使用cglib中的Enhancer对类进行增强,这样就会把大量的动态类信息放入方法区
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{
}
}
可以看出是Metaspace区域溢出,这是JDK1.8中加入的元数据区域,与堆独立。是JDK8中新规定的永久代区域。
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
/**
* VM Args:-Xmx20M -XX:MaxDirectMemorySize=10M
* @author chen
*
*/
public class DirectMemoryOOM {
private static final int _1MB = 1024*1024;
public static void main(String[] args) {
List list = new ArrayList();
while(true){
//list存放,防止被回收并重新分配
list.add(ByteBuffer.allocateDirect(_1MB));
}
}
}
错误信息:
第二章学习了JVM运行时内存区域的分布,和内存区域各自的作用。并且在之后对每个内存区域会发生的异常情况都做了代码实践,溢出的原因大致是:
Java堆:有太多无用的类但是都有到GC Roots的引用链无法被回收,抛出OOM;常量池被设置在堆中,有太多正在使用的常量(无法被回收)超出内存限制,也会在堆抛出OOM。
Java虚拟机栈和本地栈:不断递归的方法使栈帧超出栈深度或者不断定义本地变量使本地变量表超出栈限制,并且在规定了栈的大小之后,抛出SOF。不断创建线程并分配相应的栈大小,会导致栈内存分配超出本机内存限制,导致OOM。
方法区:有很多被增强的类正在使用,方法区会保存这些动态类的信息,导致Metaspace(新的方法区)抛出OOM。
直接内存:通过NIO的类去申请堆外直接内存空间,并且保存引用链,在申请前计算出无法有足够的空间分配所需申请的空间时,抛出OOM异常。
程序计数器:没有规定异常,所占空间也非常小,可以忽略。