由於並非本人原著(我只是個“搬運工“)另外個人說明一下這里所說的GC指泛指垃圾回收機制,而單指Java或其他某種特定語言中的GC——可能具體語言中實現的垃圾回收實現機制會有所不同。下面是具體內容:將內存管理,尤其是內存空間的釋放實現自動化,這就是GC(Garbage Collection)
術語定義
1,垃圾:
所謂垃圾(Garbage),就是需要回收的對象。作為編寫程序的人,是可以做出“這個對象已經不需要了“這樣的判斷,但是計算機是做不到的。因此如果程序(通過某個變量等等)可能會直接或間接的引用一個對象,那么這個對象就被視為“存活“;與之相反,已經引用不到的則被視為“死亡“。將這些死亡對象找出來,然后作為垃圾進行回收,者就是GC的本質。
2,根
所謂的根(Root),就是判斷對象是否被引用的起始點。至於哪里的才是根,不通的語言和編譯器都有不通的規定,但基本上是將變量和運行棧空間作為根。
主要GC實現方式:
標記清除方式
標記清除(Mark and Sweep)是最早開發出來的GC算法(1960年)。它的原理非常簡單:
首先從根開始將可能被引用的對象用遞歸的方式進行標記,然后將沒有標記到的對象作為垃圾進行回收。
初始狀態:
標記階段:
清除階段:
上述圖片顯示了標記清除算法的大致原理。
“初始狀態“圖中顯示了隨着程序的運行而分配出一些對象的狀態,一個對象可以對其他的對象進行引用。
“標記階段“圖中顯示了GC開始執行,從根開始可以被引用的對象上進行“標記“。大多數情況下,這種標記是通過對象內部的標志(Flag)來實現的。於是,被標記的對象我們將它塗黑。
緊接着被標記的對象所能引用的對象也會被打上標記。重復這一步驟就可以從根開始可能被間接引用到的對象全部打上標記。到此為止的操作即被稱為——標記階段(Mark phase)。標記階段完成時,被標記的對象就是“存活“對象,反之為“死亡“對象。
標記清除算法的處理時間,是和存活對象數與對象總數的總和相關的。
作為標記清除的變形,還有一種叫做標記壓縮(Mark and Compat)的算法,它不是將被標記的對象清除,而是將他們不斷壓縮。
復制收集方式
標記清除算法有一個缺點,就是在分配了大量對象,並且其中只有一小部分存活的情況下,所消耗的時間會大大超過必要的值,這是應為在清除階段還需要對大量死亡對象進行掃描。
復制收集(Copy and Collection)則試圖克服這一缺點。在這種算法中,會將從根開始被引用的對象復制到另外的空間中,然后,再將復制的對象所能夠引用的對象用遞歸的方式不斷復制下去。
初始狀態(1)——舊空間:
新空間的開辟(2)——新空間:
復制對象(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,結果這些對象就永遠不會被釋放。
必須在引用發生增減時對引用計數做出正確的增減,而如果漏掉或者更改了引用計數就會引發很難找到的內存錯誤。
引用計數不適合並行處理。如果多個線程同時對引用計數進行增減的話,引用計數的值就可能會產生不一致的問題(結果就會導致內存錯誤),為了避免這樣的事情發生,對引用計數的操作必須采用獨占的方式來進行。如果引用計數操作頻繁發生,每次使用都要使用加鎖等並發操作其開銷也不可小覷。