Java記憶體溢位情況

雲之崖發表於2017-10-15

在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垃圾回收機制


相關文章