JVM HotSpot 可達性分析算法實現細節


本文部分摘自《深入理解 Java 虛擬機第三版》


根節點枚舉

在之前關於可達性分析算法的介紹中我們講過,我們需要先找出可固定作爲 GC Roots 的節點,然後沿着引用鏈去尋找那些無用的垃圾對象。GC Roots 節點一般在全局性引用(例如常量和類靜態屬性)與執行上下文(例如棧幀中的本地變量表)中,儘管目標明確,但查找過程要做到高效並非一件易事,若要逐個查找可作爲起源的引用肯定需要消耗不少時間

迄今爲止,所有收集器在根節點枚舉這一步驟時都是必須暫停用戶線程,也即 Stop The World,因爲如果在分析過程中出現根節點集合中對象的引用關係仍在不斷變化的情況,分析結果的準確性也就無法保證了

在對棧內存進行分析時,虛擬機會看哪些位置存儲了 Reference 類型,如果發現某個位置確實存的是 Reference 類型,就意味着它所引用的對象這一次不能被回收。但問題是,棧幀的本地變量表裏面只有一部分數據是 Reference 類型的,那些非 Reference 類型(基本數據類型)的數據對我們毫無用處,但我們還是不得不對整個棧全部掃描一遍,這是對時間和資源的一種浪費。在 HotSpot 的解決方案中採用了一組稱爲 OopMap 的數據結構來實現直接找到對象引用,一旦類加載動作完成,HotSpot 就會把棧中代表引用的位置全部記錄下來,這樣收集器在掃描時就可以直接得知這些消息了


安全點

儘管有了 OopMap,但如果引用關係經常變化,虛擬機就需要爲每一條指令都生成對應的 OopMap,這將會佔用大量的額外存儲空間

HotSpot 當然沒那麼笨,它只會在特定的位置去記錄這些信息,這些位置被稱爲安全點(SafePoint)。有了安全點的設定,用戶程序就必須執行到安全點才能暫停,而不是在代碼指令流的任意位置隨意停頓。安全點的選定不能太少,讓收集器等待時間過長,也不能太頻繁,導致增大運行時內存負擔。安全點的位置選定基本上是以“是否具有讓程序長時間執行的特徵”爲標準進行選定,“長時間執行”的最明顯特徵就是指令序列的複用,例如方法調用、循環跳轉、異常跳轉等,只有具有這些功能的指令才能產生安全點

對於安全點,另外一個要考慮的問題就是,如何在垃圾收集發生時讓所有線程都跑到最近的安全點。一般有兩種方案可供選擇:

  • 搶先式中斷:垃圾收集發生時,系統首先把所有用戶線程全部中斷,如果發現有用戶線程中斷的地點不在安全點上,就恢復該線程執行,直至跑到安全點再中斷。現實中幾乎沒有虛擬機會採用搶先式中斷
  • 主動式中斷:垃圾收集發生時,不直接對線程操作,而是設置一個標誌位,各個線程在執行時會不停地主動去輪詢這個標誌,一旦發現標誌位爲真就在最近的安全點主動中斷

安全區域

安全點看似解決了我們遇到的問題,但還有一個需要思考的點:如果某一個用戶線程正好處於“不執行”狀態該怎麼辦?所謂“不執行”就是沒有分配處理器時間片,典型的場景如用戶線程處於 Sleep 或 Blocked 狀態,這時線程無法響應中斷請求,自然也就不能走到安全點主動掛起自己,而虛擬機也不可能持續等待線程重新被分處理器時間片。對於這種情況,就需要引入安全區域(Safe Region)來解決

安全區域是指能夠確保在某一代碼片段中,引用關係不會發生變化,因此,在這個區域中任意地方開始垃圾收集都是安全的。我們也可以把安全區域看作是被擴展拉伸了的安全點

當用戶線程執行到安全區域時,首先會標識自己已經進入安全區域,那樣當這段時間裏虛擬機要發起垃圾收集時就不必去管這些已經聲明自己在安全區域內的線程了。當線程要離開安全區域時,會檢查虛擬機是否已經完成了根節點枚舉,如果完成了,就繼續執行,否則一直等待,直到收到可以離開安全區域的信號爲止


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