Java記憶體溢位和記憶體洩露
雖然jvm可以通過GC自動回收無用的記憶體,但是程式碼不好的話仍然存在記憶體溢位的風險。
最近在網上搜集了一些資料,現整理如下:
一、為什麼要了解記憶體洩露和記憶體溢位?
1、記憶體洩露一般是程式碼設計存在缺陷導致的,通過了解記憶體洩露的場景,可以避免不必要的記憶體溢位和提高自己的程式碼編寫水平;
2、通過了解記憶體溢位的幾種常見情況,可以在出現記憶體溢位的時候快速的定位問題的位置,縮短解決故障的時間。
二、基本概念
理解這兩個概念非常重要。
記憶體洩露:指程式中動態分配記憶體給一些臨時物件,但是物件不會被GC所回收,它始終佔用記憶體。即被分配的物件可達但已無用
記憶體溢位:指程式執行過程中無法申請到足夠的記憶體而導致的一種錯誤。記憶體溢位通常發生於OLD段或Perm段垃圾回收後,仍然無記憶體空間容納新的Java物件的情況。
從定義上可以看出內存洩露是記憶體溢位的一種誘因,不是唯一因素。
三、記憶體洩露的幾種場景:
1、長生命週期的物件持有短生命週期物件的引用
這是記憶體洩露最常見的場景,也是程式碼設計中經常出現的問題。
例如:在全域性靜態map中快取區域性變數,且沒有清空操作,隨著時間的推移,這個map會越來越大,造成記憶體洩露。
2、修改hashset中物件的引數值,且引數是計算雜湊值的欄位
當一個物件被儲存進HashSet集合中以後,就不能修改這個物件中的那些參與計算雜湊值的欄位,否則物件修改後的雜湊值與最初儲存進HashSet集合中時的雜湊值就不同了,在這種情況下,即使在contains方法使用該物件的當前引用作為引數去HashSet集合中檢索物件,也將返回找不到物件的結果,這也會導致無法從HashSet集合中刪除當前物件,造成記憶體洩露。
3、機器的連線數和關閉時間設定
長時間開啟非常耗費資源的連線,也會造成記憶體洩露。
四、記憶體溢位的幾種情況:
1、堆記憶體溢位(outOfMemoryError:java heap space
在jvm規範中,堆中的記憶體是用來生成物件例項和陣列的。
如果細分,堆記憶體還可以分為年輕代和年老代,年輕代包括一個eden區和兩個survivor區。
當生成新物件時,記憶體的申請過程如下:
a、jvm先嚐試在eden區分配新建物件所需的記憶體;
b、如果記憶體大小足夠,申請結束,否則下一步;
c、jvm啟動youngGC,試圖將eden區中不活躍的物件釋放掉,釋放後若Eden空間仍然不足以放入新物件,則試圖將部分Eden中活躍物件放入Survivor區;
d、Survivor區被用來作為Eden及old的中間交換區域,當OLD區空間足夠時,Survivor區的物件會被移到Old區,否則會被保留在Survivor區;
e、 當OLD區空間不夠時,JVM會在OLD區進行full GC;
f、full GC後,若Survivor及OLD區仍然無法存放從Eden複製過來的部分物件,導致JVM無法在Eden區為新物件建立記憶體區域,則出現”out of memory錯誤”:
outOfMemoryError:java heap space
程式碼舉例:
/**
* 堆記憶體溢位
*
* jvm引數:-Xms5m -Xmx5m -Xmn2m -XX:NewSize=1m
*
*/
public class MemoryLeak {
private String[] s = new String[1000];
public static void main(String[] args) throws InterruptedException {
Map<String,Object> m =new HashMap<String,Object>();
int i =0;
int j=10000;
while(true){
for(;i<j;i++){
MemoryLeak memoryLeak = new MemoryLeak();
m.put(String.valueOf(i), memoryLeak);
}
}
}
}
2、方法區記憶體溢位(outOfMemoryError:permgem space)
在jvm規範中,方法區主要存放的是類資訊、常量、靜態變數等。
所以如果程式載入的類過多,或者使用反射、gclib等這種動態代理生成類的技術,就可能導致該區發生記憶體溢位,一般該區發生記憶體溢位時的錯誤資訊為:
outOfMemoryError:permgem space
程式碼舉例:
jvm引數:-XX:PermSize=2m -XX:MaxPermSize=2m
將方法區的大小設定很低即可,在啟動載入類庫時就會出現記憶體不足的情況
3、執行緒棧溢位(java.lang.StackOverflowError)
執行緒棧時執行緒獨有的一塊記憶體結構,所以執行緒棧發生問題必定是某個執行緒執行時產生的錯誤。
一般執行緒棧溢位是由於遞迴太深或方法呼叫層級過多導致的。
發生棧溢位的錯誤資訊為:
java.lang.StackOverflowError
程式碼舉例:
/**
* 執行緒操作棧溢位
*
* 引數:-Xms5m -Xmx5m -Xmn2m -XX:NewSize=1m -Xss64k
*
*/
public class StackOverflowTest {
public static void main(String[] args) {
int i =0;
digui(i);
}
private static void digui(int i){
System.out.println(i++);
String[] s = new String[50];
digui(i);
}
}
五、為了避免記憶體洩露,在編寫程式碼的過程中可以參考下面的建議:
1、儘早釋放無用物件的引用
2、使用字串處理,避免使用String,應大量使用StringBuffer,每一個String物件都得獨立佔用記憶體一塊區域
3、儘量少用靜態變數,因為靜態變數存放在永久代(方法區),永久代基本不參與垃圾回收
4、避免在迴圈中建立物件
5、開啟大型檔案或從資料庫一次拿了太多的資料很容易造成記憶體溢位,所以在這些地方要大概計算一下資料量的最大值是多少,並且設定所需最小及最大的記憶體空間值。
參考:
http://zhidao.baidu.com/question/263477119.html
https://www.ibm.com/developerworks/cn/java/l-JavaMemoryLeak/ Java的記憶體洩漏
http://jelly-x.iteye.com/blog/1120406 JVM記憶體分析及導致記憶體溢位
http://wenku.baidu.com/view/ef3158fc04a1b0717fd5ddda.html java記憶體洩露和記憶體溢位