1. 程式人生 > 其它 >jvm讀書記錄4-HotSpot的演算法細節實現

jvm讀書記錄4-HotSpot的演算法細節實現

HotSpot的演算法細節實現

一、根節點列舉

迄今為止,所有收集器在根節點列舉這一步驟時都是必須暫停使用者執行緒的,現在可達性分析演算法耗時最長的查詢引用鏈的過程已經可以和使用者執行緒一起併發,但是根節點列舉始終還是必須在一個能保障一致性快照中才得以進行。

由於目前Java虛擬機器使用的都是準確式垃圾收集,所有當使用者執行緒停頓下來之後,其實並不需要一個不漏的檢查完成所有執行上下文和全域性的引用位置,虛擬機器應當是有辦法直接得到哪些地方存放著物件的引用的。在hotspot的解決方案裡面,是使用一組稱為oopmap的資料結構來達到這個目的。

一旦類載入動作完成的時候,hotspot就會把物件內什麼偏移量上是什麼型別的資料計算出來,在即時變異過程中,也會在特定的位置記錄下棧裡和暫存器裡哪些位置是引用,這樣收集器在掃描時就可以直接得知這些資訊了,並不需要真正一個不漏地從方法區等gc roots開始查詢。

二、安全點

在oopmap的協助下, hotspot可以快速準確的完成gc roots列舉,但是導致oopmap內容變化的指令非常多,如果為每條指令都聲稱oopmap,那麼將會需要大量額外的記憶體空間。

因此只有在“特定的位置”記錄了這些資訊(例如方法呼叫、迴圈跳轉、異常跳轉),這些位置稱為安全點,有了安全點的設定,也就決定了使用者程式執行時並非在程式碼指令流的任意位置都能夠停頓下來開始垃圾收集,而是強制要求必須執行到安全點後才能夠暫停。

對於安全點,另外一個需要考慮的問題時,如何在垃圾收集發生時讓所有的執行緒都跑到最近的安全點,然後停頓下來,這裡有倆種方案可供選擇:搶先式中斷和主動式中斷,搶先式中斷不需要執行緒的執行程式碼主動配合,在垃圾收集發生時,系統首先把所有使用者執行緒中斷,如果有使用者執行緒中斷的地點不是安全點,就恢復這個執行緒讓他重新執行到安全點。現在幾乎沒有虛擬機器採用搶先式中斷來暫停執行緒響應gc事件。主動式中斷時當垃圾收集需要中斷執行緒的時候,只是簡單的設定一個標誌位,各個執行緒執行過程時會不停的主動去輪訓這個標識,一旦發現中斷標誌為真就自己在最近的安全點主動中斷關起。輪訓標誌和安全點時重合的,hotspt虛擬機器使用記憶體保護陷阱的方式,把輪訓操作精簡至只有一條彙編指令的程度。通過test指令產生一個自陷異常訊號,然後在預先註冊的異常處理器中掛起執行緒實現等待。

三、安全區域

當用戶的執行緒sleep或者blocked轉態,這時候執行緒無法響應虛擬機器的中斷請求,因此引入安全區域來解決這類問題。

安全區域指能夠確保在一段程式碼片段中,引用關係不會發生變化,因此,在這個區域中任意地方開始垃圾收集都是安全的。當用戶執行緒執行到安全區域裡面的程式碼時,首先會標記自己已經進入了安全區域,那樣當這段時間裡虛擬機器要發起垃圾收集時就不必去管這些已宣告自己在安全區域的執行緒了,當執行緒要離開安全區域時,他要檢查虛擬機器是否已經完成了根節點列舉。

四、記憶集與卡表

記憶集時一種用於記錄從非收集區域指向收集區域的指標集合的抽象資料結構。其時為了標記跨代引用,方便在gc roots掃描時把整個老年代加進gc roots掃描範圍。

收集器只需要通過記憶集判斷出某一塊非收集區域是否存在有指向了收集區域的指標可以了,並不需要了解這個跨代指標的全部細節,

那麼在實現記憶集的時候買就可以選擇更為粗曠的記錄精度來節省記憶集的儲存和維護成本,包括:

1、字長精度:記錄精確到機器字長

2、物件精度:每個記錄精確到一個物件

3、卡精度:每個記錄精確到一塊記憶體區域,其實現時用一種稱為“卡表”的方式去實現記憶集,這也是目前最常用的一種記憶集實現形式。

五、寫屏障

使用記憶集可以縮減gc roots掃描範圍的問題,但是卡表元素如何維護呢?例如他們何時變髒、誰來把他們變髒。

何時變髒:有其他分代區域的物件引用了本區域的物件時

如何變髒:在hotspot虛擬機器裡時通過寫屏障技術維護卡表狀態的;

寫屏障可以看做是虛擬機器層面對“引用型別欄位賦值”這個動作的aop切面,在引用物件賦值時會產生一個環形通知,供程式執行額外的動作,也就是說賦值的前後都在寫屏障的覆蓋範圍。

即寫前屏障和寫後屏障。hotspot虛擬機器的許多收集器都是用的寫後屏障,排除g1收集器。

卡表在高併發的場景下面臨著偽共享的問題,現代中央處理器的快取系統中時以快取行為單位儲存的,當多執行緒修改相互獨立的變數時,如果這些變數恰好共享同一個快取行,就會彼此影響,導致效能降低,解決這個額問題一個簡單的方案是不採用無條件的寫屏障,而是先檢查卡表標記,只有卡表未被標記時,才將其標記為變髒。

六、併發的可達性分析

標記階段時追蹤式垃圾收集演算法的共同特徵,而且必須在一個能保障一致性快照上才能進行物件圖的遍歷。同時隨著堆上堆上越多, 其掃描的時間也就越長。

當用戶的執行緒和收集器併發工作的時候,可能導致“物件消失” (三色標記分析 此處略)

要解決物件消失的問題有倆種解決方案:增量更新、原始快照

增量更新:當黑色物件插入新的白色物件引用關係就將這個新的插入記錄下來,等併發掃描完成之後,再將這些記錄過的引用關係中的黑色物件為根,重新掃描一次。

原始快照:當灰色物件要刪除指向白色物件的引用關係時,就將這個刪除的引用記錄下來,等併發掃描完成之後,再將這些記錄過的引用關係中的灰色物件為根,重新掃描一次。

參考《深入理解Java虛擬機器》 周志明