Java記憶體區域及記憶體溢位

NO IMAGE
1 Star2 Stars3 Stars4 Stars5 Stars 給文章打分!
Loading...

堆溢位

Java堆用於儲存物件例項,只要不斷地建立物件,並且保證GC Roots到物件之間有可達路徑避免垃圾回收,當到達最大堆的容量限制後就會產生Java.lang.OutOfMemoryError.

/**
 * VM Options:
 * -Xms20M
 * -Xmx20M
 * -XX: HeapDumpOnOutOfMemoryError
 */
public class HeapOOM{
    static class OOMObject{}
    
    public static void main(String[] args){
        List<OOMObject> list = new ArrayList<OOMObject>();
        while(true){
            list.add(new OOMObject());
        }
    }
}

結果:
GC多次執行後觸發OutOfMemoryError.

棧溢位

關於虛擬機器棧,在Java規範中描述了兩種異常:

  1. 如果執行緒請求的棧深度大於虛擬機器所允許的最大深度,將丟擲StackOverflowError異常。
  2. 如果虛擬機器在擴充套件棧時無法申請到足夠的記憶體空間,則丟擲OutOfMemoryError異常。

然而,在單執行緒下,虛擬機器在棧空間不足時會嘗試擴充套件棧空間,因此,當無法繼續分配時,到底是記憶體太小,還是已使用的棧空間太大,其實是一回事。在實驗中,單執行緒環境下,只會丟擲StackOverflowError異常。

/**
 * VM Option:
 * -Xss160K
 */
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(Throwable e){
            System.out.println("Stack length:"   oom.stackLength);
            throw e;
        }
    }
}

結果:

作業系統分配給每個程序的記憶體是有限制的,通常為作業系統限制總記憶體-最大堆容量(Xmx)-最大方法區容量(MaxPermSize)-程式計數器消耗。每個執行緒分配到的棧容量越大,可以建立的執行緒數目越小。

/**
 * VM Options:
 * -Xss2M
 */
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) throws Throwable{
        JavaVMStackOOM oom = new JavaVMStackOOM();
        oom.stackLeakByThread();
    }
}

結果:
Exception in thread "main" java.lang.outOfMemoryError: unable to create new native thread

執行時常量池溢位

執行時常量池在JDK 1.6及之前版本中在方法區中,在1.7及之後轉移至堆空間。在JDK 1.6及之前版本中可以通過限制方法區大小,從而間接限制執行時常量池大小。

/**
 * ONLY WORKS BEFORE JDK 1.7
 * VM Options:
 * -XX:PermSize=10M
 * -XX:MaxPermSize=10M
public class RuntimeConstantPoolOOM{
    public static void main(String[] args){
        List<String> list = new ArrayList<String>();
        int i = 0;
        while(true){
            list.add(String.valueof(i  ).intern());
        }
    }
}

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

方法區溢位

方法區用於存放Class相關資訊,因此要使得方法區溢位,除了在JDK 1.7之前使執行時常量池溢位外,基本的思路是執行時生成大量的類去填滿方法區。
結果
Exception in thread "main" java.lang.OutOfMemoryError:PermGen space

直接記憶體(DirectMemory)溢位

直接記憶體不是虛擬機器執行時資料區的一部分。在JDK 1.4中新加入了NIO(New Input/Output)類,引入了一種基於通道(Channel)與緩衝區(Buffer)的I/O方式,使用Native函式庫直接分配堆外記憶體。
DirectMemory容量可通過-XX: MaxDirectMemorySize指定,如果不指定,則預設與Java堆最大值一樣(-Xmx)。直接通過allocateMemory可以造成本機記憶體溢位。

結果:
Exception in thread "main" java.lang.OutOfMemoryError
直接記憶體溢位的一個特徵是Heap Dump檔案中不會看先明顯的異常指示。如果OOM之後Dump檔案很小,而程式中又直接或間接使用了DIO,就應該檢查是否直接記憶體溢位。

String.intern()

String.intern()是一個Native方法,作用是:如果字串常量池中已經包含一個等於此String物件的字串,則返回代表池中這個字串的String物件,否則,將此String物件包含的字串新增到常量池中,並且返回此String物件的引用。

public class RuntimeConstantPoolOOM{
    public static void main(String[] args){
        String str1 = new StringBuilder("計算機").append("軟體").toString();
        System.out.println(str1.intern() == str1);//JDK 1.6 false JDK 1.7 true
        
        String str2 = new StringBuilder("ja").append("va").toString();
        System.out.println(str2.intern() == str2);//JDK 1.6 false JDK 1.7 true
    }
}

在JDK 1.6中,intern()方法會把首次遇到的字串例項複製到永久代(方法區執行時常量池),返回的是這個永久代中這個字串例項的引用,而由StringBuilder建立的字串例項在Java堆上,所以必然不是同一個引用。
在JDK 1.7中,intern()實現不會再複製,只是在常量池中記錄首次出現的例項引用,因此intern()返回的引用和由StringBuilder建立的那個字串例項是同一個。

小節

記憶體區域描述VM Option異常
程式計數器
虛擬機器棧存放編譯器可知的各種基本型別,物件引用和returnAddress型別-Xss160K 每個執行緒的棧大小StackOverflowError/OutOfMemoryError
Java堆存放物件例項-Xms10M 最大值
-Xmx20M 最小值
OutOfMemory: Java heap space
執行時常亮池存放編譯期生成的字面量和符號引用,執行期也能放入常量池(string.intern())。JDK 1.7之前在方法區中,JDK 1.7及之後移至堆中隨方法區或堆設定OutOfMemoryError
方法區儲存虛擬機器載入的類資訊、常亮、靜態變數、即時編譯器編譯後的程式碼等資料,又稱為永久代(Permanent Generation)-XX:PermSize=10M 初始值
-XX:MaxPermSize=20M 最大值
OutOfMemoryError: PermGen space
直接記憶體在JDK 1.4中加入NIO類,直接分配堆外記憶體-XX:MaxDirectMemorySize=10M,
如果不指定預設與-Xmx一樣
OutOfMemoryError

相關文章

開發語言 最新文章