美文网首页知识
Java内存溢出情况

Java内存溢出情况

作者: 云飞扬1 | 来源:发表于2017-10-15 22:40 被阅读27次

    在Java运行时数据区中,除了Program Counter Register(程序计数器)之外,其他几个数据区中均可能发生OutOfMemoryError,俗称OOM。

    1.Java堆溢出

    堆主要是用于对象创建时内存的分配,只要我们不断创建对象,并且这些对象在GC时不会被回收掉,则会发生Java堆内存溢出。我们通过以下程序来模拟Java堆溢出的情况:

    首先配置虚拟机参数,将堆大小设置为20M并且不可扩展:

    -verbose:gc -Xms20M -Xmx20M -XX:+PrintGCDetails
    
    public class Test { 
        public static class Obj {   
        }
        
        public static void main(String[] args) throws Exception {        
            List<Obj> list = new ArrayList<Obj>();
            while(true) {
                    //循环创建对象,并且对象并不能被回收
                list.add(new Obj());
            }
        }
    }
    

    运行结果如下:

    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:242)
        at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:216)
        at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:208)
        at java.util.ArrayList.add(ArrayList.java:440)
        at Test.main(Test.java:13)
    

    Java堆内存溢出是最常见的内存溢出情况,出现Java堆的OOM时,异常栈信息一般会有java.lang.OutOfMemoryError: Java heap space

    2.虚拟机栈溢出

    当线程执行一个方法时,java虚拟机会创建一个新的栈帧(stack frame),并压入到该线程的栈中,只要这个方法没有返回,这个栈帧就一直存在于栈中。如果方法嵌套调用层次太多(如上面的递归调用),则该线程栈中的栈帧会越来越多,并且方法都没有结束返回,这些栈帧也不会出栈,最终导致这个线程的栈中所有栈帧的大小总和大于-Xss设置的值,从而产生StackOverflowError异常。

    本地方法栈与虚拟机栈类似,在这里会有2种溢出情况:

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

    我们来模拟栈溢出情况,首先配置虚拟机参数,设置栈大小为160k:

    -verbose:gc -Xss160k -XX:+PrintGCDetails
    
    public class Test { 
        
        private int l = 1;
        
        public void callMethod() {
            System.out.println(l);
            l++;
            callMethod();
        }
        
        public static void main(String[] args) throws Exception {        
            Test t = new Test();
            try {
                t.callMethod();         
            } catch(Exception e) {
                System.out.println("stack length : " + t.l);
                throw e;
            }
        }
    }
    

    运行结果如下:

    Exception in thread "main" java.lang.StackOverflowError
        at Test.callMethod(Test.java:11)
        at Test.callMethod(Test.java:12)
        at Test.callMethod(Test.java:12)
            ......
            ......
    

    在单线程下测试以上代码时,发现只会抛出StackOverflowError异常。

    3.方法区的溢出

    方法区用于存放Class信息、常量等,方法区的内存溢出主要有2种情况:

    1. 现在的很多主流框架如Spring,会在运行时生成大量的增强类,增强的类越多,就需要越大的方法区来保存动态生成的Class信息,因此也越容易发生内存溢出。
    2. 运行时常量池内存溢出。
      我们可以使用String.intern()方法不断生成字符串常量来模拟内存溢出,同样先设置方法区的内存大小。
    -verbose:gc -XX:PermSize=4M -XX:MaxPermSize=4M -XX:+PrintGCDetails
    
    public class Test { 
        public static void main(String[] args) {        
            List<String> list = new ArrayList<String>();
            int i = 0;
            while(true) {
                System.out.println(i);
                list.add(String.valueOf(i++).intern());
            }
        }
    }
    

    运行结果如下:

    Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
        at java.lang.String.intern(Native Method)
            ......
    

    从异常栈信息中可以看到,在java.lang.OutOfMemoryError后面跟随的是"PermGen space",这里说明常量池是属于虚拟机中永久代的一部分。

    4.小结

    本文主要模拟了不同的内存区域出现内存溢出的情况,当我们自己的应用程序出现内存溢出时,就能根据异常信息定位到内存溢出的代码所在。

    系列文:
    1.Java内存区域
    2.Java内存溢出情况
    3.Java垃圾回收机制

    相关文章

      网友评论

        本文标题:Java内存溢出情况

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