GC 的三種基本實現方式

因爲並不是本人原著(我只是個「搬運工「)另外我的說明一下這裏所說的GC指泛指垃圾回收機制,而單指Java或其餘某種特定語言中的GC——可能具體語言中實現的垃圾回收實現機制會有所不一樣。下面是具體內容:將內存管理,尤爲是內存空間的釋放實現自動化,這就是GC(Garbage Collection)算法

 

術語定義

1,垃圾:緩存

所謂垃圾(Garbage),就是須要回收的對象。做爲編寫程序的人,是能夠作出「這個對象已經不須要了「這樣的判斷,可是計算機是作不到的。所以若是程序(經過某個變量等等)可能會直接或間接的引用一個對象,那麼這個對象就被視爲「存活「;與之相反,已經引用不到的則被視爲「死亡「。將這些死亡對象找出來,而後做爲垃圾進行回收,者就是GC的本質。
2,根
所謂的根(Root),就是判斷對象是否被引用的起始點。至於哪裏的纔是根,不通的語言和編譯器都有不通的規定,但基本上是將變量和運行棧空間做爲根。
併發

主要GC實現方式:

標記清除方式

標記清除(Mark and Sweep)是最先開發出來的GC算法(1960年)。它的原理很是簡單: 
首先從根開始將可能被引用的對象用遞歸的方式進行標記,而後將沒有標記到的對象做爲垃圾進行回收。函數

初始狀態:性能

 

 標記階段:線程

 

清除階段:3d

上述圖片顯示了標記清除算法的大體原理。
「初始狀態「圖中顯示了隨着程序的運行而分配出一些對象的狀態,一個對象能夠對其餘的對象進行引用。

「標記階段「圖中顯示了GC開始執行,從根開始能夠被引用的對象上進行「標記「。大多數狀況下,這種標記是經過對象內部的標誌(Flag)來實現的。因而,被標記的對象咱們將它塗黑。

緊接着被標記的對象所能引用的對象也會被打上標記。重複這一步驟就能夠從根開始可能被間接引用到的對象所有打上標記。到此爲止的操做即被稱爲——標記階段(Mark phase)。標記階段完成時,被標記的對象就是「存活「對象,反之爲「死亡「對象。

標記清除算法的處理時間,是和存活對象數與對象總數的總和相關的。

做爲標記清除的變形,還有一種叫作標記壓縮(Mark and Compat)的算法,它不是將被標記的對象清除,而是將他們不斷壓縮。

複製收集方式
標記清除算法有一個缺點,就是在分配了大量對象,而且其中只有一小部分存活的狀況下,所消耗的時間會大大超過必要的值,這是應爲在清除階段還須要對大量死亡對象進行掃描。

複製收集(Copy and Collection)則試圖克服這一缺點。在這種算法中,會將從根開始被引用的對象複製到另外的空間中,而後,再將複製的對象所可以引用的對象用遞歸的方式不斷複製下去。

初始狀態(1)——舊空間:對象

新空間的開闢(2)——新空間:blog

 

複製對象(3)遞歸

如上圖:
(1)部分是GC開始前的內存狀態,者也同時表明着對象在內存中所佔用的「舊空間「。
圖(2)在舊空間之外開闢「新空間「並將可能從根被引用的對象複製到新空間中。
圖(3)從已經複製的對象開始再將能夠被引用的對象逐個複製到新空間當中……隨着複製的進行,直到複製完成——最終「死亡「對象就留在了「舊空間「當中,接着將舊空間廢棄掉,這樣就能夠將「死亡「對象所佔用的空間一口氣釋放出來,而沒有必要再次掃描「死亡「對象的必要。而等到下次GC操做是,此次所建立的「新空間「就成爲了未來的「舊空間「了。

複製收集方式的過程至關於只存在於標記清除方式中的標記階段。因爲清除階段中須要對全部對象進行掃描,這樣若是在存在大量對象,且其中大量對象已經爲「死亡「對象的狀況下必然會形成沒必要要的資源和性能上的開銷。
而在複製收集方式中就不存在這樣的開銷。可是和標記相比,將對象複製一份的開銷相對要大,所以在「存活「對象相對比例較高的狀況下,反而不利。

複製收集方式的另外一個優勢是:它具備局部性(Locality)。在複製收集過程當中,會按照對象被引用的順序將對象複製到新空間中。因而,關係較近的對象被放置在距離較近的內存空間中的可能性會提升,這樣被稱爲局部性。局部性高的狀況下,內存緩存會更容易有效運做,程序的運行也可以獲得提升。

引用計數方式
引用計數方式是GC算法中最簡單也最容易實現的一種,它和標記清除方式差很少是同一時間被髮明出來的。

它的原理是:在每一個對象中保存該對象的引用計數,當引用發生增減時對計數進行更新。
引用計數的增減,通常發生在變量複製,對象內容更新,函數結束(局部變量不在被引用),等時間點。當一個對象的引用計數爲0時,則說明它未來不會再被引用,所以能夠釋放相應的內存空間。
1)

2)

 

3)

 

如上圖:
(1)中全部對象都保存着本身被多少個對象進行引用的數量(引用計數)——圖中右上角的的數字。
(2)當對象引用發生變化時,引用計數也會更者變化。在這裏圖中的對象B到D的引用實效後,對象D的引用計數變爲0,因爲對象D的引用計數變爲0,所以D到E和C的引用計數也分=別減小。結果E的引用計數也變爲0,因而想象E也會被釋放。
(3)引用計數爲0的對象被釋放——「存活」對象被保留下來。而這個GC過程當中不須要對全部對象進行掃描。

優勢
相比標記清除和複製收集方式實現更容易。
當對象再也不被引用的瞬間就會被釋放。
其餘GC機制中,要預測一個對象什麼時候會被釋放是很困難的,而在引用計數方式中則是當即被釋放。
因爲釋放操做是針對個別執行的,所以和其餘算法相比,由GC而產生的中斷時間就比較短。
缺點

 

沒法釋放循環引用的對象。如上圖A,B,C三個對象沒有被其餘對象引用,而是互相之間循環引用,所以他們的計數永遠不會爲0,結果這些對象就永遠不會被釋放。必須在引用發生增減時對引用計數作出正確的增減,而若是漏掉或者更改了引用計數就會引起很難找到的內存錯誤。引用計數不適合並行處理。若是多個線程同時對引用計數進行增減的話,引用計數的值就可能會產生不一致的問題(結果就會致使內存錯誤),爲了不這樣的事情發生,對引用計數的操做必須採用獨佔的方式來進行。若是引用計數操做頻繁發生,每次使用都要使用加鎖等併發操做其開銷也不可小覷。

相關文章
相關標籤/搜索