為什麼「GC標記-清除演算法」與「寫時複製技術(copy-on-write)」不兼容?

在《垃圾回收演算法與實現-中村成洋-2010》一書中,(以下內容來自這本書,我只是摘錄了,侵刪)

首先,什麼是GC標記-清除演算法

GC標記-清除演算法:標記階段是 把所有活動對象都做上標記的階段。清除階段是把那些沒有標記的對象,也就是非活動對象 回收的階段。通過這兩個階段,就可以令不能利用的內存空間重新得到利用。

其中「GC標記-清除演算法」的缺點有一條是:與寫時複製技術(copy-on-write)不兼容。

寫時複製技術(copy-on-write)是在 Linux 等眾多 UNIX 操作系統的虛擬存儲中用到的高速化方法。

打個比方,在 Linux 中複製進程,也就是使用 fork() 函數時,大部分內存空間都不會被複制。只是複製進程,就複製了所有內存空間的話也太說不過去了吧。因此,寫時複製技術只是裝作已經複製了內存空間,實際上是將內存空間共享了。在各個進程中訪問數據時,能夠訪問共享內存就沒什麼問題了。

然而,當我們對共享內存空間進行寫入時,不能直接重寫共享內存。因為從其他程序訪問時,會發生數據不一致的情況。在重寫時,要複製自己私有空間的數據,對這個私有空間 進行重寫。複製後只訪問這個私有空間,不訪問共享內存。

像這樣,因為這門技術是「在寫入時進行複製」的,所以才被稱為寫時複製技術。

這樣的話,GC 標記 - 清除演算法就會存在一個問題 : 與寫時複製技術不兼容。即使沒重寫對象,GC 也會設置所有活動對象的標誌位,這樣就會頻繁發生本不應該發生的複製, 壓迫到內存空間。

為了處理這個問題,我們採用點陣圖標記(bitmap marking)的方法。關於這個方法,將在 2.6 節中介紹。

我的問題是,我還是看不懂上面的解釋,為什麼不兼容?

「 這樣的話,GC 標記 - 清除演算法就會存在一個問題 : 與寫時複製技術不兼容。即使沒重寫對象,GC 也會設置所有活動對象的標誌位,這樣就會頻繁發生本不應該發生的複製, 壓迫到內存空間。」

怎麼就 「GC 也會設置所有活動對象的標誌位,這樣就會頻繁發生本不應該發生的複製」 ?

這個我不懂。


剛好在前一段時間買了這本書並且看了一半。

說不上一定正確只是分享一下我的見解。

題主的問題是,看不懂那段話為什麼說明了 「 GC標記-清除演算法」與「寫時複製技術(copy-on-write) 」 不兼容。

換一個說法。

你硬要把標記清除演算法和寫時複製弄在一起行不行?

行。所謂的不兼容,意思是這樣的結合方式並沒有發揮到寫時複製技術的優點,和不用寫時複製沒有太大區別

寫時複製技術的優點是什麼?

fork進程的時候,內存空間是引用而不是複製的

意味著執行完fork後的兩個進程的內存指向的是同一片區域。但是進程可能會執行不同的操作,產生不同的數據,這時候才在其他空閑內存區域寫入一個新的對象並改寫原指針。如果兩個程序的大部分內存數據都是相同的,那麼這個技術能省下非常多的內存空間

而GC標記-清除演算法做了什麼?

回到書本第一章第一節關於對象的 「 頭 」 的描述。

「 ...比如將在第 2 章中介紹的 GC 標記 - 清除演算法,就是在對象的頭部中設置 1 個 flag (標誌位)來記錄對象是否已標記,從而管理各個對象。... 」(書中原話)

在每一個執行 GC 的時候,標記清除演算法都會對被引用的對象進行標記操作。被引用的對象僅僅意味著這個對象不是垃圾,不能被清除,而不表示對象的內容已經被改寫了。

試想一種情況:在fork後,兩個程序都沒有生成新的數據,也就是說所有對象的域都不變

這時候執行一次 GC 標記-清除演算法,標記階段將所有被引用的對象的 flag 進行一個置位操作。這種操作的意義等同於改動了數據。這時候 「 寫時複製技術 」 會判斷對象已經被改動,從空閑內存中寫入新的對象並改寫原指針。

一次 GC 標記-清除演算法 執行完以後,已經不存在共享的對象了,每一個對象都被複制了一遍。

這就是我理解的 「 頻繁發生本不應該發生的複製 」 的原因。

以上是我的想法。

有任何錯誤,歡迎大家指出。


推薦閱讀:

既然JVM有Full GC,為什麼還會出現OutOfMemoryError?
當jvm的eden區滿了,進行回收時,s0區滿了,此時eden區還有存活對象沒複製完,會怎樣?
Go1.6中的gc pause已經完全超越JVM了嗎?
問下, C++ 的垃圾回收機制 如何實現的話 要怎麼掃描 bss data 之類的欄位?
XMLHttpRequest對象的生命周期是如何管理的?

TAG:Java虛擬機JVM | GC垃圾回收計算機科學 |