參考資料:
https://www.iteye.com/blog/rednaxelafx-1044951
《深入理解Java虛擬機(周志明)》
1. 預備知識:
1.1 虛擬機棧的內存模型
略
1.2 GCRoots
- GC Root是可達性分析的根節點對象
GC Root 有如下幾種:
- 虛擬機棧(棧幀中的本地變量表)中引用的對象, 例如局部變量指向的對象等
- 方法區中類靜態屬性指向的對象
- 方法區中常量引用的對象
- JNI引用的對象
- 虛擬機內部引用的對象
- 被鎖持有的對象
- 反映Java虛擬機內部情況的JMXBean, JVMTI中註冊的回調,本地代碼緩存等
2. 回收對象遇到的問題
在1.1中介紹了很多可達性分析的根節點對象,
除了1.虛擬機棧(棧幀中的本地變量表)中引用的對象,是經常變化的,其他一般比較穩定
虛擬機在以線程爲GCRoot的時候,會遇到什麼問題:
- JVM的GC在發生的時候,需要保證對象的各種引用非常穩定,這樣才能分析出需要GC的對象
- 那麼保證引用關係穩定,也就是讓棧裏面的變量不要亂動,最好的辦法就是暫停所有的線程
問題1 : 那麼如何在棧上如何獲得穩定GCRoot呢?
3. 如何在棧上如何獲得穩定GCRoot
答1: 暫停所有線程
- 問題又來了, 如何暫停所有線程
- 暫停線程主要分兩種方法, 1. 搶先式中斷; 2.主動式中斷
3.1 搶先式中斷
搶先式中斷就是說,在觸發GC的時候,不管代碼執行到哪,直接把所有線程全部中斷
詳情: 略
3.2 主動式中斷
主動式中斷的思想是:
- 當GC觸發的時候, 只是設置一個標誌位
- 線程執行過程中會有檢查點, 當線程執行到檢查點的時候, 就會去檢查這個標誌
- 一旦發現中斷標誌, 則主動中斷掛起
- 這個檢查點就被叫做
Safepoint
4. Safepoint
Safepoint,解釋器和JIT編譯器都支持
- 既然選擇了Safepoint作爲輪詢檢查的點,那麼肯定不能太密集,如果每執行一條字節碼都檢查,對JVM負擔肯定太重了
- 但是也不能太稀疏,太稀疏會導致,遲遲不能進入Safepoint,會GC的時間變得很長
問2: 那麼Safepoint一般會出現在什麼位置呢?
答2:
- 循環的末尾
- 方法臨返回前/調用方法的call指令後
- 可能拋異常的位置
4.1 如何在Safepoint位置找到GCRoot呢?
好,假設我們這時GC觸發了
- 設置GC標誌位
- 線程走到Safepoint
- 檢查GC標誌位,中斷線程
- 開始做可達性分析
這裏又出現一個問題了:
棧幀中的局部變量表(本地變量表)是以變量槽(Variable Slot, 32bit)爲單位的一組內存空間
如果不結合字節碼指令,這裏的內存區域根本分辨不出來儲存的是什麼數據類型
這也導致了一個問題
GC收集器, 拿着這個本地變量表一看, 就是一個破byte數組, 根本不知道那一塊表示的是對象的指針, 那也就無法確定GCRoot是哪個對象, 吐血
問3: GC收集器是如何找到棧幀中的本地變量表中引用的對象呢?
5. OopMap
答3: Hotspot使用OopMap來標記引用的位置
- 爲GC生成的符號信息是OopMap,指出棧上和寄存器裏哪裏有GC管理的指針
- 數據結構: OopMap{零到多個“數據位置=內容類型”的記錄 off=該OopMap關聯的指令的位置}
; 例:
;《深入理解Java虛擬機(周志明)》 3.4.1 中的例子
0x026eb7a9: call 0x026e83e0 ; OopMap{ebx=Oop [16]=Oop off=142}
; EBX寄存器 和 棧中偏移量爲16的內存區域 中各有一個普通對象指針的引用(Ordinary Object Pointer, OOP)
; 有效範圍爲: 從 call指令開始直到 0x026e83e0+142=0x026eb7be位置
; 0x026e83e0(指令流起始位置)
; 142(OopMap記錄的偏移量)
; 0x026e83e0 ~ 0x026eb7be 這個指令流範圍內OopMap生效的區域
6. 比較高效的Safepoint檢查
問4: 如果在Safepoint檢查GC標誌位是個很頻繁的操作,怎麼才能優化它,讓它的操作輕一點呢?
答4:
- 設置某個內存頁爲不可讀
- Safepoint點的指令操作是讀這個不可讀的內存頁
- 觸發一個異常信號
- 異常處理器中掛起線程,實現等待操作
- 這樣就起到只要一條指令(讀指定內存頁),即可起到在safepoint檢查gc標誌位的作用了
7. Safe Region
Safepoint是針對正常執行的線程提出的機制
但是如果線程處於sleep狀態或者blocked狀態,怎麼進入safepoint呢
問5: 如果已經被掛起的線程無法走到safepoint怎麼辦
答5:
- 對於這種情況,虛擬機引入了安全區域(Safe Region)來解決
安全區域是指能夠確保在某一段代碼片段之中,引用關係不會發生變化, 因此,在這個區域中任意地方開始垃圾收集都是安全的. 我們也可以把安全區域看做被擴展拉伸了的安全點(Safepoint)
當用戶線程執行到安全區域裏面的代碼時, 首先會標識自己已經進入了安全區域,那樣當這段時間裏虛擬機要發起垃圾收集時就不必去管這些已經聲明自己在安全區域內的線程了
當線程要離開安全區域時, 它要檢查虛擬機是否已經完成了根節點枚舉(或者垃圾收集過程中其他需要暫停用戶線程的階段), 如果完成了, 那線程就當沒事發生過, 繼續執行;
否則它就必須一致等待, 知道收到可以離開安全區域的信號爲止