美文网首页
深入理解JVM之OOM

深入理解JVM之OOM

作者: 田真的架构人生 | 来源:发表于2017-08-03 09:29 被阅读0次

    做Java程序开发,难免会遇到OutOfMemory,导致的原因也是不尽相同,下面我们来捋一捋OOM出现的场景。
    一,堆空间不足,这是最容易,也是最常见的OOM。了解Java内存结构的同学应该知道,Java里面创建的对象大部分都是位于堆空间的,当创建的对象太多,而堆空间不足时,很容易抛出OOM,如下:

    import java.util.ArrayList;
    import java.util.List;
    
    /*VM args:-Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError
     * 
     * -Xms:堆的最小值;-Xmx:堆的最大值
     * 
     * */
    public class HeapOOM {
    
        static class OOMObject {
        }
    
        public static void main(String[] args) {
            List list = new ArrayList();
            while (true) {
                list.add(new OOMObject());
            }
        }
    }
    

    运行结果:

    Exception in thread \"main\" java.lang.OutOfMemoryError: Java heap space
        at java.util.Arrays.copyOf(Arrays.java:2760)
        at java.util.Arrays.copyOf(Arrays.java:2734)
        at java.util.ArrayList.ensureCapacity(ArrayList.java:167)
        at java.util.ArrayList.add(ArrayList.java:351)
        at test.java.VM.OOM.HeapOOM.main(HeapOOM.java:19)
    

    二,直接分配内存溢出,Java提供了一些可以直接操作内存和线程的低层次操作(native)-sun.misc.Unsafe,其被JDK广泛用于自己的包中,如java.nio和java.util.concurrent。
    但是丝毫不建议在生产环境中使用这个Unsafe,从名字就可以看出,这个API十分不安全、不轻便、而且不稳定。

    import java.lang.reflect.Field;
    
    import sun.misc.Unsafe;
    
    /*VM args:-Xmx10m -XX:MaxDirectMemorySize=5m
     * 
     * */
    public class DirectMemoryOOM {
        private static final int _1MB = 1024 * 1024;
    
        public static void main(String[] args) throws Exception {
            Field unsafeField = Unsafe.class.getDeclaredField(\"theUnsafe\");
            unsafeField.setAccessible(true);
            Unsafe unsafe = (Unsafe) unsafeField.get(null);
    
            while (true) {
                unsafe.allocateMemory(_1MB);
            }
        }
    }
    

    运行结果:

    Exception in thread \"main\" java.lang.OutOfMemoryError
        at sun.misc.Unsafe.allocateMemory(Native Method)
        at test.java.VM.OOM.DirectMemoryOOM.main(DirectMemoryOOM.java:19)
    

    三,方法区溢出,方法区主要存放类的信息、静态变量、Field、Method信息等,当不停地有类动态创建并加载时,方法区也能产生OOM。

    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=5m -XX:MaxPermSize=5m
     * 
     * */
    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 {
                        //return method.invoke(obj, args);
                        return proxy.invokeSuper(obj, args);
                    }
                });
                OOMObject object = (OOMObject) enhancer.create();
            }
        }
    
        static class OOMObject {
        }
    }
    

    运行结果:

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

    四,常量池溢出,常量池属于方法区,存放一些常量值(如static final),还有一些文本形式出现的符号引用,如:类和接口的全限定名、字段的名称和描述符。

    import java.util.ArrayList;
    import java.util.List;
    
    /*VM rags:-XX:PermSize=5m -XX:MaxPermSize=5m
     * 
     * 
     * */
    public class RuntimeConstantPoolOOM {
        public static void main(String[] args) {
            //使用List保持着常量池引用,避免Full GC回收常量池行为
            List list = new ArrayList();
    
            int i = 0;
            while (true) {
                list.add(String.valueOf(i++).intern());//将String对象加入常量池
            }
        }
    }
    

    运行结果:

    Exception in thread \"main\" java.lang.OutOfMemoryError: PermGen space
        at java.lang.String.intern(Native Method)
        at test.java.VM.OOM.RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java:17)
    Exception in thread \"Reference Handler\" java.lang.OutOfMemoryError: PermGen space
        at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:123)
    

    五,Stack溢出,JVM方法栈为线程私有,当方法执行时会被创建,当方法执行完毕,其对应的栈帧所占用的内存也会被自动释放。
    当方法栈的深度不足时,会抛出StackOverflowError,不过只要不出现无穷递归,栈的深度不会太大。

    /*VM args:-Xss128k
     * 
     * 在单线程下,无论是由于栈帧太大,还是虚拟机容量太小,
     * 当内存无法分配的时候,虚拟机抛出的都是StackOverflowError
     * 如果测试不限于单线程,通过不断创建线程的方式倒是可以产生内存溢出异常(详见JavaVMStackOF2)
     * */
    public class JavaVMStackOF {
        private int stackLength = 1;
    
        public void stackLeak() {
            stackLength++;//其实,即使没有操作数,也会throw StackOverflowError
            stackLeak();
        }
    
        public static void main(String[] args) throws Throwable {
            JavaVMStackOF oom = new JavaVMStackOF();
            try {
                oom.stackLeak();
            } catch (Throwable e) {
                System.out.println(\"stack length:\" + oom.stackLength);
                throw e;
            }
        }
    }
    

    运行结果:

    stack length:1007Exception in thread \"main\" java.lang.StackOverflowError
    
        at test.java.VM.OOM.JavaVMStackOF.stackLeak(JavaVMStackOF.java:13)
        at test.java.VM.OOM.JavaVMStackOF.stackLeak(JavaVMStackOF.java:14)
    

    补充:

    /*VM args:-Xss10m
     * 
     * 通过不断创建新线程达到OutOfMemoryError
     * 物理内存-Xmx(最大堆容量)-MaxPermSize(最大方法区容量)=虚拟机栈+本地方法栈,程序计数器消耗内存较小,可以忽略。
     * -Xss10m,分配给每个线程的内存。所以-Xss越大,越容易出现OutOfMemoryError(可创建的线程越少)。
     * 
     * 栈深度在虚拟机默认情况下,达到1000~2000完全没问题,对于正常的方法调用(包括递归),这个深度完全够用了。
     * 但是,如果是建立过多线程导致 的内存溢出,在不能减少线程数或者更换64位虚拟机的情况下,就只能通过减少最大堆容量和减少栈容量来换取更多的线程。
     * 如果没有这方面的经验,这种通过“减少内存”的手段来解决内存溢出的方式会比较难以想到!
     * */
    

    相关文章

      网友评论

          本文标题:深入理解JVM之OOM

          本文链接:https://www.haomeiwen.com/subject/zxbulxtx.html