本文部分摘自《深入理解 Java 虛擬機第三版》
根節點列舉
在之前關于可達性分析演算法的介紹中我們講過,我們需要先找出可固定作為 GC Roots 的節點,然后沿著參考鏈去尋找那些無用的垃圾物件,GC Roots 節點一般在全域性參考(例如常量和類靜態屬性)與執行背景關系(例如堆疊幀中的本地變數表)中,盡管目標明確,但查找程序要做到高效并非一件易事,若要逐個查找可作為起源的參考肯定需要消耗不少時間
迄今為止,所有收集器在根節點列舉這一步驟時都是必須暫停用戶執行緒,也即 Stop The World,因為如果在分析程序中出現根節點集合中物件的參考關系仍在不斷變化的情況,分析結果的準確性也就無法保證了
在對堆疊記憶體進行分析時,虛擬機會看哪些位置存盤了 Reference 型別,如果發現某個位置確實存的是 Reference 型別,就意味著它所參考的物件這一次不能被回收,但問題是,堆疊幀的本地變數表里面只有一部分資料是 Reference 型別的,那些非 Reference 型別(基本資料型別)的資料對我們毫無用處,但我們還是不得不對整個堆疊全部掃描一遍,這是對時間和資源的一種浪費,在 HotSpot 的解決方案中采用了一組稱為 OopMap 的資料結構來實作直接找到物件參考,一旦類加載動作完成,HotSpot 就會把堆疊中代表參考的位置全部記錄下來,這樣收集器在掃描時就可以直接得知這些訊息了
安全點
盡管有了 OopMap,但如果參考關系經常變化,虛擬機就需要為每一條指令都生成對應的 OopMap,這將會占用大量的額外存盤空間
HotSpot 當然沒那么笨,它只會在特定的位置去記錄這些資訊,這些位置被稱為安全點(SafePoint),有了安全點的設定,用戶程式就必須執行到安全點才能暫停,而不是在代碼指令流的任意位置隨意停頓,安全點的選定不能太少,讓收集器等待時間過長,也不能太頻繁,導致增大運行時記憶體負擔,安全點的位置選定基本上是以“是否具有讓程式長時間執行的特征”為標準進行選定,“長時間執行”的最明顯特征就是指令序列的復用,例如方法呼叫、回圈跳轉、例外跳轉等,只有具有這些功能的指令才能產生安全點
對于安全點,另外一個要考慮的問題就是,如何在垃圾收集發生時讓所有執行緒都跑到最近的安全點,一般有兩種方案可供選擇:
- 搶先式中斷:垃圾收集發生時,系統首先把所有用戶執行緒全部中斷,如果發現有用戶執行緒中斷的地點不在安全點上,就恢復該執行緒執行,直至跑到安全點再中斷,現實中幾乎沒有虛擬機會采用搶先式中斷
- 主動式中斷:垃圾收集發生時,不直接對執行緒操作,而是設定一個標志位,各個執行緒在執行時會不停地主動去輪詢這個標志,一旦發現標志位為真就在最近的安全點主動中斷
安全區域
安全點看似解決了我們遇到的問題,但還有一個需要思考的點:如果某一個用戶執行緒正好處于“不執行”狀態該怎么辦?所謂“不執行”就是沒有分配處理器時間片,典型的場景如用戶執行緒處于 Sleep 或 Blocked 狀態,這時執行緒無法回應中斷請求,自然也就不能走到安全點主動掛起自己,而虛擬機也不可能持續等待執行緒重新被分處理器時間片,對于這種情況,就需要引入安全區域(Safe Region)來解決
安全區域是指能夠確保在某一代碼片段中,參考關系不會發生變化,因此,在這個區域中任意地方開始垃圾收集都是安全的,我們也可以把安全區域看作是被擴展拉伸了的安全點
當用戶執行緒執行到安全區域時,首先會標識自己已經進入安全區域,那樣當這段時間里虛擬機要發起垃圾收集時就不必去管這些已經宣告自己在安全區域內的執行緒了,當執行緒要離開安全區域時,會檢查虛擬機是否已經完成了根節點列舉,如果完成了,就繼續執行,否則一直等待,直到收到可以離開安全區域的信號為止
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/240754.html
標籤:Java
上一篇:Java基礎之:Set——HashSet——LinkedHashSet
下一篇:學習一下 SpringCloud (二)-- 服務注冊中心 Eureka、Zookeeper、Consul、Nacos
