GC垃圾收集器概述

一個優秀的Java程序員必須瞭解GC的工作原理、如何優化GC的性能、如何和GC進行有效的交互,因爲有一些應用程序對性能要求較高,例如嵌入式系統、實時系統等。只有全面提升內存的管理效率,才能提高整個應用程序的性能。 本篇文章首先簡單介紹GC的工作原理,然後再對GC的幾個關鍵問題進行深入探討,最後提出一些Java程序設計建議,從GC角度提高Java程序的性能。

GC的基本原理
Java的內存管理實際上就是對象的管理,其中包括對象的分配和釋放,對於程序員來說,分配對象使用new關鍵字;釋放對象時,只要將對象所有引用賦值爲null,讓程序不能夠再訪問到這個對象,我們稱該對象爲"不可達的".GC將負責回收所有"不可達"對象的內存空間。
對於GC來說,當程序員創建對象時,GC就開始監控這個對象的地址、大小以及使用情況。通常,GC採用有向圖的方式記錄和管理堆(heap)中的所有對象,通過這種方式確定哪些對象是"可達的",哪些對象是"不可達的"。當GC確定一些對象爲"不可達"時,GC就有責任回收這些內存空間。但是,爲了保證GC能夠區別平臺實現的問題,Java規範標準對GC的很多行爲都沒有進行嚴格的規定。例如,對於採用什麼類型的回收算法、什麼時候進行回收等重要問題都沒有明確的規定。因此,不同的JVM的實現者往往有不同的實現算法。這也給Java程序員的開發帶來很多不確定性。本文研究了幾個和GC工作相關的問題,努力減少這種不確定性給Java程序帶來的負面影響。

  增量式GC( Incremental GC )
GC在JVM中通常由一個或一組進程來實現,它本身也和用戶程序一樣佔用heap空間,運行時也佔用CPU,當GC進程運行時,應用程序停止運行。因此,當GC運行時間較長時,用戶能夠感到Java程序的停頓,另一方面,如果GC運行時間太短,可能對象回收率太低,這意味着還有很多應該回收的對象沒有被回收,仍然佔用大量內存。因此,在設計GC的時候,就必須在停頓時間和回收率之間進行權衡。

一個好的GC實現允許用戶定義自己所需要的設置,例如內存有限的設備,對內存的使用量非常敏感,希望GC能夠準確的回收內存,它並不在意程序速度的放慢,另外一些實時網絡遊戲,就不能夠允許程序有長時間的中斷。 增量式GC就是通過一定的回收算法,把一個長時間的中斷,劃分爲很多個小的中斷,通過這種方式減少GC對用戶程序的影響。雖然,增量式GC在整體性能上可能不如普通GC的效率高,但是它能夠減少程序的最長停頓時間。
Sun JDK提供的HotSpot JVM就能支持增量式GC。HotSpot JVM缺省GC方式爲不使用增量GC,爲了啓動增量GC,我們必須在運行Java程序時增加-Xincgc的參數。HotSpot JVM增量式GC,實現是採用Train GC算法,它的基本想法:將堆中的所有對象按照創建和使用情況進行分組(分層),將使用頻繁和具有相關性的對象放在一隊中,隨着程序的運行,不斷對組進行調整,當GC運行時,它總是先回收最老的(最近很少訪問的)對象,如果整組都爲可回收對象,GC將整組回收,這樣,每次GC運行只回收一定比例的不可達對象,保證程序的順暢運行。

finalize()函數
finalize是位於Object類的一個思路方法,該思路方法的訪問修飾符爲protected,由於所有類爲Object的子類,因此用戶類很容易訪問到這個思路方法。由於,finalize函數沒有自動實現鏈式調用,我們必須手動實現,因此finalize函數的最後一個語句通常是super.finalize()。通過這種方式,我們可以實現從下到上實現finalize的調用,即先釋放自己的資源,然後再釋放父類的資源。
根據Java語言規範標準,JVM保證調用finalize函數之前,這個對象是不可達的,但是,JVM不保證這個函數一定會被調用。另外,規範標準還保證finalize函數最多運行一次。
很多Java初學者會認爲這個思路方法類似和C++中的析構函數,將很多對象、資源的釋放都放在這一函數裏面。其實,這不是一種很好的方式,原因如下:其一,GC爲了能夠支持finalize函數,要對覆蓋這個函數的對象作很多附加的工作;其二,在finalize運行完成之後,該對象可能變成可達的,GC還要再檢查一次該對象是否是可達的,因此,使用finalize會降低GC的運行性能;其三,由於GC調用finalize的時間是不確定的,因此通過這種方式釋放資源也是不確定的。
通常,finalize用於一些不容易控制,並且非常重要資源的釋放,例如一些I/O操作、數據連接等,這些資源的釋放對整個應用程序是非常關鍵的。在這種情況下,程序員應該以通過程序本身管理(包括釋放)這些資源爲主,以finalize函數釋放資源方式爲輔,形成一種雙保險的管理機制,而不應該僅僅依靠finalize來釋放資源。
程序如何和GC進行交互
(不懂...)
Java2增強了內存管理功能,增加了一個java.lang.ref包,其中定義了3種引用類。這3種引用類分別爲SoftReference、WeakReference和PhantomReference.通過使用這些引用類,程序員可以在一定程度和GC進行交互,以便改善GC的工作效率。這些引用類的引用強度介於可達對象和不可達對象之間。
一些Java編碼的建議
根據GC的工作原理,我們可以通過一些竅門技巧和方式,讓GC運行更加有效率,更加符合應用程序的要求。以下就是一些程序設計的幾點建議:
1、最基本的建議就是儘早釋放無用對象的引用。大多數程序員在使用臨時變量的時候,都是讓引用變量在退出活動域(scope)後自動設置爲null。我們在使用這種方式時候,必須特別注意一些複雜的對象圖,例如數組、隊列、樹、圖等,這些對象之間有相互引用,關係較爲複雜。對於這類對象,GC回收它們一般效率較低。如果程序允許,儘早將不用的引用對象賦爲null。這樣可以加速GC的工作。
2、儘量少用finalize函數。Finalize函數是Java提供給程序員一個釋放對象或資源的機會,但是,它會加大GC的工作量,因此儘量少採用finalize方式回收資源。
3、注意集合數據類型,包括數組、樹、圖、鏈表等數據結構,這些數據結構對GC來說回收更爲複雜。另外,注意一些全局的變量,以及靜態變量,這些變量往往容易引起懸掛對象(dangling reference),造成內存浪費。
4、當程序有一定的等待時間,程序員可以手動執行System.gc(),通知GC運行,但是Java語言規範標準並不保證GC一定會執行,此時使用增量式GC可以縮短Java程序的暫停時間。

 

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