前面文章中從理論上介紹了物件存活判定(這裡為可達性分析演算法)和垃圾收集演算法,而在HotSpot虛擬機器上實現這些演算法時,必須對演算法的執行效率有嚴格的考量,才能保證虛擬機器高效執行。

HotSpot虛擬機器在發生GC時所產生的問題以及解決這些問題的方案

問題提出一(時間):

1. 在可達性分析演算法來判定物件是否存活時,需要執行的動作是檢查該物件是否被引用鏈引用,可以預見實際的應用中物件是很多的,這裡逐個檢查會佔用很多的時間。

2. 可達性分析演算法分析結果準確的前提是GC停頓。也是佔用時間。

針對時間問題的解決方案:

上面問題產生的原因是,虛擬機器因為不知道哪些地方存放著的是物件的引用,他必須笨笨的去全域性檢查。假如虛擬機器在類載入完成的時候就已經知道哪些地方存放的是物件的引用,那麼就不用再去瞎找一番。目前的虛擬機器使用的都是準確式GC有辦法直接得知哪些地方存放著物件的引用。在HotSpot的實現中使用一組成為OopMap的資料結構來在特定位置記錄哪些些地方存放的是引用。這樣在 GC 發生時,HotSpot 就可以直接掃描 OopMap 來獲取物件引用的儲存位置,從而進行 GC Roots 列舉。也相應的減少了GC停頓的時間。OopMap的作用就是幫助HotSpot虛擬機器快速且準確的完成GC Roots列舉。

問題提出二(空間):

在OopMap的協助下,HotSpot可以快速準確的完成GC Roots,但是一個很現實的問題隨之而來:可能導致引用關係的變化(為什麼會導致這個變化,是我太較真了嗎),或者說OopMap內容變化的指令很多,如果為每一條指令都生成對應的OopMap,那麼會產生大量的額外空間,這樣GC的空間成本很高。

針對空間問題的解決方案:

前面講到HotSpot只是在 “特定的位置”記錄了OopMap資訊,這些位置成為安全點。安全點即程式執行時並非在所有的地方都能停頓下來開始GC(不能隨地大小便),只有在到達安全點時才能暫停。安全點的選定既不能太少以致於讓GC等待時間太長,也不能太過於頻繁以致於過分增大執行時的負荷。因此,安全點的選定基本上是以程式“是否具有讓程式長時間執行的特徵”來選定的長時間執行即指令序列複用,例如方法呼叫、迴圈跳轉、異常跳轉等

小問題1:在GC發生時,如果程式還沒跑到最近的安全點該怎麼辦?

有兩種方案選擇:

搶先式中斷:搶先式中斷不需要執行緒的執行程式碼主動去配合,在GC發生時,首先把所有執行緒全部中斷,如果發現有執行緒中斷的地方不在安全點上,就恢復該執行緒讓他跑到安全點。現在虛擬機器都不採用這種方式。

主動式中斷: 當GC需要中斷時,不直接對執行緒操作,僅僅簡單設定一個標誌,各執行緒主動去輪詢這個標誌,發現中斷標誌時就自己中斷掛起。輪詢標誌的地方和安全點是重合的,另外再加上建立物件需要分配記憶體的地方。

問題提出三(執行緒sleep或者blocked):

安全點機制保證了程式執行時,在不太長的時間內就會遇到可進入GC的安全點。但是對於不執行(即沒有分配CPU時間)的程式,如執行緒處於sleep或Blocked狀態,執行緒就無法響應JVM的中斷請求,走到安全點去掛起。JVM也不太可能等待執行緒重新被分配CPU時間。這時就需要安全區域來解決了。

安全區域即在一段程式碼片段中,引用關係不會發生變化。在這個區域任意地方開始GC都是安全的。可以看作是被擴充套件了的安全點。

執行緒到達安全區域時,先標識自己進入安全區域。當這段時間內JVM要發起GC時,就不用管標識安全區域狀態的執行緒了。當執行緒要離開安全區域時,先檢查系統是否完成了根結點列舉或整個GC過程,完成了就繼續執行,沒有就等待直到收到可以安全離開安全區域的訊號為止。