1. 程式人生 > >java垃圾回收期如何工作(編程思想)

java垃圾回收期如何工作(編程思想)

and 指向 其他 單獨 垃圾回收機制 常用 表格 周期 真的

垃圾回收器如何工作:
在以前的程序語言中,在堆上分配對象的代價十分昂貴,因此讀者會自然覺得對Java中所有對象(基本類型除外)都在堆上分配的方式也非常高昂。然而,垃圾回收期對提高對象的創建速度,卻具有明顯的效果。
打個比方,你可以吧C++裏面的堆想象成一個院子,裏面的每個對象都負責管理自己的地盤。一段時間之後,對象可能被銷毀,但地盤必須加以重用。在某些Java虛擬機中,堆的實現截然不同:它更像一個傳送帶,每分配一個對象,它就往前移動一格。這意味著對象存儲空間的分配速度非常快。Java的“堆指針”只是簡單地移動到尚未分配的區域,其效率比得上C++在堆棧上分配空間的效率。
java中的堆並非完全像傳送帶那樣工作。要是真的那樣的惡化,勢必會導致頻繁的內存頁面調度————將其移進移出硬盤,因此會顯得需要擁有比實際需要更多的內存。頁面調度會顯著地影響性能,最終,在創建了足夠多的翠香之後,內存資源將耗盡。其中的秘密在於垃圾回收器的介入。當它工作時,將一面回收空間,一面使堆中的對象緊湊排列,這樣“堆”指針就可以很容易移動到更靠近傳送帶的開始處,也就避免了頁面錯誤。通過垃圾回收器對對象重新排序,實現了一種高速的、有無限空間可供分配的堆模型。
想要更好地理解Java中的垃圾回收,可以了解一下其他系統中的垃圾回收機制。引用計數器是一種簡單但速度很慢的垃圾回收技術。每個對象都含有一個引用計數器,讓有引用連接至對象時,引用技術加1.當對象離開作用域或被置為null時,引用計數器減少。雖然管理引用技術的開銷不大,但這項開銷在整個程序生命周期中持續發生。垃圾回收期會在含有全部對象的列表上遍歷,當發現某個對象的引用計數為0時,就釋放其占用的空間(但是,引用計數模式經常會在計數值變成0時立即釋放對象)。這種方法有個缺陷,如果對象之間存在循環引用,可能出現“對象應該被回收,但引用計數卻不為零”的情況。對於垃圾回收器而言,定位這樣的交互自引用的對象組所需的工作量極大。引用計數常用來說明垃圾收集的工作方式,但似乎從未被應用於任何一種Java虛擬機實現中。
在一些更快的模式中,垃圾回收期並非基於引用計數技術。它們依據的思想是:對任何“活”的對象,一定能最終追溯到其存貨在堆棧或靜態存儲區之中的引用。這個引用鏈條可能會穿過數個對象層次。由此,如果從堆棧和靜態存儲區開始,遍歷所有的引用,就能找到所有“活"的對象。對於發現的每個引用,必須追蹤它鎖引用的對象,然後是此對象包含的而所有引用,如此反復進行,知道“根源於堆棧和靜態存儲區的引用”形成的網絡全部被訪問為止。你所訪問過的對象必須都是“活”的。註意,這就解決了“交互自引用的對象組”的問題————這種現象根本不會被發現,因此也就被自動回收了。
在這種方式下,Java虛擬機將采用一種自適應的垃圾回收技術。置於如何找到的存活對象,取決於不同的java虛擬機實現。有一種做法名為停止——賦值(stop and copy)。顯然意味著,先暫停程序的運行(所以它不屬於後臺回收模式),然後將所有存活的對象從當前堆賦值到另一個堆,沒有被賦值的全部都是垃圾,當對象被賦值到新堆時,它們是一個挨著一個的,所以新堆保持緊湊排列,然後就可以按前述方法簡單、直接地分配新空間了。
當把對象從一處搬到另一處時,所有指向它的那些引用都必須修正。位於堆或者靜態存儲區的引用可以直接被修正,但可能還有其他指向這些對象的引用,它們在遍歷的過程中才能被找到(可以想象成有個表格,講話舊地址映射至新地址)
對於這種所謂的復制式回收器而言,效率會降低,這有兩個原因。首先,得有兩個堆,然後得在這兩個分離的堆之間來回搗騰,從而得維護比實際需要多1倍的空間。某些Java虛擬機對此問題的處理方式是,按需從堆中分配幾塊較大的內存,復制動作發生在這些大塊內存之間。
第二個問題在於復制。程序進入穩定狀態之後,可能只會產生少量垃圾,甚至沒有垃圾,盡管如此,復制式回收器仍然會將所有內存自一處復制到另一處,這很浪費。為了避免這種情形,一些java虛擬機會進行檢查:要是沒有新垃圾產生,就會轉換到另一種工作模式(即“自適應”)。對一般用途而言,“標記——清掃”方式速度相當慢,但是當你知道只會產生少量垃圾甚至不會產生垃圾時,它的速度就很快了。
“標記——清掃”鎖依據的思路同樣是從堆棧和靜態存儲區出發,遍歷所有的引用,從而找出所有存活的對象。每當它找到一個存活對象,就會給對象設一個標記,這個過程中不會回收任何對象。只有全部標記工作完成時,清理工作才會開始。在清理過程中,沒有標記的對象將被釋放,不會發生任何復制操作。所以剩下的堆空間是不連續的,垃圾回收器要是希望得到連續空間的話,就得重新整理剩下的對象。
“停止——復制”的意思是這種垃圾回收動作不是在後臺進行的;相反,垃圾回收器動作發生的同事,程序將會被暫停。在Sun公司的文檔中會發現,許多參考文檔將垃圾回收視為低優先級的後臺進程,但實際上垃圾回收器在Sun公司早期版本的java虛擬機中並非以這種方式實現的。當可用內存數量較低的時,Sun版本的垃圾回收器會暫停運行程序,同樣,“標記-清掃”工作也必須在程序暫停的情況下才能進行。
如上文所述,在這裏所討論的Java虛擬機中,內存分配以較大的“塊”為單位。如果對象較大,它會占用單獨的塊。嚴格來說,“停止-復制”要求在釋放舊有對象之前,必須先把所有的存貨兌現從舊堆復制到新堆,這將導致大量內存復制行為。有了快之後,垃圾回收期在回收的時候就可以往飛起的塊裏拷貝對象了。每個塊都用相應的代數(generation count)來記錄它是否存活。通常,如果快被某處引用,其代數會增加,垃圾回收期會定期進行完整的清理動作————大型對象仍然不會被復制(只是其代數會增加),內含小型對象的那些塊則被復制並整理。java虛擬機會進行監視,如果所有對象都很穩定,垃圾回收器的效率降低的話,就切換到“標記-清掃”方式;同樣,java虛擬機會跟蹤“標記-清掃”的效果,要是堆空間出現很多碎片,就會切換回“停止-復制”的方式。這就是“自適應”技術,你可以給它個啰嗦的稱呼:“自適應的、分代的、停止-復制、標記-清掃”式垃圾回收器。

java垃圾回收期如何工作(編程思想)