可達性分析算法
以一系列“GC Roots”根對象作為起始節點集,從這些節點開始,根據引用關系向下搜索,搜索過程所走過的路徑稱為“引用鏈”(Reference Chain),如果某個對象到GC Roots間沒有任何引用鏈相連, 或者用圖論的話來說就是從GC Roots到這個對象不可達時,則證明此對象是不可能再被使用的。
從GC Roots開始向下進行引用搜索,如果某對象和任何GC Root沒有關聯,則認為該對象不再被使用。
GC Roots
- 虛擬機棧中引用的對象,對應虛擬機方法棧中當前執行的方法所使用的參數,局部變量,臨時變量等。
- 方法區中:類的靜態變量,常量
- 本地方法棧中引用的對象
記憶集與卡表
記憶集是一種用于記錄從非收集區域指向收集區域的指針集合的抽象數據結構。
卡表:是記憶集的一種實現形式,采用的是“卡精度”的方式實現記憶集。卡精度指精確到一塊內存區域(該內存區域又稱為“卡頁”),這個區域內有跨代指針的話,就將其標識出來(實際是使用的0/1標識);
在進行GCROOTS掃描時,同時再去篩選卡表中變臟的元素(掃描指定的內存區域塊),可以快速定位到關聯區域,將跨區引用的對象一起加入GCROOTS掃描。
寫屏障
寫屏障主要解決的是卡表元素的維護更新,即處理卡表變臟的問題。
何時變臟:其他分代區域有對象引用了該區域對象時,其對應的卡表元素則變臟。
寫屏障的處理類似于一個AOP切面,即在本區對象被引用時,添加了Around環繞通知,可在引用賦值前后添加寫前屏障和寫后屏障。通常虛擬機在寫后屏障中增加維護卡表的操作。
卡表在高并發場景下還存在偽共享的問題,由于64個卡表共享一個緩存行,當多個線程更新同一緩存行數據時,會出現并發更新影響性能的情況。
同時虛擬機提供-XX:+UseCondCardMark參數配置,開啟該參數則會預先檢查該卡表是否已變臟,再行更新的策略。對未變臟的進行更新,已經變臟的卡表不再更新。開啟該參數增加異常額外判斷的開銷,但可以避免偽共享的問題
SATB(snapshot at the beginning)原始快照
沿著GCRoots進行并發掃描時,通常用戶線程也在并發執行。這時會面臨著已經被垃圾收集器掃描的對象圖被用戶線程更改的情況。
情況一:某些對象被用戶線程斷開了引用,其實該對象已經成為垃圾,現在仍被垃圾收集器標識為存活
情況二:某些被垃圾收集器標識為垃圾的對象被用戶線程重新引用,導致存活對象被垃圾收集器回收了
綜合以上兩種情況可知,情況一其實可以容忍,只是程序產生了一些浮動垃圾,待下一次垃圾收集時可以一并回收。情況二把原本存活的對象標記為了死亡,則會造成程序的致命錯誤。
關于情況二即并發掃描時的對象消失問題,不同的垃圾收集器的解決辦法不同。CMS中使用增量更新的方式,G1和shenandoah采用原始快照的方式。
CMS使用增量更新的方式:
當有黑色對象插入指向白色對象的引用時,就將這個引用關系記錄下來。在并發掃描結束后(最終標記),以這些黑色對象為根再次進行掃描一次。
垃圾收集器掃描完成的對象引用了一個未被掃描過(新創建或已斷開引用)的對象,會將該引用關系記錄下來,在最終標記階段再以該新創建的對象為根,再次進行掃描一次。
G1和shenandoah的原始快照方式:
當灰色對象要刪除一個指向白色對象的引用時,就將該要刪除引用關系記錄下來。在并發掃描結束再以這些灰色對象為根進行掃描一次。這種做法最終導致無論關系刪除與否,都會按照垃圾收集器開始掃描的那一刻的對象圖來進行對象搜索。
TAMS(Top at mark start)標記頂部
G1收集器中的概念,G1為每個region設計兩個TAMS的指針,用于在并發回收階段新的對象的分配。在回收階段新對象分配的內存地址將落在region的兩個TAMS指針之間。G1默認在兩個指針之間的對象是存活的,不對其進行垃圾回收。