hotspot的安全區(saferegion)和安全點(safepoint)

 

一、經過OopMap完成根節點枚舉

HotSpot虛擬機使用可達性分析算法肯定對象是否能夠被GC。java

可達性分析算法從一系列GCRoot對象開始,向下搜索引用鏈,若是一個對象沒有與任何GCRoot對象關聯,這個對象就會被斷定爲可回收對象。算法

GCRoot包括如下對象:安全

  1. 虛擬機棧上的本地變量表引用的對象多線程

  2. 方法區中類的靜態屬性引用的對象函數

  3. 方法區中常量引用的對象this

  4. 本地方法棧中JNI引用的對象.net

這一過程稱爲根節點枚舉,也就是垃圾回收中的標記過程,當前全部的垃圾收集器,在標記階段都必須中止全部java執行線程(STW),以保證對象引用狀態不會發生變化。線程

HotSpot虛擬機做爲準確式虛擬機,維護了一個專門的映射表(OopMap)記錄哪些位置存放着對象引用,來快速完成根節點枚舉過程。翻譯

類加載完成,HotSpot就會把對象內某個偏移位置是否爲對象引用記錄下來,JIT編譯過程當中,也會在特定的位置記錄下棧和局部變量表中哪些位置是引用。指針

二、安全點SafePoint

爲每個操做記錄OopMap不現實,HotSpot虛擬機引入了SafePoint。安全點就是某些記錄線程此時調用棧、寄存器等一些重要的數據區域裏什麼地方包含了GC要管理的指針(對象引用),而這些對象引用是經過OopMap結構進行記錄的,能夠直接經過對OopMap結構的訪問來得到對象的引用。

SafePoint是程序中的某些位置,線程執行到這些位置時,線程中的某些狀態是肯定的,在safePoint能夠記錄OopMap信息,線程在safePoint停頓,虛擬機進行GC。一個線程能夠在SafePoint上,也能夠不在SafePoint上。一個線程在SafePoint時,它的狀態能夠安全地被其餘JVM線程所操做和觀測。

線程停頓方式有兩種,搶先式中斷和主動式中斷:

  1. 搶先式中斷:虛擬機須要GC時,中斷全部線程,讓沒有到達SafePoint的線程繼續執行至SafePoint並中斷

  2. 主動式中斷:虛擬機不直接中斷線程,而是在內存中設置標誌位,線程檢查到標誌位被設置,運行至SafePoint時主動中斷

hotspot採用的是第一種, 也就是主動檢測的方式. 而在主動檢測的方式中又分爲兩種方式:

  1. 指定點執行檢測代碼
  2. polling page訪問異常觸發

Hotspot, 顧名思義, 就是熱點的意思, 這裏所謂的熱點指的是熱點代碼, 也就是執行頻率很高的代碼, hotspot會根據運行時的信息來統計, 並將高頻率執行的java字節碼直接翻譯成本地代碼, 由此提升執行效率. 所以, hotspot有兩種執行方式, 一個是解釋執行, 一個是編譯執行。指定點檢測主要是解釋執行用的,對於須要高效實現的地方,則採用polling page。

polling page和普通物理頁面沒什麼區別,須要safepoint時, 會修改該頁面的權限爲不可訪問, 這樣編譯的代碼在訪問這個頁面時, 會觸發段違規異常(SEGEV). 而hotspot在啓動時捕獲了這個異常, 當意識到是訪問polling page致使時, 則主動掛起。

SafePoint通常出如今如下位置:

  1. 循環體的結尾

  2. 方法返回前

  3. 調用方法的call以後

  4. 拋出異常的位置

這些位置保證線程不會長時間運行而沒法到達SafePoint,避免其餘線程都停頓等待本線程。

public static void main(String[] args) throws Exception {
     [1]DemoObject demoObject = new DemoObject();
     [2]//往demoObject上掛一個字符串對象
     [3]demoObject.val1 = "this is a string object";
     [4]Thread.sleep(1000000);
}

咱們知道代碼是在線程裏執行的, GC的代碼也是在線程裏執行, 若是執行GC的時候其餘線程也同時執行的話, heap的狀態將是難以追蹤的. 以上面的代碼爲例, 假設GC線程經過掃描線程的stack(線程stack是一種GC Root), 掃描到demoObject, 而後根據這時候, main函數執行到[3], 但還未執行, 掃描的結果只發現demoObject是存活的, 接下來, main函數的線程執行[3], demoObject.val1引用了一個字符串對象, 這個對象的掃描就漏掉了, 除非以某種方式記錄下這個變化, 而後從新掃描demoObject. 即使有辦法記錄這個賦值致使的變化而後再次掃描, 若是其餘線程這時候又來搗亂, 那麼從新掃描的時候有可能又發生了變化, 陷入循環…

再往下一點, 咱們知道CPU執行運算時的數據, 須要從內存裏載入寄存器中, 運算完再從寄存器存入內存, 對象的地址也要通過這麼個過程. 假如一個java線程分配了一個對象A, 該對象的地址存在某個寄存器中, 而後線程的cpu時間片到期被切換出去, 同時GC的線程開始掃描存活對象, 因爲沒有路徑到這個地址還在寄存器中的對象, 這個對象被認爲是garbage, 回收了. 而後睡眠的java線程醒來了, 把寄存器中的對象地址賦值給了存活對象的某個字段, over…

GC的目的在於幫助咱們收集再也不使用的內存, 可是把正在是使用的內存當成垃圾回收顯然是不能接受的. 同時經過分析也看到, 因爲多線程運行環境的存在, GC的工做會變的異常複雜, 要安全的回收垃圾, 須要具有兩個條件:

  • heap的變化是受限的, 固然了, 全部線程都停下來最好, 這樣heap 在GC過程當中是穩定的,這是最簡單的狀況.

  • heap的狀態是已知的, 不會有活着的對象找不到或者很難找的狀況. 想一想對象地址在寄存器中的狀況, 雖然能夠有辦法能夠掃描線程的寄存器, 即便這樣, 也必須知道哪一個寄存器在某個時刻存的是地址, 要作到掃描不漏是很複雜的事情.

三、安全區SafeRegion

SafePoint沒法解決線程未達到SafePoint並處於休眠或等待狀態的狀況,此時引入SafeRegion的概念。

SafeRegion是代碼中的一塊區域或線程的狀態,在SafeRegion中,線程執行與否不會影響對象引用的狀態。線程進入SafeRegion會給本身加標記,告訴虛擬機能夠進行GC;線程準備離開SafeRegion前會詢問虛擬機GC是否完成。

 

 

參考文章:

(1)聊聊JVM(六)理解JVM的safepoint http://www.javashuo.com/article/p-zcfsrnxu-dn.html

(2)聊聊JVM(九)理解進入safepoint時如何讓Java線程所有阻塞 http://www.javashuo.com/article/p-ooxttjcb-gz.html

相關文章
相關標籤/搜索