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

 

代碼舉例:

 

Java代碼  收藏代碼
  1. <span style="font-size: 11px;">/** 
  2. * 堆內存溢出 
  3. * 
  4. * jvm參數:-Xms5m -Xmx5m -Xmn2m -XX:NewSize=1m 
  5. * 
  6. */  
  7. public class MemoryLeak {  
  8.      
  9.     private String[] s = new String[1000];  
  10.    
  11.     public static void main(String[] args) throws InterruptedException {  
  12.         Map<String,Object> m =new HashMap<String,Object>();  
  13.         int i =0;  
  14.         int j=10000;  
  15.         while(true){  
  16.             for(;i<j;i++){  
  17.                 MemoryLeak memoryLeak = new MemoryLeak();  
  18.                 m.put(String.valueOf(i), memoryLeak);  
  19.             }  
  20.         }  
  21.     }  
  22. }</span>  
 

 

           

2、方法區內存溢出outOfMemoryError:permgem space

       在jvm規範中,方法區主要存放的是類信息、常量、靜態變量等。

       所以如果程序加載的類過多,或者使用反射、gclib等這種動態代理生成類的技術,就可能導致該區發生內存溢出,一般該區發生內存溢出時的錯誤信息爲:

             outOfMemoryError:permgem space

 

代碼舉例:

 

Java代碼  收藏代碼
  1. <span style="font-size: 11px;">jvm參數:-XX:PermSize=2m -XX:MaxPermSize=2m  
  2.   
  3. 將方法區的大小設置很低即可,在啓動加載類庫時就會出現內存不足的情況</span>  
 

 

 

3、線程棧溢出java.lang.StackOverflowError

       線程棧時線程獨有的一塊內存結構,所以線程棧發生問題必定是某個線程運行時產生的錯誤。

       一般線程棧溢出是由於遞歸太深或方法調用層級過多導致的。

       發生棧溢出的錯誤信息爲:

              java.lang.StackOverflowError

 

代碼舉例:

 

Java代碼  收藏代碼
  1. <span style="font-size: 11px;">/** 
  2. * 線程操作棧溢出 
  3. * 
  4. * 參數:-Xms5m -Xmx5m -Xmn2m -XX:NewSize=1m -Xss64k 
  5. * 
  6. */  
  7. public class StackOverflowTest {  
  8.      
  9.     public static void main(String[] args) {  
  10.         int i =0;  
  11.         digui(i);  
  12.     }  
  13.      
  14.     private static void digui(int i){  
  15.         System.out.println(i++);  
  16.         String[] s = new String[50];  
  17.         digui(i);  
  18.     }  
  19.   
  20. }  
  21. </span>  
 

 

 

五、爲了避免內存泄露,在編寫代碼的過程中可以參考下面的建議:

 

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內存泄露和內存溢出

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章