JVM學習(五)GCRoot深入


這里所提到的到都是GC涉及到的一些概念,具體不同的收集器由於內存結構不同,並發串行不同,實現上不止下面這些東西

STW GC中Stop the world

即在執行垃圾收集算法時,Java應用程序的其他所有除了垃圾收集收集器線程之外的線程都被掛起。此時,系統只能允許GC線程進行運行,其他線程則會全部暫停,等待GC線程執行完畢后才能再次運行。jvm虛擬機調優的目的:盡量避免full gc

GC的過程

  • 垃圾對象:沒有被引用的對象,沒有GCRoots鏈條上的對象
  • 非垃圾對象:被引用的對象

可達性分析算法

難點:

  • 查找的過程,光方法區就常有數百上千兆,逐個檢查效率太低
  • 查找的過程,是必須要暫停用戶線程。

安全點(能說大概)

  • 來由:由於查找的過程需要暫停用戶線程,不可能程序運行每條指令都執行一遍垃圾回收。
  • 定義:在特定的位置才會進行GC,這些位置就叫做安全點。
  • 選取原則:可以讓程序一直安全的跑下去,不能讓太多的死對象占據內存。
    • 遇到長時間執行的指令前就給它 GC 一下,類如方法的調用、循環跳轉、異常跳轉等,遇到類似的執行才會生成 Safepoint
  • 實現方式(這里的多線程是指運行同一份代碼無法確定運行順序的解決方案):
    • 搶占式中斷:不管你現在在哪里,先給你中斷,然后一看,噢原來你不在安全點,就恢復線程讓你跑一會,然后又中斷直到你到了安全點再 GC。(已經沒有虛擬機會用這個)
    • 主動式中斷:需要 GC 的時候,不需要強制中斷線程了,只需要在安全點設置一個輪詢標識,線程只需要去輪詢這個標識即可,線程到安全點了,自己主動中斷,進而 GC。

安全區域(能說大概)

針對主動式中斷,假如我有一個線程正好block住,它沒有在運行,就不能去輪詢 GC 標識了,我們難道要等到它運行再 GC,不可能的對吧!

反過來想一下就是,假如線程本身就不再執行,那何必去管它呢,因為它不可能使引用發生變化啊。故我們又定義了一個安全區域的概念,在這個代碼片段之中發生 GC 都是可以的,因為引用不曾改變。這就是擴大版的安全點啊,也就是它會把block的相關代碼點設為安全區域

在線程執行到安全區域中的代碼時,首先標識自己已經進入了安全區域,那樣,當這段時間內 JVM 要發起 GC 時,就不管沒到安全點但是在安全區域的線程。在線程要離開安全區域時,要檢查系統是否已經完成了 GC,如果完成了,那就繼續執行,否則就要等待 GC 結束的標識之后才可以離開安全區域。

記憶集(能說大概)

學會卡表和寫屏障的概念

如果發現老年代的內存區域存在新生代的引用,那么將會將對象加入記憶集。垃圾回收時,記憶集里面的對象會加入GCroot,就避免了老年代的全遍歷回收。

記憶集的實現方式是通過 卡表的方式

  • card Table:值01,若為1說明該卡頁內對象存在指向收集區域的指針
  • card Page:存地址值
  • 映射關系:card Table[address>>9] 類似hashmap

何時記錄變臟呢?

在引用類型字段賦值的那一刻,通過機器碼層面的手段,把維護卡表的動作放到每一個賦值操作之中。也叫寫屏障

並發可達性分析(必備)

根節點枚舉所帶來的停頓是非常短暫且相對固定的,但是GC Roots往下遍歷對象圖,這一步驟的時間與堆容量成正比,因此采用並發掃描。然而並發掃描存在並發問題如下:

垃圾收集器從 GC Roots 開始標記的過程示意圖如下:

三色標記(也可能會考):

  • 白色:對象尚未被垃圾收集器訪問過(若在分析結束后,對象仍為白色,則表示不可達)
  • 黑色:對象已被垃圾收集器訪問過,且該對象所有引用都已被掃描(安全存活的)
  • 灰色:對象已被垃圾收集器訪問過,但未掃描完所有引用(即該對象正在被掃描,可理解為中間態)
注意引用是有方向的。

但是,如果在標記過程中,用戶線程對引用關系做了修改,如下:

在上圖的(4)中:

  1. 原先對象 A 未引用 C,對象 B 引用了 C;
  2. 但標記到 B 時,用戶線程斷開了 B 到 C 的引用,而使 A 引用了 C;
  3. 則垃圾收集器標記完成后,C 依然是白色(即會被回收掉);
  4. 對象 DEFG 同理。

這樣導致的后果就是:正在被對象 A 和 D 引用的對象 C 和 G,在垃圾收集器標記的過程中,由於用戶線程的運行,導致本應存活的對象被垃圾收集器標記為消亡、並回收了。程序會因此報錯,這是個嚴重的問題。

如何解決對象消失(主要掌握這個)

如何解決上述“對象消失”的問題呢?理論證明,當且僅當以下兩個條件同時滿足時,才會產生“對象消失”的問題:

  1. 賦值器插入了一條或多條從黑色對象到白色對象的新引用;
  2. 賦值器刪除了全部從灰色對象到該白色對象的直接或間接引用。
針對這兩個條件,在上圖中,以對象 A、B、C 為例解釋如下:
若只增加了 A 對 C 的引用,則 C 在垃圾回收后依然是存活的,不會出錯。
若只有 B 斷開了對 A 的引用,則 C 在垃圾回收后是消亡的,但並沒有 A 對 C 的引用,因此也不會出錯。

因此,要解決並發掃描時的對象消失問題,只需破壞其中一個即可。由此產生了兩種解決方案:增量更新(Increment Update)和原始快照(Snapshot At The Begining, SATB)。

增量更新

  • 思路:破壞第一個條件。
  • 做法:黑色對象(A)插入新的指向白色對象(C)的引用關系(A→C)時,就將這個新插入的引用記錄下來,待並發掃描結束之后,再以這些記錄過的引用關系中的黑色為根,重新掃描一次。
  • 簡化理解:黑色對象一旦新插入了指向白色對象的引用,它就變為灰色(需重新掃描)了。

原始快照

  • 思路:破壞第二個條件。
  • 做法:當灰色對象(B)要刪除指向白色對象(C)的引用關系(B→C)時,就將這個要刪除的引用記錄下來,並發掃描結束后,再以這些記錄過的引用關系中的灰色對象為根,重新掃描一次(可能會有一些垃圾對象但是也沒關系)。
  • 簡化理解:無論引用關系刪除與否,都會按照剛開始掃描那一刻的對象圖快照來進行搜索。

這兩種方案都有在用:在 HotSpot 虛擬機中,CMS 是基於增量更新來做並發標記的,G1、Shenandoah 則是用原始快照實現的。

舉例

以上圖為例:在並發掃描時,增加了 A→C 引用,並且刪除了 B→C 引用,若不采取任何措施,則掃描結束后對象 C 會消失。

兩種解決方案的做法分別如下:

  • 增量更新:將已標記為黑色的對象 A 置為灰色,待並發掃描結束后,重新掃描對象 A。此時可以掃描到 A→C 引用,對象 C 不會消失。
  • 原始快照:若要刪除 B→C 引用,則將原始的 B→C 引用記錄下來(原始的快照),待並發掃描結束后,重新掃描對象 B,由於記錄的是原始信息,其中包含 B→C 引用。這樣,即便未掃描到 A→C 引用,對象 C 也不會消失。

此外,無論引用關系記錄的插入還是刪除,虛擬機都是通過寫屏障實現的

參考來源


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM