1. 程式人生 > 實用技巧 >詳細解釋垃圾回收器為什麼必須要停頓下?

詳細解釋垃圾回收器為什麼必須要停頓下?

美麗又短暫的假期居然這麼快就結束了,學習的小車輪繼續的滾起來吧

垃圾回收器為什麼必須要停頓下?

在垃圾收集器在獲取根節點這一步時必須暫停使用者執行緒的也就是我們常說的STW,目前可達性分析演算法耗時最長的查詢引用鏈的過程已經可以做到和使用者執行緒一起併發,但根節點列舉的獲取還必須是要在一個能保證一致性的快照中才能進行。

這裡說的一致性就是根節點列舉分析期間執行子系統看起來就像被凍結在某個時間點上,不會出現一邊分析,根節點的物件引用關係還在不斷的變化的情況。這也是導致垃圾收集過程必須停頓所有使用者執行緒的其中一個重要原因,即便是號稱停頓時間可控的CMS、G1、ZGC等,跟節點分析時也是必須要停頓的。

GC Roots

都知道JVM內物件存活判定一般用可達性分析演算法,也就是說在HotSpot 使用裡面維護了很多根節點(GC Roots)。

GC Roots種類:

  • 靜態變數引用的物件

  • 常量引用的物件

  • 棧針本地變量表引用的物件

  • JNDI 引用的物件

什麼是OopMap ?

目前主流JVM垃圾收集,在當用戶執行緒停頓下後其實是不需要一個不漏的檢查完所有的執行上下文和全域性引用位置的。在HotSpot中是使用一組成為OopMap的資料結構來達到這個目的的。

當類載入動作完成時,HotSpot就會將物件內的型別、偏移量等資料計算出來,這時在垃圾收集器掃描的時候就可以直接得到這些資訊了,並不需要一個不漏的從GC Roots開始查詢。

藉助於OopMap,虛擬機器可以快速列舉GC Root引用,這就是典型的以空間換時間。但導致引用關係變化非常多,又不能生成過多的OopMap從而導致儲存資源的浪費,所以又出現了安全點和安全區域。

什麼是安全點?

在OopMap的幫助下,可以快速的完成GC Roots資料掃描,但可以導致引用關係變化的可能太多了,也就是說導致OopMap內容變化的指令非常多,不可能每次變化都生成對應的OopMap。

所以,在某個特定的位置來記錄關係資訊到OopMap,這些位置就被稱為安全點。其實也就是在程式碼執行到達指定的位置才能夠暫停進行資訊收集。

舉例Serial 收集器(其他收集器也差不多):

  • 單執行緒收集器,收集的時候會暫停所有使用者執行緒(簡稱STW)
  • 客戶端模式下預設收集器
  • 簡單高效,是所有收集器中額外記憶體消耗最小的

安全點的選定:具有讓程式長時間執行的特徵,例如方法呼叫、迴圈跳轉、異常跳轉等。

怎麼到達安全點?

在實際情況下,是不可能在發生垃圾收集的時候所有的執行緒都正好在安全點,所以就需要執行緒都跑到最近的安全點然後停頓下來。

有兩種方案:

搶先試中斷(Preemptive Suspension):(現在幾乎沒有用這種的了)

不需要執行緒的執行程式碼主動配合,在垃圾收集發生時,系統首先把所有使用者執行緒全部中斷,如果發現有使用者執行緒中斷的地方不再安全點上,就恢復這條執行緒執行,讓它一會再重新中斷,直到到達安全點。

主動式中斷(Voluntary Suspension):

在垃圾收集需要中斷執行緒的時候,不直接對執行緒操作,僅簡單的設定一個標誌位,各個執行緒執行過程時會不停的主動去輪詢這個標誌,一旦發現中斷標誌為true時就在自己最近的安全點上主動掛起。

什麼是安全區域?

安全點似乎解決了讓虛擬機器內部執行緒主動停頓,整個虛擬機器進入垃圾回收狀態的問題。但在實際情況下,如果執行緒處於sleep 或Blocked狀態的話是沒有分配CPU時間的,這時執行緒是無法響應虛擬機器的中斷請求,不能再走到安全點進行掛起,而虛擬機器也不能持續的等待執行緒被重新啟用分配CPU。這種情況,就必須引入安全區域(Safe Region)來解決問題了。

安全區域,可以看作是安全點的擴充套件。指的是能夠確保在某一段程式碼片段中,引用關係不會發生變化,在這個區域中任意地方開始來及手機都是安全的。

安全區域內發生了什麼?

當執行緒執行到安全區域裡的程式碼時,會先標識自己進入了安全區域,如果這時段裡進行了垃圾收集虛擬機器就不必去管這些已經標識過的執行緒了

當執行緒離開安全區域時,它要檢查下虛擬機器是否完成了根節點的掃描或者垃圾收集過程中需要停頓的階段。如果完成了,那執行緒就會繼續執行。否則就必須一直等待,直到收到可以離開安全區域的訊號。

結尾

看完這些,你能回答下面的問題嗎!

垃圾收集器為什麼必須要停頓下?

安全點和安全區域的區別?