Java記憶體溢位場景及解決辦法
Java記憶體溢位即程式在申請記憶體時,沒有足夠的空間供其使用,出現out of memory。常見於四種情況:棧溢位(StackOverflowError)、堆溢位(OutOfMemoryError:java heap space)、永久代溢位(OutOfMemoryError: PermGen space)、OutOfMemoryError:unable to create native thread,以下一一進行總結。
1、棧溢位
首先搞清楚java棧空間儲存的是什麼。java棧空間是執行緒私有的,是java方法執行的記憶體模型。每個方法執行時都會在java棧空間產生一個棧幀,存放方法的變量表,返回值等資訊,方法的執行到結束就是一個棧幀入棧到出棧的過程。
所以棧溢位的原因一般是迴圈呼叫方法導致棧幀不斷增多,棧深度不斷增加,最終沒有記憶體可以分配,出現StackOverflowError,比如下面這種情況:
public class stack{
public void test(){
this.test();
}
public static void main(String[] args){
for(; ; ;)
new stack().test;
}
}
棧記憶體溢位一般是程式錯誤導致,如遞迴死迴圈等等。
2、堆溢位
java堆是執行緒共有的區域,主要用來存放物件例項,幾乎所有的java物件都在這裡分配記憶體,也是JVM記憶體管理最大的區域。java堆記憶體分年輕代和年老代,堆記憶體溢位一般是年老代溢位。當程式不斷地建立大量物件例項並且沒有被GC回收時,就容易產生記憶體溢位。當一個物件產生時,主要過程是這樣的:
JVM首先在年輕代的Eden區為它分配記憶體;
若分配成功,則結束,否則JVM會觸發一次Young GC,試圖釋放Eden區的不活躍物件;
如果釋放後還沒有足夠的記憶體空間,則將Eden區部分活躍物件轉移到Suvivor區,Suvivor區長期存活的物件會被轉移到老年代;
當老年代空間不夠,會觸發Full GC,對年老代進行完全的垃圾回收;
回收後如果Suvivor和老年代仍沒有充足的空間接收從Eden複製過來的物件,使得Eden區無法為新產生的物件分配記憶體,即溢位。
由此可見,當程式不斷地建立大量物件例項並且沒有被GC回收時,就容易產生記憶體溢位。如下:
public class heap{ public static void main(String[] args){ ArrayList list = new ArrayList(); while(true){ list.add(new heap()); } } }
堆記憶體溢位很可能伴隨記憶體洩漏,應首先排查可能洩露的物件,再通過工具檢查GC roots引用鏈,從而發現洩露物件是由於何種引用關係使得GC無法回收他們;若不存在記憶體洩漏,換句話說就是記憶體中的物件還都需要繼續存活,則可通過修改虛擬機器的堆引數將堆記憶體增大。
3、永久代溢位
永久代也是java堆記憶體的一部分,主要用來存放Class的相關資訊,如類名,訪問修飾符等等。一般永久代溢位的原因是動態載入大量的Class並且沒有及時被GC回收。只能通過調整永久代記憶體引數的方式解決。
4、無法建立本地執行緒
我們知道,作業系統對每個程序的記憶體都是有一定限制的,當堆記憶體和非堆記憶體分配過大時,剩餘的記憶體不足以建立足夠的執行緒棧,就會產生OutOfMemoryError。因此我們可以增大程序佔用的總記憶體或減小堆記憶體等來解決問題。
總結:
- 棧記憶體溢位:程式所要求的棧深度過大導致。
- 堆記憶體溢位: 分清 記憶體洩露還是 記憶體容量不足。洩露則看物件如何被 GC Root 引用。不足則通過 調大 -Xms,-Xmx引數。
- 持久帶記憶體溢位:Class物件未被釋放,Class物件佔用資訊過多,有過多的Class物件。
- 無法建立本地執行緒:總容量不變,堆記憶體,非堆記憶體設定過大,會導致能給執行緒的記憶體不足。
參考部落格: