Java内存溢出情况

在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 list = new ArrayList();
        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 list = new ArrayList();
        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内存溢出情况)