本文已收錄至Github,推薦閱讀 ?? Java隨想錄
微信公眾號:Java隨想錄
CSDN: 碼農BookSea
目錄世界上最快樂的事,莫過于為理想而奮斗,——蘇格拉底
- 什么是根節點列舉
- 根節點列舉存在的問題
- 如何解決根節點列舉的問題
- 安全點
- 安全區域
HotSpot使用的是可達性分析演算法,該演算法需要進行根節點列舉,查找根節點列舉的程序要做到高效并非一件容易的事情,現在Java應用越做越龐大,光是方法區的大小就常有數百上千兆,里面的類、常量等更是恒河沙數(一種修辭手法),若要逐個檢查以這里為起源的參考肯定得消耗不少時間,
大家可以思考下,如果你是JVM的開發者,你會怎么去做?
看完這一章節,你或許會跟我一樣,感嘆JVM開發者的智慧,
什么是根節點列舉
顧名思義,根節點列舉就是找出所有的GC Roots,
固定可作為GC Roots的節點主要在全域性的參考(例如常量或類靜態屬性)與執行背景關系(例如堆疊幀中的本地變數表)中,
固定可作為GC Roots的物件包括以下幾種(摘抄自《深入理解虛擬機 第3版》):
- 在虛擬機堆疊(堆疊幀中的本地變數表)中參考的物件,譬如各個執行緒被呼叫的方法堆疊中使用到的引數、區域變數、臨時變數等,
- 在方法區中常量參考的物件,譬如字串常量池(String Table)里的參考,
- 在本地方法堆疊中JNI(即通常所說的Native方法)參考的物件,
- Java虛擬機內部的參考,如基本資料型別對應的Class物件,一些常駐的例外物件(比如NullPointExcepiton、OutOfMemoryError)等,還有系統類加載器,
- 所有被同步鎖(synchronized關鍵字)持有的物件,
- 反映Java虛擬機內部情況的JMXBean、JVMTI中注冊的回呼、本地代碼快取等,
根節點列舉存在的問題
迄今為止,所有收集器在根節點列舉這一步驟時都是必須暫停用戶執行緒的,因此毫無疑問根節點列舉與之前提及的整理記憶體碎片一樣會面臨相似的“Stop The World”的困擾,根節點列舉必須在一個能保障一致性的快照中才得以進行——這里“一致性”的意思是整個列舉期間執行子系統看起來就像被凍結在某個時間點上,
為什么要這么做?
如果不”凍結”的話,根節點集合的物件參考關系在不斷變化,那么分析結果準確性也就無法保證,所以即使是號稱停頓時間可控,或者(幾乎)不會發生停頓的CMS、G1、ZGC等收集器,在列舉根節點這一步也是必須要停頓的,
如何解決根節點列舉的問題
目前主流Java虛擬機使用的都是準確式垃圾收集(準確式 GC 使用的物件訪問定位方式是直接指標訪問),所以當用戶執行緒停頓下來之后,其實并不需要一個不漏地檢查完所有執行背景關系和全域的參考位置,虛擬機應當是有辦法直接得到哪些地方存放著物件參考的,
在HotSpot的解決方案里,是使用一組稱為OopMap的資料結構來達到這個目的,OopMap可以理解為就是映射表,存盤堆疊上的物件參考的資訊,這是一種空間換時間的做法,在 GC Roots 列舉時,只需要遍歷每個堆疊楨的 OopMap,通過 OopMap 存盤的資訊,快捷地找到 GC Roots,
用大白話說,其實就是用類似映射表這種手段記錄下來參考關系,時不時去更新下映射表,然后根節點列舉只需要掃描映射表就知道哪些地方存放參考了,而不用去進行全域掃描,
安全點
OK,問題又來了,既然OopMap是一個映射表,這個表什么時候被更新?要知道參考關系變化是十分頻繁的,如果參考每變化一次就更新對應的OopMap,那將會需要大量的額外存盤空間,這樣垃圾收集伴隨而來的空間成本就會變得無法忍受的高昂,
解決這個的辦法就是安全點,事實上,只是在“特定的位置”記錄了這些資訊,這些位置被稱為安全點(Safepoint),因此GC不是隨時隨地來的,得到達安全點時才可以開始GC,
一般會在如下幾個位置選擇安全點:
- 回圈的末尾
- 方法臨回傳前
- 呼叫方法之后
- 拋例外的位置
但是還有一個問題是需要考慮的:如何在垃圾收集發生時讓所有執行緒都跑到最近的安全點,然后停頓下來,
有兩種方案可供選擇:搶先式中斷(Preemptive Suspension)和主動式中斷(Voluntary Suspension),
- 搶先式中斷:不需要執行緒的執行代碼主動去配合,在垃圾收集發生時,系統首先把所有用戶執行緒全部中斷,如果發現有用戶執行緒中斷的地方不在安全點上,就恢復這條執行緒執行,讓它一會再重新中斷,直到跑到安全點上,現在幾乎沒有虛機實作采用搶先式中斷來暫停執行緒回應GC事件,
- 主動式中斷:當垃圾收集需要中斷執行緒的時候,不直接對執行緒操作,僅僅簡單地設定一個標志位,各個執行緒執行程序時會不停地主動去輪詢這個標志,一旦發現中斷標志為真時就自己在最的安全點上主動中斷掛起,輪詢標志的地方和安全點是重合的,另外還要加上所有創建物件和其他需要在Java堆上分配記憶體的地方,這是為了檢查是否即將要發生垃圾收集,避免沒有足夠記憶體分配新物件,
安全區域
安全點的設計似乎已經完美解決如何停頓用戶執行緒,但是仍然有問題,安全點機制保證了程式執行時,在不太長的時間內就會遇到可進入垃圾收集程序的安全點,但是,程式“不執行”的時候呢?
所謂的程式不執行就是沒有分配處理器時間,典型的場景便是用戶執行緒處于Sleep狀態或者Blocked狀態,這時候執行緒無法回應虛擬機的中斷請求,不能再走到安全的地方去中斷掛起自己,對于這種情況,JVM引入安全區域(Safe Region)來解決,
安全區域是指能夠確保在某一段代碼片段之中,參考關系不會發生變化,因此,在這個區域中任意地方開始垃圾收集都是安全的,我們也可以把安全區域看作被擴展拉伸了的安全點,
當用戶執行緒執行到安全區域里面的代碼時,首先會標識自己已經進入了安全區域,那樣當這段時間里虛擬機要發起垃圾收集時就不必去管這些已宣告自己在安全區域內的執行緒了,**當執行緒要離開安全區域時,它要檢查虛擬機是否已經完成了根節點列舉(或者垃圾收集程序中其他需要暫停用戶執行緒的階段),如果完成了,那執行緒就當作沒事發生過,繼續執行;否則它就必須一直等待,直到收到可以離開安全區域的信號為止,
如果本篇博客有任何錯誤和建議,歡迎給我留言指正,文章持續更新,可以關注公眾號第一時間閱讀,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/542577.html
標籤:其他
上一篇:Zookeeper集群
