1. 程式人生 > >java記憶體洩漏與記憶體溢位

java記憶體洩漏與記憶體溢位

記憶體洩漏指你用malloc或new申請了一塊記憶體,但是沒有通過free或delete將記憶體釋放,導致這塊記憶體一直處於佔用狀態。

記憶體溢位指你申請了10個位元組的空間,但是你在這個空間寫入11或以上位元組的資料,就是溢位。

1. 記憶體溢位 out of memory

	是指程式在申請記憶體時,沒有足夠的記憶體空間供其使用,出現out of memory;

1.1 常見的記憶體溢位分為以下幾種:

1- Java堆溢位

Java 堆用於儲存物件例項,只要不斷地建立物件,並且保證垃圾回收機制清除這些物件,那麼在物件數量達到最大堆限制就會產生記憶體溢位異常。

測試方案:無限迴圈new物件例項出來,在List中儲存引用,防止GC回收,最終會產生OOM ,異常堆疊資訊並提示Java heap space。

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

關於虛擬機器棧和本地方法棧,Java虛擬機器規範中定義了兩種異常: 如果執行緒請求的棧深度大於虛擬機器所允許的最大深度,將丟擲StackOverflowError 異常。 如果虛擬機器在擴充套件棧時無法申請到足夠的記憶體空間,則丟擲OutOfMemoryError異常。

測試方案: 單執行緒條件下,通過不斷遞迴呼叫方法,如不斷累加的方法,如下所示:

public class JavaVMStackSOF{
    private int stackLength=1;
    public void stackLeak(){
        stackLength++;//累加變數
        stackLeak();//呼叫自身
    }
} 

最終會產生StackOverflowError棧溢位異常;

多執行緒條件下,無限迴圈地建立執行緒,併為每個執行緒無限迴圈的增加記憶體,最終會導致OutOfMemoryError異常。 3 方法區和執行時常量池溢位 執行時常量池是方法區的一部分。方法區用於存放Class的相關資訊,如類名,訪問修飾符,常量池,欄位描述,方法描述等。測試方法: (1)對於非常量池部分,執行時生成大量的動態類填滿方法區; (2)對於常量池部分,無限迴圈呼叫String的intern()方法產生不同的String物件例項,並在List中儲存其引用,以防止被GC回收,最終會產生溢位

2. 記憶體洩露 memory leak

 是指程式在申請記憶體後,無法釋放已申請的記憶體空間 
 memory leak會最終會導致out of memory!

2.1 記憶體洩漏分類

  1. 常發性記憶體洩漏。發生記憶體洩漏的程式碼會被多次執行到,每次被執行的時候都會導致一塊記憶體洩漏。

  2. 偶發性記憶體洩漏。發生記憶體洩漏的程式碼只有在某些特定環境或操作過程下才會發生。常發性和偶發性是相對的。對於特定的環境,偶發性的也許就變成了常發性的。所以測試環境和測試方法對檢測記憶體洩漏至關重要。

  3. 一次性記憶體洩漏。發生記憶體洩漏的程式碼只會被執行一次,或者由於演算法上的缺陷,導致總會有一塊僅且一塊記憶體發生洩漏。比如,在類的建構函式中分配記憶體,在解構函式中卻沒有釋放該記憶體,所以記憶體洩漏只會發生一次。

  4. 隱式記憶體洩漏。程式在執行過程中不停的分配記憶體,但是直到結束的時候才釋放記憶體。嚴格的說這裡並沒有發生記憶體洩漏,因為最終程式釋放了所有申請的記憶體。但是對於一個伺服器程式,需要執行幾天,幾周甚至幾個月,不及時釋放記憶體也可能導致最終耗盡系統的所有記憶體。所以,我們稱這類記憶體洩漏為隱式記憶體洩漏。

  5. 從使用者使用程式的角度來看,記憶體洩漏本身不會產生什麼危害,作為一般的使用者,根本感覺不到記憶體洩漏的存在。真正有危害的是記憶體洩漏的堆積,這會最終消耗盡系統所有的記憶體。從這個角度來說,一次性記憶體洩漏並沒有什麼危害,因為它不會堆積,而隱式記憶體洩漏危害性則非常大,因為較之於常發性和偶發性記憶體洩漏它更難被檢測到

2.2 記憶體洩漏引起的原因

記憶體洩露是指無用物件(不再使用的物件)持續佔有記憶體或無用物件的記憶體
得不到及時釋放,從而造成的記憶體空間的浪費稱為記憶體洩露。

2.3 Java記憶體洩露根本原因是什麼呢?

長生命週期的物件持有短生命週期物件的引用就很可能發生記憶體洩露, 儘管短生命週期物件已經不再需要,但是因為 長生命週期物件持有它的引用而導致不能被回收 這就是java中記憶體洩露的發生場景。具體主要有如下幾大類:

1、靜態集合類引起記憶體洩露:

像HashMap、Vector等的使用最容易出現記憶體洩露,這些靜態變數的生命週期和應用程式一致,他們所引用的所有的物件Object也不能被釋放,因為他們也將一直被Vector等引用著

Static Vector v = new Vector(10);
for (int i = 1; i<100; i++)
{
Object o = new Object();
v.add(o);
o = null;
}//

在這個例子中,迴圈申請Object 物件,並將所申請的物件放入一個Vector 中,如果僅僅釋放引用本身(o=null),那麼Vector 仍然引用該物件,所以這個物件對GC 來說是不可回收的。因此,如果物件加入到Vector 後,還必須從Vector 中刪除,最簡單的方法就是將Vector物件設定為null。

2、當集合裡面的物件屬性被修改後,再呼叫remove()方法時不起作用。

public static void main(String[] args)
{
Set<Person> set = new HashSet<Person>();
Person p1 = new Person("唐僧","pwd1",25);
Person p2 = new Person("孫悟空","pwd2",26);
Person p3 = new Person("豬八戒","pwd3",27);
set.add(p1);
set.add(p2);
set.add(p3);
System.out.println("總共有:"+set.size()+" 個元素!"); //結果:總共有:3 個元素!
p3.setAge(2); //修改p3的年齡,此時p3元素對應的hashcode值發生改變

set.remove(p3); //此時remove不掉,造成記憶體洩漏

set.add(p3); //重新新增,居然新增成功
System.out.println("總共有:"+set.size()+" 個元素!"); //結果:總共有:4 個元素!
for (Person person : set)
{
System.out.println(person);
}
}