1. 程式人生 > >Java記憶體溢位異常

Java記憶體溢位異常

Java堆溢位

Java堆用於儲存物件例項,只要不斷的建立物件,並且保證GC Roots到物件之間有可達路徑來避免垃圾回收機制清楚這些物件,那麼在物件數量到達最大對的容量限制後就會產生記憶體溢位異常。

package com.xrq.test;

import java.util.ArrayList;
import java.util.List;

/**
 * 測試內容:堆溢位
 *
 * 虛擬機器引數:-Xms20M -Xmx20M -XX:+HeapDumpOnOutOfMemoryError
 */
public class HeapOverflowTest
{
    public static void main(String[] args)
    {
        List<HeapOverflowTest> list = new ArrayList<HeapOverflowTest>();
        while (true)
        {
            list.add(new HeapOverflowTest());
        }
    }
}
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid8876.hprof ...
Heap dump file created [15782068 bytes in 0.217 secs]
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 com.xrq.test.HeapOverflowTest.main(HeapOverflowTest.java:18)

-Java堆記憶體的OOM異常是實際應用中常見的記憶體溢位異常情況。當出現Java堆記憶體溢位時,異常堆中資訊"java.lang.OutOfMemoryError"會跟著進一步提示‘“Java heap space”。

解決方案:一般手段是先通過記憶體映像分析工具(如Eclipse Memory Analyzer) 對Dump出來的堆轉儲快照分析,重點是確認記憶體中的物件是否是必要,也就是先分清楚是記憶體洩漏還是記憶體溢位引起的OOM。

記憶體洩漏(不再會被使用的物件記憶體不能被GC):進一步使用工具檢視洩漏物件到GC Roots的引用鏈。掌握了洩漏物件的型別資訊及GC Roots引用鏈的資訊,就可以比較準確的定位出洩漏程式碼的位置。

記憶體溢位:檢查虛擬機器的堆引數(-Xmx與-Xms),與機器實體記憶體對比看是否還可以調大,從程式碼上檢查是否存在某些物件生命週期過長、持有狀態時間過長的情況,嘗試減少程式執行期的記憶體消耗。

虛擬機器棧和本地方法棧溢位

HotSpot虛擬機器中不區分虛擬機器棧和本地方法棧,因此棧容量只有-Xss引數設定。關於虛擬機器棧和本地方法棧,在虛擬機器規範中描述了兩種異常:

(1)如果執行緒請求的棧深度大於虛擬機器所允許的最大深度,將丟擲StackOverflowError異常。

(2)如果虛擬機器在擴充套件棧時無法申請到足夠的記憶體空間,則丟擲OutOfMemoryError異常。

package com.xrq.test;

/**
 * 測試內容:棧溢位測試(遞迴呼叫導致棧深度不斷增加)
 * 
 * 虛擬機器引數:-Xss128k
 */
public class StackOverflowTest
{
    private int stackLength = 1;
    
    public void stackLeak()
    {
        stackLength++;
        stackLeak();
    }
    
    public static void main(String[] args) throws Throwable
    {
        StackOverflowTest stackOverflow = new StackOverflowTest();
        try
        {
            stackOverflow.stackLeak();
        }
        catch (Throwable e)
        {
            System.out.println("stack length:" + stackOverflow.stackLength);
            throw e;
        }        
    }
}
stack length:1006
Exception in thread "main" java.lang.StackOverflowError
    at com.xrq.test.StackOverflowTest.stackLeak(StackOverflowTest.java:14)
    at com.xrq.test.StackOverflowTest.stackLeak(StackOverflowTest.java:15)
    at com.xrq.test.StackOverflowTest.stackLeak(StackOverflowTest.java:15)
    at com.xrq.test.StackOverflowTest.stackLeak(StackOverflowTest.java:15)
    at com.xrq.test.StackOverflowTest.stackLeak(StackOverflowTest.java:15)
    at com.xrq.test.StackOverflowTest.stackLeak(StackOverflowTest.java:15)
  ...

StackOverFlowError這個異常,有錯誤堆疊可以閱讀,比較好定位。而且如果使用虛擬機器預設引數,棧深度在大多數情況下,達到1000~2000完全沒有問題,正常方法的呼叫這個深度應該是完全夠了。但是如果建立過多執行緒導致的OutOfMemoryError,在不能減少執行緒數或者更換64位虛擬機器的情況下,就只能通過減小最大堆容量和減小棧容量來換取更多的執行緒了。

方法區和執行時常量池溢位

執行時常量池也是方法區的一部分,所以這兩個區域一起看就可以了。這個區域的OutOfMemoryError可以利用String.intern()方法來產生。這是一個Native方法,意思是如果常量池中有一個String物件的字串就返回池中的這個字串的String物件;否則,將此String物件包含的字串新增到常量池中去,並且返回此String物件的引用。

package com.xrq.test;

import java.util.ArrayList;
import java.util.List;

/**
 * 測試內容:常量池溢位(這個例子也可以說明執行時常量池為方法區的一部分)
 * 
 * 虛擬機器引數-XX:PermSize=10M -XX:MaxPermSize=10M
 */
public class ConstantPoolOverflowTest
{
    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 "Reference Handler" Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
    at java.lang.String.intern(Native Method)
    at com.xrq.test.ConstantPoolOverflowTest.main(ConstantPoolOverflowTest.java:19)
java.lang.OutOfMemoryError: PermGen space
    at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:123)

對於HotSpot而言,方法區=永久代,這裡看到OutOfMemoryError的區域是“PermGen space”,即永久代,那其實也就是方法區溢位了。注意一下JDK1.7下是不會有這個異常的,while迴圈將一直下去,因為JDK1.7之後溢位了永久代並採用Native Memory來實現方法區的規劃了