各種垃圾回收算法的通俗解釋(轉)

引用計數( Reference Counting )算法

1960 年以前,人們爲胚胎中的 Lisp 語言設計垃圾收集機制時,第一個想到的算法是引用計數算法。拿餐巾紙的例子來說,這種算法的原理大致可以描述爲: 

午餐時,爲了把腦子裏突然跳出來的設計靈感記下來,我從餐巾紙袋中抽出一張餐巾紙,打算在上面畫出系統架構的藍圖。按照“餐巾紙使用規約之引用計數版”的要求,畫圖之前,我必須先在餐巾紙的一角寫上計數值 1 ,以表示我在使用這張餐巾紙。這時,如果你也想看看我畫的藍圖,那你就要把餐巾紙上的計數值加 1 ,將它改爲 2 ,這表明目前有 2 個人在同時使用這張餐巾紙(當然,我是不會允許你用這張餐巾紙來擦鼻涕的)。你看完後,必須把計數值減 1 ,表明你對該餐巾紙的使用已經結束。同樣,當我將餐巾紙上的內容全部謄寫到筆記本上之後,我也會自覺地把餐巾紙上的計數值減 1 。此時,不出意外的話,這張餐巾紙上的計數值應當是 0 ,它會被垃圾收集器——假設那是一個專門負責打掃衛生的機器人——撿起來扔到垃圾箱裏,因爲垃圾收集器的惟一使命就是找到所有計數值爲 0 的餐巾紙並清理它們。 

引用計數算法的優點和缺陷同樣明顯。這一算法在執行垃圾收集任務時速度較快,但算法對程序中每一次內存分配和指針操作提出了額外的要求(增加或減少內存塊的引用計數)。更重要的是,引用計數算法無法正確釋放循環引用的內存塊,對此, D. Hillis 有一段風趣而精闢的論述: 

一天,一個學生走到 Moon 面前說:“我知道如何設計一個更好的垃圾收集器了。我們必須記錄指向每個結點的指針數目。” Moon 耐心地給這位學生講了下面這個故事:“一天,一個學生走到 Moon 面前說:‘我知道如何設計一個更好的垃圾收集器了……’” 

D. Hillis 的故事和我們小時候常說的“從前有座山,山上有個廟,廟裏有個老和尚”的故事有異曲同工之妙。這說明,單是使用引用計數算法還不足以解決垃圾收集中的所有問題。正因爲如此,引用計數算法也常常被研究者們排除在狹義的垃圾收集算法之外。當然,作爲一種最簡單、最直觀的解決方案,引用計數算法本身具有其不可替代的優越性。 1980 年代前後, D. P. Friedman , D. S. Wise , H. G. Baker 等人對引用計數算法進行了數次改進,這些改進使得引用計數算法及其變種(如延遲計數算法等)在簡單的環境下,或是在一些綜合了多種算法的現代垃圾收集系統中仍然可以一展身手。 

標記-清除( Mark-Sweep )算法

第一種實用和完善的垃圾收集算法是 J. McCarthy 等人在 1960 年提出併成功地應用於 Lisp 語言的標記-清除算法。仍以餐巾紙爲例,標記-清除算法的執行過程是這樣的: 

午餐過程中,餐廳裏的所有人都根據自己的需要取用餐巾紙。當垃圾收集機器人想收集廢舊餐巾紙的時候,它會讓所有用餐的人先停下來,然後,依次詢問餐廳裏的每一個人:“你正在用餐巾紙嗎?你用的是哪一張餐巾紙?”機器人根據每個人的回答將人們正在使用的餐巾紙畫上記號。詢問過程結束後,機器人在餐廳裏尋找所有散落在餐桌上且沒有記號的餐巾紙(這些顯然都是用過的廢舊餐巾紙),把它們統統扔到垃圾箱裏。 

正如其名稱所暗示的那樣,標記-清除算法的執行過程分爲“標記”和“清除”兩大階段。這種分步執行的思路奠定了現代垃圾收集算法的思想基礎。與引用計數算法不同的是,標記-清除算法不需要運行環境監測每一次內存分配和指針操作,而只要在“標記”階段中跟蹤每一個指針變量的指向——用類似思路實現的垃圾收集器也常被後人統稱爲跟蹤收集器( Tracing Collector ) 

伴隨着 Lisp 語言的成功,標記-清除算法也在大多數早期的 Lisp 運行環境中大放異彩。儘管最初版本的標記-清除算法在今天看來還存在效率不高(標記和清除是兩個相當耗時的過程)等諸多缺陷,但在後面的討論中,我們可以看到,幾乎所有現代垃圾收集算法都是標記-清除思想的延續,僅此一點, J. McCarthy 等人在垃圾收集技術方面的貢獻就絲毫不亞於他們在 Lisp 語言上的成就了。

複製( Copying )算法

爲了解決標記-清除算法在垃圾收集效率方面的缺陷, M. L. Minsky 於 1963 年發表了著名的論文“一種使用雙存儲區的 Lisp 語言垃圾收集器( A LISP Garbage Collector Algorithm Using Serial Secondary Storage )”。 M. L. Minsky 在該論文中描述的算法被人們稱爲複製算法,它也被 M. L. Minsky 本人成功地引入到了 Lisp 語言的一個實現版本中。 

複製算法別出心裁地將堆空間一分爲二,並使用簡單的複製操作來完成垃圾收集工作,這個思路相當有趣。借用餐巾紙的比喻,我們可以這樣理解 M. L. Minsky 的複製算法: 

餐廳被垃圾收集機器人分成南區和北區兩個大小完全相同的部分。午餐時,所有人都先在南區用餐(因爲空間有限,用餐人數自然也將減少一半),用餐時可以隨意使用餐巾紙。當垃圾收集機器人認爲有必要回收廢舊餐巾紙時,它會要求所有用餐者以最快的速度從南區轉移到北區,同時隨身攜帶自己正在使用的餐巾紙。等所有人都轉移到北區之後,垃圾收集機器人只要簡單地把南區中所有散落的餐巾紙扔進垃圾箱就算完成任務了。下一次垃圾收集的工作過程也大致類似,惟一的不同只是人們的轉移方向變成了從北區到南區。如此循環往復,每次垃圾收集都只需簡單地轉移(也就是複製)一次,垃圾收集速度無與倫比——當然,對於用餐者往返奔波於南北兩區之間的辛勞,垃圾收集機器人是決不會流露出絲毫憐憫的。 

M. L. Minsky 的發明絕對算得上一種奇思妙想。分區、複製的思路不僅大幅提高了垃圾收集的效率,而且也將原本繁紛複雜的內存分配算法變得前所未有地簡明和扼要(既然每次內存回收都是對整個半區的回收,內存分配時也就不用考慮內存碎片等複雜情況,只要移動堆頂指針,按順序分配內存就可以了),這簡直是個奇蹟!不過,任何奇蹟的出現都有一定的代價,在垃圾收集技術中,複製算法提高效率的代價是人爲地將可用內存縮小了一半。實話實說,這個代價未免也太高了一些。 

標記-整理( Mark-Compact )算法

標記-整理算法是標記-清除算法和複製算法的有機結合。把標記-清除算法在內存佔用上的優點和複製算法在執行效率上的特長綜合起來,這是所有人都希望看到的結果。不過,兩種垃圾收集算法的整合並不像 1 加 1 等於 2 那樣簡單,我們必須引入一些全新的思路。 1970 年前後, G. L. Steele , C. J. Cheney 和 D. S. Wise 等研究者陸續找到了正確的方向,標記-整理算法的輪廓也逐漸清晰了起來: 

在我們熟悉的餐廳裏,這一次,垃圾收集機器人不再把餐廳分成兩個南北區域了。需要執行垃圾收集任務時,機器人先執行標記-清除算法的第一個步驟,爲所有使用中的餐巾紙畫好標記,然後,機器人命令所有就餐者帶上有標記的餐巾紙向餐廳的南面集中,同時把沒有標記的廢舊餐巾紙扔向餐廳北面。這樣一來,機器人只消站在餐廳北面,懷抱垃圾箱,迎接撲面而來的廢舊餐巾紙就行了。 

實驗表明,標記-整理算法的總體執行效率高於標記-清除算法,又不像複製算法那樣需要犧牲一半的存儲空間,這顯然是一種非常理想的結果。在許多現代的垃圾收集器中,人們都使用了標記-整理算法或其改進版本。 

增量收集( Incremental Collecting )算法

對實時垃圾收集算法的研究直接導致了增量收集算法的誕生。 

最初,人們關於實時垃圾收集的想法是這樣的:爲了進行實時的垃圾收集,可以設計一個多進程的運行環境,比如用一個進程執行垃圾收集工作,另一個進程執行程序代碼。這樣一來,垃圾收集工作看上去就彷彿是在後臺悄悄完成的,不會打斷程序代碼的運行。 

在收集餐巾紙的例子中,這一思路可以被理解爲:垃圾收集機器人在人們用餐的同時尋找廢棄的餐巾紙並將它們扔到垃圾箱裏。這個看似簡單的思路會在設計和實現時碰上進程間衝突的難題。比如說,如果垃圾收集進程包括標記和清除兩個工作階段,那麼,垃圾收集器在第一階段中辛辛苦苦標記出的結果很可能被另一個進程中的內存操作代碼修改得面目全非,以至於第二階段的工作沒有辦法開展。 

M. L. Minsky 和 D. E. Knuth 對實時垃圾收集過程中的技術難點進行了早期的研究, G. L. Steele 於 1975 年發表了題爲“多進程整理的垃圾收集( Multiprocessing compactifying garbage collection )”的論文,描述了一種被後人稱爲“ Minsky-Knuth-Steele 算法”的實時垃圾收集算法。 E. W. Dijkstra , L. Lamport , R. R. Fenichel 和 J. C. Yochelson 等人也相繼在此領域做出了各自的貢獻。 1978 年, H. G. Baker 發表了“串行計算機上的實時表處理技術( List Processing in Real Time on a Serial Computer )”一文,系統闡述了多進程環境下用於垃圾收集的增量收集算法。 

增量收集算法的基礎仍是傳統的標記-清除和複製算法。增量收集算法通過對進程間衝突的妥善處理,允許垃圾收集進程以分階段的方式完成標記、清理或複製工作。詳細分析各種增量收集算法的內部機理是一件相當繁瑣的事情,在這裏,讀者們需要了解的僅僅是: H. G. Baker 等人的努力已經將實時垃圾收集的夢想變成了現實,我們再也不用爲垃圾收集打斷程序的運行而煩惱了

分代收集( Generational Collecting )算法

和大多數軟件開發技術一樣,統計學原理總能在技術發展的過程中起到強力催化劑的作用。 1980 年前後,善於在研究中使用統計分析知識的技術人員發現,大多數內存塊的生存週期都比較短,垃圾收集器應當把更多的精力放在檢查和清理新分配的內存塊上。這個發現對於垃圾收集技術的價值可以用餐巾紙的例子概括如下: 

如果垃圾收集機器人足夠聰明,事先摸清了餐廳裏每個人在用餐時使用餐巾紙的習慣——比如有些人喜歡在用餐前後各用掉一張餐巾紙,有的人喜歡自始至終攥着一張餐巾紙不放,有的人則每打一個噴嚏就用去一張餐巾紙——機器人就可以制定出更完善的餐巾紙回收計劃,並總是在人們剛扔掉餐巾紙沒多久就把垃圾撿走。這種基於統計學原理的做法當然可以讓餐廳的整潔度成倍提高。 

D. E. Knuth , T. Knight , G. Sussman 和 R. Stallman 等人對內存垃圾的分類處理做了最早的研究。 1983 年, H. Lieberman 和 C. Hewitt 發表了題爲“基於對象壽命的一種實時垃圾收集器( A real-time garbage collector based on the lifetimes of objects )”的論文。這篇著名的論文標誌着分代收集算法的正式誕生。此後,在 H. G. Baker , R. L. Hudson , J. E. B. Moss 等人的共同努力下,分代收集算法逐漸成爲了垃圾收集領域裏的主流技術。 

分代收集算法通常將堆中的內存塊按壽命分爲兩類,年老的和年輕的。垃圾收集器使用不同的收集算法或收集策略,分別處理這兩類內存塊,並特別地把主要工作時間花在處理年輕的內存塊上。分代收集算法使垃圾收集器在有限的資源條件下,可以更爲有效地工作——這種效率上的提高在今天的 Java 虛擬機中得到了最好的證明。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章