為什麼「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垃圾回收計算機科學 |