天天看點

java記憶體洩露 垃圾回收_Java中記憶體洩露及垃圾回收機制

3垃圾回收機制

3.1 什麼是垃圾

垃圾,記憶體中的垃圾,即記憶體中已無效但又無法自動釋放的空間。在Java語言中,沒有引用句柄指向的類對象最容易成為垃圾。,産生垃圾的情況有很多,主要有以下3種:

(1)      超出對象的引用句柄的作用域時,這個引用句柄引用的對象就變成垃圾。

例:

Person p1 = new Person();

……

引用句柄p1的作用域是從定義到“}”處,執行完這對大括号中的所有代碼後,産生的Person對象就會變成垃圾,因為引用這個對象的句柄p1已超過其作用域,p1已經無效,Person對象不再被任何句柄引用了。

(2)      沒有超出對象的引用句柄的作用域時,給這個引用句柄指派為空時,這個引用句柄引用的對象就變成垃圾。

例:

Person p1 = new Person();

…..

p1 = null;

….

在執行完“p1=null;”後,即使句柄p1還沒有超出其作用域,仍然有效,但它已被指派為空,不再指向任何對象,則這個Person對象不再被任何句柄引用,變成了垃圾。此後p1還可以指向其它Person對象,因為還沒有超出它的作用域。

(3)      建立匿名對象時,匿名對象用完以後即成垃圾。

例:

new Person();              //因為是匿名對象,沒有引用句柄指向它,即為垃圾

new Person().print();

//當運作完匿名對象的print()方法,這個對象也變成了垃圾

……

是以,在程式中應盡量少用匿名對象。

3.2 垃圾回收

在Java程式運作過程中,一個垃圾回收器會(Garbage Collector,簡稱GC)不定時地被喚起檢查是否有不再被使用的對象,并釋放它們占用的記憶體空間。垃圾回收器的回收無規律可循,可能在程式的運作的過程中,一次也沒有啟動,也可能啟動很多次。是以,并不會因為程式代碼一産生垃圾,垃圾回收器就馬上被喚起而自動回收垃圾,很可能到程式結束時垃圾回收器都沒有啟動。是以垃圾回收器并不能完全避免記憶體洩漏的問題。

另一方面,垃圾回收會給系統資源帶來額外的負擔和時空開銷。它被啟動的幾率越小,帶來的負擔的幾率就越小。是以,垃圾的回收政策也很重要。

3.3 垃圾回收器的回收政策

不同廠商、不同版本的Java虛拟機中的記憶體垃圾回收機制并不完全一樣,通常越新版本的記憶體回收機制越快。而不同的Java虛拟機采用不同的回收政策,常用的有兩種:複制式回收政策和自省式回收政策。

複制式回收政策:先将正在運作中的程式暫停,然後把正在被使用的所有對象從它們所在的堆記憶體A裡複制到另一塊堆記憶體B,再釋放堆記憶體A中的所有空間,這些那些不再使用的對象所占用的記憶體空間就會被釋放掉。這種方式需要維護所需記憶體數量的至少兩倍的記憶體空間,适合垃圾比較多的情況。當程式隻産生了少量垃圾或者沒有垃圾時,這種回收政策的效率就非常低。

自省式回收政策:首先檢測所有正在使用的對象,并為它們标注,比如用1來标注正在使用的對象,用0來标注不再被使用的對象,然後将所有标注為0的記憶體空間一次釋放。因為标注會增大系統的開銷,是以這種方式的速度仍然很慢,尤其是在垃圾比較多的情況下,效率會很低。這種方法适合垃圾比較少的情況。

這兩種方式具有互補性,是以在一些Java虛拟機中兩種方式被有機的結合運用。

4 System.gc()

由于Java的垃圾回收器的啟用不由程式員控制,而且回收也無規律可循,并不會一産生了垃圾,垃圾回收器就被喚起;有時甚至可能到程式終止,回收器都沒有啟動的機會。是以這個垃圾回收機制不是一個很可靠的機制。因為垃圾不能及時回收,它們所占用的記憶體空間不能釋放,就會影響程式的性能;如果某段程式産生大量的垃圾而沒有回收,回收工作也會變得困難。為了解決這個問題,Java提供一個System.gc()方法,可以強制啟動垃圾回收器來回收垃圾,以減少記憶體洩露發生的機率。

例:匿名對象會産生垃圾,如果擔心這些垃圾不能及時回收,可以在使用完這些匿名對象以後,加上一條語句:System.gc(),強制啟動垃圾回收器來回收垃圾。

class TestJc

{

public void finalize()

{

System.out.println("Free the occupied memory...");

}

public static void main(String args[])

{

new TestJc();

new TestJc();

new TestJc();

System.gc();

System.out.println("End of program.");

}

}

程式的運作結果是:

End of program.

Free the occupied memory...

Free the occupied memory...

Free the occupied memory...

System.gc()有一個特點,就是在對象被當成垃圾從記憶體中釋放前要調用finalize()方法,而且釋放一個對象調用一次finalize()方法。從程式的運作結果可以看到:垃圾回收器啟動以後,并不一定馬上開始回收垃圾,很可能要等待一段時間才執行。這是因為在程式運作過程中,垃圾收集線程的優先級比較低,如果有比這個線程優先級高的線程,先運作這些優先級高的線程,等這些線程執行完畢,才進行垃圾回收。是以System.gc()方法隻是一種“建議”,它建議Java虛拟機執行垃圾回收,釋放記憶體空間,但什麼時候能夠回收就不能夠預知了。

如果我們把“System.gc();”語句,放在第二個匿名對象語句後面,再進行編譯和執行,會發現結果是這樣的:

End of program.

Free the occupied memory...

Free the occupied memory...

這是因為,啟動完垃圾回收器以後,它隻能檢測到在垃圾回收器強制啟動之前程式運作所産生的垃圾,Java的虛拟機盡最大的努力從被丢棄的對象上回收垃圾;對于在啟動垃圾回收器以後産生的垃圾,這個線程檢測到的機率就非常小了,如果檢測不到,就不能回收這些垃圾。

是以,Java中的垃圾回收器機制及System.gc()方法,并不能夠完全避免記憶體洩露的問題,隻是盡可能降低記憶體洩露的可能性和程度。

5. Java程式設計中需要注意的事項

為了提高垃圾的回收效率,在實際應用中,使用下列幾種方法可以在一定程度上避免Java中的記憶體洩露。

(1)      盡量少用匿名對象,慎用内部類

匿名對象被使用完以後就會變成垃圾;而在内部類中,隐含着一個外部類對象的引用,這個引用也無法自動消除。

(2)      在使用System.gc()方法的程式中,盡量少用finalize()方法

因為System.gc()方法在回收每一個對象所占用的記憶體空間時,都會調用finalize()方法,在這個方法中的任何操作都會增加垃圾回收的開銷。

(3)      慎用System.gc()方法,減少線程的個數

在程式中可以顯式地調用System.gc()方法,但這種方法不能保證清除所有的垃圾。另外垃圾回收也是一個線程,也會消耗系統的資源,啟動垃圾回收也可能會造成間歇性停頓。線程越多,垃圾回收線程挂起和恢複的可能性就越大,而耗費的時間就越長,系統的開銷就越大。