《跟我一起學JVM》之HotSpot的垃圾收集算法實現

人生如戲!主角是自己,如果你足夠努力,你可以成為這部戲的導演、編劇、製片,沒有什麼不可能,自己的戲自己演,自己的飯自己吃,自己的路自己走,自己的人生自己活!

《跟我一起學JVM》之HotSpot的垃圾收集算法實現

關注一下,更多精彩等著你

枚舉根節點

從可達性分析中從GC Roots節點找引用鏈這個操作為例,可作為GC Roots的節點主要在全局性的引用(例如常量或類靜態屬性)與執行上下文(例如棧幀中的本地變量表)中,現在很多應用僅僅方法區就有數百兆,如果要逐個檢查這裡面的應用,那麼必然會消耗很多時間。

另外,可達性分析對執行時間的敏感還體現在GC停頓上,因為這項分析工作必須在一個能保證一致性的快照中進行——這裡“一致性”的意思是指在整個分析期間整個執行系統看起來就像被凍結在某個時間點上,不可以出現分析過程中對象引用關係還在不斷變化的情況,該點不滿足的話分析結果準確性就無法得到保證。這點是導致GC進行時必須停頓所有Java執行線程的其中一個重要原因,即使是在號稱(幾乎)不會發生停頓的CMS收集器中,枚舉根節點時也是必須要停頓的。

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

安全點

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

實際上,HotSpot也的確沒有為每條指令都生成OopMap,前面已經提到,只是在“特定位置”記錄了這些信息,這些位置稱為安全點(Safepoint),即程序執行時並非在所有地方都能停頓下來開始GC,只有在到達安全點時,才能暫停。Safepoint的選定既不能太少以至於讓GC等待時間太長,也不能過於頻繁以至於過分增大運行時的負荷。所以,安全點的選定基本上是以程序“是否具有讓程序長時間執行的特徵”為標準進行選定的——因為每條指令執行的時間都非常短暫,程序不太可能因為指令流長度太長這個原因而過長時間運行,“長時間執行”的最明顯特徵就是指令序列複用,例如方法調用、循環跳轉、異常跳轉等,所以具有這些功能的指令才會產生Safepoint。

對於SafePoint,另一個需要考慮的問題是如何在GC發生時讓所有線程(這裡不包括執行JNI調用的線程)都“跑”到最近的安全點上再停頓下來。這裡有兩種方案可供選擇:搶先式中斷(Preemptive Suspension)和主動式中斷(Voluntary Suspension),其中搶先式中斷不需要線程的執行代碼主動去配合,在GC發生時,首先把所有線程全部中斷,如果發現有線程中斷的地方不在安全點上,就恢復線程,讓它“跑”到安全點上。現在幾乎沒有虛擬機實現採用搶先式中斷來暫停線程從而相應GC事件。

而主動式中斷的思想是當GC需要中斷線程的時候,不直接對線程操作,僅僅簡單地設置一個標誌,各個線程執行時主動去輪詢這個標誌,發現中斷標誌為真時就自己中斷掛起。輪詢標誌的地方和安全點時重合的,另外再加上創建對象需要分配內存的地方。

安全區域

使用Safepoint似乎已經完美地解決了如何進入GC的問題,但實際情況卻並不一定。Safepoint機制保證了程序執行時,在不太長的時間內就會遇到可進入GC的Safepoint。但是,程序“不執行”的時候呢?所謂的程序不執行就是沒有分配CPU時間,典型的例子就是線程處於Sleep狀態或者Blocked狀態,這時候線程無法響應JVM的中斷請求,“走”到安全的地方去中斷掛起,JVM也顯然不太可能等待線程重新被分配CPU時間。對於這種情況,就需要安全區域(Safe Region)來解決。

安全區域是指在一段代碼片段之中,引用關係不會發生變化。在這個區域中的任意地方開始GC都是安全的。我們也可以把Safe Region看做是被擴展了的Safepoint。

在線程執行到Safe Region中的代碼時,首先標識自己已經進入了Safe Region,那樣,當在這段時間裡JVM要發起GC時,就不用管標識自己為Safe Region狀態的線程了。在線程要離開Safe Region時,它要檢查系統是否已經完成了根節點枚舉(或者是整個GC過程),如果完成了,那線程就繼續執行,否則它就必須等待知道收到安全離開Safe Region的信號為止。

擴展閱讀:

《跟我一起學JVM》之運行時數據區域

《跟我一起學JVM》之對象是怎麼創建的

《跟我一起學JVM》之對象的內存佈局

《跟我一起學JVM》之對象在JVM中的訪問方式

《跟我一起學JVM》之如何判斷對象“已死”

《跟我一起學JVM》之垃圾收集算法


分享到:


相關文章: