HotSpot 對算法的實現

前面垃圾收集器概述垃圾收集算法兩節從理論上介紹了對象存活判斷算法和垃圾收集算法,本節則主要介紹HotSpot對上述算法的實現規則。

枚舉根節點

可達性算法中從GC Roots節點找到引用鏈這個操作可能會因爲巨大的方法區而導致大量時間的消耗,而且可達性分析會因爲GC停頓的原因而導致執行時間的敏感。

由於目前的主流java虛擬機使用的都是準確式GC,所以當執行系統停頓下來之後,並不需要一個不漏的檢查完所有執行上下文和全局的引用位置,因爲虛擬機是有辦法直接得知哪些地方存放着對象引用的。那麼是怎麼實現的呢?在HotSpot的實現中,是用一組稱爲OopMap的數據結構來達到這個目的的。在類加載完成的時候HotSpot就把對象內什麼偏移量上是什麼類型的數據計算出來,在JIT編譯過程中也會在特定的位置記錄下棧和寄存器中哪些位置是引用。這樣,GC在掃描時就可以直接得知這些信息了。

垃圾收集必須在一個能確保一致性的快照中進行,所謂“一致”是指在整個分析期間整個執行系統看起來就像被凍結在一個時間點上,不可能出現分析過程中對象引用關係不斷變化的情況,如果不能保持這種一致性就無法保證結果的準確性。
這是導致GC進行時必須停頓所有java執行線程(Stop The World)的其中一個原因。即使是在號稱幾乎不會發生停頓的CMS收集器(一款垃圾收集器)中,枚舉根節點時也是必須要停頓的。

由於目前的主流java虛擬機使用的都是準確式GC,所以當執行系統停頓下來之後,並不需要一個不漏的檢查完所有執行上下文和全局的引用位置,因爲虛擬機是有辦法直接得知哪些地方存放着對象引用的。那麼是怎麼實現的呢?在HotSpot的實現中,是用一組稱爲OopMap的數據結構來達到這個目的的。在類加載完成的時候HotSpot就把對象內什麼偏移量上是什麼類型的數據計算出來,在JIT編譯過程中也會在特定的位置記錄下棧和寄存器中哪些位置是引用。這樣,GC在掃描時就可以直接得知這些信息了。

準確式GC:就是讓JVM知道內存中某位置數據的類型什麼。比如當前內存位置中的數據究竟是一個整型變量還是一個引用類型。這樣JVM可以很快確定所有引用類型的位置,從而更有針對性的進行GC roots枚舉。
保守式GC:就是JVM並不知道內存中某位置數據的類型是什麼。

安全點

選擇安全點

在OopMap協助下,HotSpot可以準確地完成GC Roots的枚舉。但一個很現實的問題隨之而來:可能導致引用關係發生變化,或者說OopMap內容變化的指令非常多,如果爲每一條指令都生成對應的Oomap,那將需要大量的額外空間,這樣GC的空間成本會非常高。

因此,HotSpot沒有爲每條指令都生成OopMap,只是在特定位置記錄這些信息。這些位置稱爲安全點(Safepoint)。即程序執行時並非在所有位置都能停下來GC,只有到達安全點纔可以暫停。安全點的選定不能太少以至於讓GC等待時間太長,也不能過於頻繁以至於過分增大運行時的負荷。

所以安全點選定規則就變成了:是否具有讓程序長時間執行的特徵

因此具有較長運行時間的指令才能被選爲安全點,如方法調用、循環跳轉、異常跳轉等。

停頓到安全點

接下來要考慮的便是,如何在GC時保證所有的線程都“跑”到安全點上停頓下來。

這裏有兩種方案:搶先式中斷(Preemptive Suspension)和主動式中斷(Voluntary Suspension)。

  • 搶先式中斷會把所有線程中斷,如果某個線程不在安全點上,就恢復讓它跑到安全點上。幾乎沒有虛擬機採用這種方式。
  • 主動式中斷思想是設立一個GC標誌,各個線程會輪詢這個標誌並在需要時自己中斷掛起。這樣,標誌和安全點是重合的。

安全區域

安全點機制可以保證某一程序在運行的時候,在不長的時間裏就可以進入GC的安全點。但是如果程序沒有分配CPU時間,例如處於Sleep狀態或者Blocked狀態,這時候線程無法響應JVM的中斷請求。對於這種情況,只能用安全區域(Safe Region)來解決。

安全區域是指在一段代碼片段之中,引用關係不會發生變化。在這個區域中任意地方開始都是安全的。

  • 在線程執行到安全區域中的代碼時,就標記自己已經進入了安全區域,這樣JVM在發起GC時就跳過這些線程。
  • 在線程要離開安全區域時,它要檢查系統是否已經完成了枚舉(或GC過程),如果完成了線程就繼續執行,否則就等待。

參考文獻

JVM 之 OopMap 和 RememberedSet
《垃圾回收的算法與實現》—— 保守式GC

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章