ITPub博客

深入理解Java虚拟机笔记1: OOM实战

原创 作者:zhuyiquan90 时间:2018-06-29 09:26:25 0 删除 编辑

通过代码模拟Java虚拟机规范中描述的各个运行时区域内存溢出的场景。



1. Java堆溢出

首先,虚拟机启动参数配置如下:

-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
  • 1

其中,-verbose:gc表示输出虚拟机中GC的详细情况。-Xms20M -Xmx20M最小堆内存20M,最大堆内存20M。-Xmn10M设置新生代的容量10M。-XX:+PrintGCDetails打印GC详细信息,-XX:SurvivorRatio=8年轻代中Eden区与Survivor区的大小比值。

import java.util.ArrayList; import java.util.List; /**
 * 
 * 
 * VM Args:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
 * @author 作者 zhuyiquan90
 * @created 2017-9-1 上午11:46:34
 * @version 1.0.0
 * @date 2017-9-1 上午11:46:34
 */ public class HeapOOM { static class OOMObject {

    } public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<OOMObject>(); while (true) {
            list.add(new 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

输出:

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

-Xms20M -Xmx20M导致Java堆内存20M不可扩展,通过-XX:+HeapDumpOnOutOfMemoryError可以让虚拟机在内存溢出异常时DUMP内存快照供分析。

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

  • 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常;
  • 如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。

2.1 StackOverflowError

虚拟机参数增加-Xss128k,每个线程栈大小128k。栈深度在大多数情况下(压入栈的帧大小不同)达到1000-2000是没有问题的,正常的方法调用和递归,这个深度是够用的。

/**
 * 
 * 
 * 功能描述: 模拟StackOverflowError
 * VM Args: -Xss128k
 * @author 作者 zhuyiquan90
 * @created 2017-9-1 上午11:04:01
 * @version 1.0.0
 * @date 2017-9-1 上午11:04:01
 */ 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 (Exception e) {
            System.out.println("stack length" + oom.stackLength); throw e;
        }
    }

}
  • 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

虚拟机抛出StackOverflowError异常,输出:

Exception in thread "main" java.lang.StackOverflowError
    at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:7)
    at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:8) ...
  • 1
  • 2
  • 3
  • 4

2.2 栈OutOfMemoryError

需要注意,为每个线程的栈分配的内存越大,反而越容易产生栈内存溢出异常。 
这个不难理解,每个线程分配到栈容量越大,可以建立的线程数量自然就越少,建立线程时就越容易把剩下的内存耗尽。 
因此,可以通过“减少内存”的手段来解决栈内存溢出问题。

/**
 * 
 * 
 * 功能描述: 栈OutOfMemoryError 
 * VM Args:-Xss2M 调大单线程可使用栈空间大小
 * @author zhuyiquan90
 * @created 2017-9-1 上午11:20:06
 * @version 1.0.0
 * @date 2017-9-1 上午11:20:06
 */ 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

Windows平台由于Java的线程是映射到操作系统的内核线程,因此上述代码可能会导致操作系统假死。

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

3.1 运行时常量池溢出

JDK 1.7开始逐步“去永久代化”。在JDK 1.6及之前的版本,由于常量池分配在永久代,可以通过-XX:PermSize和-XX:MaxPermSize限制方法区大小,从而间接限制常量池容量。

import java.util.ArrayList; import java.util.List; /**
 * 
 * 
 * 功能描述: 运行时常量池内存溢出
 * VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M
 * @author 作者 zhuyiquan90
 * @created 2017-9-1 上午11:50:48
 * @version 1.0.0
 * @date 2017-9-1 上午11:50:48
 */ 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());
        }
    }

}
  • 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.6以下,运行结果:

Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
    at java.lang.String.intern(Native Method)
    at RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java:22)
  • 1
  • 2
  • 3

在JDK 1.7及以上,while循环将一直进行下去。

3.2 方法区溢出

方法区用于存放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; /**
 * 
 * 
 * 功能描述: 方法区内存溢出
 * VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M
 * @author 作者 zhuyiquan90
 * @created 2017-9-1 下午3:31:27
 * @version 1.0.0
 * @date 2017-9-1 下午3:31:27
 */ 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

运行结果:

Exception in thread "main" net.sf.cglib.core.CodeGenerationException: java.lang.reflect.InvocationTargetException-->null
    at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:237)
    at net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:377)
    at net.sf.cglib.proxy.Enhancer.create(Enhancer.java:285)
    at com.suning.JavaMethodAreaOOM.main(JavaMethodAreaOOM.java:35) Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "main"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

4. 本机直接内存溢出

DirectMemory容量可通过-XX:MaxDirectMemorySize指定,如果不指定,则默认与Java堆最大值(-Xmx指定)一样。

import java.lang.reflect.Field; import sun.misc.Unsafe; /**
 * 
 * 
 * 功能描述: 本机直接内存溢出
 * VM Args: -Xmx20M -XX:MaxDirectMemorySize=10M
 * @author 作者 zhuyiquan90
 * @created 2017-9-1 下午3:43:58
 * @version 1.0.0
 * @date 2017-9-1 下午3:43:58
 */ public class DirectMemoryOOM { private static final int _1MB = 1024 * 1024; public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException {
        Field unsafeField = Unsafe.class.getDeclaredFields()[0];
        unsafeField.setAccessible(true);
        Unsafe unsafe = (Unsafe) unsafeField.get(null); while(true) {
            unsafe.allocateMemory(_1MB);
        }
    }
}
  • 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

运行结果:

Exception in thread "main" java.lang.OutOfMemoryError at sun.misc.Unsafe.allocateMemory(Native Method)
    at DirectMemoryOOM.main(DirectMemoryOOM.java:14)
  • 1
  • 2
  • 3

需要注意,由DirectMemory导致的内存溢出,在Heap Dump文件中不会看见异常(Java NIO中是在和DirectMemory交互)。

小结

Java的OOM包括了:

  1. Java堆溢出
  2. 虚拟机栈和本地方法栈溢出
  3. 方法区和运行时常量池溢出
  4. 本机直接内存溢出
下一篇: 没有了~
请登录后发表评论 登录
全部评论

注册时间:2018-06-07

  • 博文量
    13
  • 访问量
    2396