Safe Point 安全點
思考: 如上圖 GC的時候,是不是可以馬上GC,而不用去care用戶線程 ?
答案肯定是不行的。 HotSpot中GC不是在任意位置都可以進入,而只能在safepoint處進入。
JVM在設計的時候在“特定位置”記錄了OopMap , 而這些位置被稱爲安全點。
簡單來說
安全點就是指代碼運行到這個地方,它的狀態是確定的, JVM就可以安全的進行一些操作,比如GC。
所以GC不是想什麼時候做就立即觸發的,是需要等待所有線程運行到安全點後才能觸發。
安全點主要解決的是如何停頓用戶線程。
這些特定的安全點位置主要有以下幾種:
- 方法返回之前
- 調用某個方法之後
- 拋出異常的位置
- 循環的末尾
- …等等
安全點的選定的核心在於: 既不能太少 (太少的話用戶線程一直在跑,跑不到SafePoint, 那就沒法GC, 並且跑的過程中用戶線程也會創建對象,也要佔內存,本身需要GC,那就說明內存喫緊了) ,也不能太多 (太多太頻繁就意味着運行時內存負荷較高) 。
第二個問題需要考慮: 如何在GC時讓用戶線程都跑到最近的安全點,然後停下來。 JVM 採取的方式是主動式終端,不直接線程操作,僅簡單設置一個標誌位,各個程序執行的時候去輪詢這個標誌,一旦返現中斷標誌位真就自己在最近的安全點上主動掛起。
輪詢標誌的地方和安全點是重合的。
既然是輪詢,那必須得高效,HotSpot把輪詢的操作精簡到只有一條彙編指令的程度,使用的是內存保護陷阱的方式。
Safe Region 安全區域
安全似乎解決了如何停頓用戶線程,讓虛擬機進入GC狀態的問題了。 但如果程序“不執行”呢?
舉個例子,線程休眠 Thread.sleep(100_000) 休眠100秒,要等100秒才能運行到安全區域啊 ,這可咋玩? 或者用戶狀態Blocked了 ,這都不執行了,壓根就沒法跑到safe point點了。。。。。
JVM設計大神引入了 Safe Region 來解決類似問題。
Safe Region 是指在一段代碼片段中,引用關係不會發生變化。在這個區域內的任意地方開始 GC 都是安全的。
當用戶線程執行到安全區裏的代碼是,會先標識自己進入了安全區域,那GC的時候就不管這些已經聲明自己在安全區域的線程了。
OopMap
GC 我們都知道是清理那些引用不可達的對象, 簡單來說 JVM怎樣才能夠判斷出所有位置上的數據是不是指向GC堆裏的引用 ?
從外部記錄下類型信息,存成映射表 , 這種數據結構被稱爲 OopMap 。
在HotSpot中,對象的類型信息裏有記錄自己的OopMap,記錄了在該類型的對象內什麼偏移量上是什麼類型的數據。
oopMap是一個附加的信息,告訴你棧上哪個位置本來是個什麼東西。
這個信息是在JIT編譯時跟機器碼一起產生的。因爲只有編譯器知道源代碼跟產生的代碼的對應關係。
每個方法可能會有好幾個oopMap,就是根據safepoint把一個方法的代碼分成幾段,每一段代碼一個oopMap,作用域自然也僅限於這一段代碼。
循環中引用多個對象,肯定會有多個變量,編譯後佔據棧上的多個位置。那這段代碼的oopMap就會包含多條記錄。
每個被JIT編譯過後的方法也會在一些特定的位置記錄下OopMap,記錄了執行到該方法的某條指令的時候,棧上和寄存器裏哪些位置是引用。
這樣GC在掃描棧的時候就會查詢這些OopMap就知道哪裏是引用了。這些特定的位置主要在:
- 1、循環的末尾
- 2、方法臨返回前 / 調用方法的call指令後
- 3、可能拋異常的位置
這種位置被稱爲“安全點”(safepoint)。之所以要選擇一些特定的位置來記錄OopMap,是因爲如果對每條指令(的位置)都記錄OopMap的話,這些記錄就會比較大,那麼空間開銷會顯得不值得。
選用一些比較關鍵的點來記錄就能有效的縮小需要記錄的數據量,但仍然能達到區分引用的目的。
因此,HotSpot中GC不是在任意位置都可以進入,而只能在safepoint處進入。