垃圾標記
是垃圾回收的第一階段,也是垃圾回收算法中關鍵的一步。它的主要目的是通過遍歷內存中存活的對象,來標記哪些對象仍是可達的(即活躍的),哪些對象是不可達的(既可以被垃圾回收)。
標記的結果,將作為下一步(清理,壓縮)的依據,確定哪些對象需要被釋放或者移動
垃圾回收策略
- 引用計數法
原理:
- 每個對象維護一個 引用計數器,用於記錄該對象被引用的次數。
- 當對象被創建或被引用時,計數器增加。
- 當引用被移除或銷燬時,計數器減少。
- 如果計數器為零,説明該對象不可達,可以被回收。
實現流程:
- 初始化時,計數器設為 0。
- 每當有新的引用指向該對象時,計數器 +1。
- 每當引用消失時,計數器 -1。
- 如果計數器為 0,該對象會被立即回收。
缺點:
-
循環引用問題:
- 如果兩個或多個對象相互引用,但沒有其他外部對象引用它們,則它們的計數器始終大於零,導致無法被回收。
- 例如:
class Node {
Node next;
}
Node a = new Node();
Node b = new Node();
a.next = b;
b.next = a;
-
維護開銷大:
- 每次引用變化都需要更新計數器,可能會影響性能。
- 根搜索法
原理:
- 從一組稱為 GC Roots 的根對象開始,通過引用關係遍歷對象圖。
- 如果某個對象可以通過 GC Roots 找到,則認為它是可達的;否則認為它是不可達的,可以被回收。
實現流程:
- 從 GC Roots 出發,標記所有可以訪問到的對象。
- 遍歷整個堆,將未被標記的對象視為垃圾,進行回收。
優點:
-
無循環引用問題:
- 可達性分析僅關注對象是否能從根集合到達,不會因循環引用無法回收。
-
適用於複雜的引用關係:
- 能處理對象圖中存在大量交叉引用的複雜場景。
-
分代垃圾回收的基礎:
- 新生代、老年代的分代策略都基於此方法。
缺點:
-
暫停應用線程:
- 傳統的垃圾收集器需要暫停所有應用線程(Stop-the-World),執行可達性分析,可能導致停頓時間較長。
-
實現複雜:
- 需要額外的輔助數據結構和更復雜的遍歷邏輯。
分代回收策略
- 新生代 GC(Minor GC)
-
觸發條件:
- 新生代的 Eden 區滿時觸發。
-
回收策略:
- 採用 複製算法:
- 將 Eden 區和一個 Survivor 區中的存活對象複製到另一個 Survivor 區或老年代。
- 減少內存碎片問題,適合對象存活時間短的特點。
-
特點:
- 回收速度快,代價低。
- 老年代 GC(Major GC / Full GC)
-
觸發條件:
- 老年代內存不足時觸發。
-
回收策略:
-
通常採用 標記-清除算法 或 標記-整理算法:
- 標記-清除算法:標記存活對象,清除無用對象,但可能會產生碎片。
- 標記-整理算法:在標記存活對象後,將存活對象移動到一側,整理內存,避免碎片。
-
特點:
- 回收速度較慢,代價高,可能會導致應用長時間停頓。
-
垃圾回收器
- Serial GC
-
特點:
- 單線程工作
- 適用於單核環境和小堆內存(100MB)
- 新生代使用複製算法,老年代使用標記-整理算法
-
優點:
- 實現簡單,適合小型應用
-
缺點
- 垃圾回收期間會停止所有應用線程(STW),延遲較高
-
適用環境
- 單線程,內存較小的應用
- Parallel GC
-
特點:
- 多線程執行垃圾回收,提升回收速度
- 注意吞吐量(應用運行時間與垃圾回收時間的比值)
- 新生代使用複製算法,老年代使用標記-整理算法
-
優點:
- 吞吐量高,適合多核CPU
-
缺點:
- STW時間比較長,不適合低延遲的場景
-
使用場景:
- 後台計算型任務,大批量數據處理應用
- CMS GC(Concurrent Mark-Sweep)
-
特點
- 專注於低延遲,適合對響應時間要求高的應用
- 老年代使用標記-清理算法,新生代使用複製算法
-
老年代回收分為以下階段
- 初始階段:標記GC Roots可達的對象(短暫暫停)
- 併發標記:多線程遍歷對象圖
- 重新標記:修正併發標記階段的變動對象(短暫暫停
- 併發清楚:清楚不可達對象
-
優點
- 停頓時間短,適合交互型應用。
-
缺點
- 容易產生內存碎片
- 併發清理階段可能導致系統吞吐量下降
-
使用場景
- 低延遲、高響應的應用,如Web服務器
- G1 GC(Garbage First)
-
特點:
- 通過將堆劃分為多個區域,以區域為單位回收
- 同時支持新生代和老年代的回收(混合回收)
- 停頓可控:允許用户設置最大停頓時間目標
-
回收過程
- 初始標記:標記 GC Roots 直接可達的對象。
- 併發標記:遍歷堆中的對象圖,標記存活對象。
- 篩選回收:按回收收益優先級回收部分區域。
- 清理:整理內存,釋放區域。
-
優點:
- 減少老年代碎片。
- 可控的停頓時間。
-
缺點:
- 相比 CMS,初期調優難度較高。
-
使用場景:
- 大堆內存、多核 CPU 的低延遲場景。
- ZGC(Z Garbage Collector)
-
特點:
- 超低延遲垃圾回收器,目標是將停頓時間控制在10ms以下
- 使用染色指針標記對象狀態
-
回收過程
- 併發標記:找到存活對象
- 併發轉移:將存活對象複製到新區域
- 併發清除:清除垃圾對象
- 支持非常大的堆內存(TB 級別)
-
優點:
- 停頓時間極低,適合實時性要求高的場景
-
缺點:
- 初期內存佔用比較高
- 僅支持64位系統
-
使用場景:
- 超低延遲需求的應用,如金融交易系統
- Shenandoah GC
-
特點:
- 和ZGC類似,目標是低延遲,減少STW時間
- 通過併發整理老年代對象,避免碎片化
-
優點:
- 和 G1 相比,停頓時間更短
-
缺點:
- 內存開銷高
-
使用場景:
-
延遲敏感的服務端應用
-
垃圾標記的過程
- 從根集合(GC Roots)出發:
- 垃圾標記階段以一組成為GC Roots的引用集合為起點。
-
常見的GC Roots包括:
- Java棧中的局部變量
- 方法區的靜態變量和常量
- 本地方法棧中的JNI引用
-
通過引用關係遍歷對象圖:
- 以GC Roots為起點,通過深度優先或者廣度優先的方式遍歷所有可達的對象。
- 遍歷到的對象會被標記為“存活”狀態
-
標記不可達對象:
- 未被訪問到的對象會被認為是垃圾,標記為“不可達”
垃圾標記的方式
垃圾標記的具體實現方式可能因垃圾回收算法不同而異。常見的標記方式有:
-
標記-清除算法(Mark-Sweep)
- 標記階段:從GC Roots出發,遍歷並標記所有存活的對象
- 清除階段:掃描堆內存,清理未被標記的對象
-
缺點:
- 標記和清理後會產生很多的碎片
-
標記-複製算法(Mark-Copy)
- 將可達對象複製到新的區域,未被複制的對象被認為是垃圾
-
優點:
- 避免內存碎片,但需要額外的內存空間
-
標記-整理算法(Mark-Compact)
- 標記階段:標記所有存活的對象
- 整理階段:將存活的對象壓到堆的一端,清理無用對象,釋放空間
-
優點:
- 解決了內存碎片的問題
-
三色標記法(多線程GC的經典方式)
- 白色:尚未訪問的對象(默認狀態,潛在垃圾)
- 灰色:已訪問但未處理完引用的對象
- 黑色:已訪問且其引用已處理完的對象
- 標記完成後,白色對象即為垃圾,可以被回收
GC Roots的作用
- GC Roots是垃圾標記的起點,決定了哪些對象是根可達的
-
可通過以下途徑訪問到的對象會被視為存活:
- 棧中的局部變量
- 靜態屬性引用的對象
- 常量引用的對象
- JNI(本地方法接口)中引用的對象
標記技術的優化
現代 GC 使用了多種技術優化垃圾標記的性能:
-
增量標記
- 講標記過程拆分成多個小步驟,減少GC的事件
-
併發標記
- 標記階段與應用線程一同進行,進一步降低停頓時間(G1和ZGC)
-
分代標記
- 對象分代後,新生代和老年代按照不同的標記策略,提升性能
加餐知識 三色標記法
是垃圾回收(GC)中的一種對象標記算法,常用於併發垃圾收集器(如 G1、ZGC)中。它通過將對象分為三種顏色(白色、灰色和黑色)來表示其標記狀態,能夠有效地支持併發標記和增量標記,從而減少 GC 暫停時間。
三色標記法的基本概念
-
白色(White)
- 未被標記到的對象
- 在標記階段開始時,所有對象都是白色
- 最終未被標記為其他顏色的白色對象會被回收
-
灰色(Gray)
- 已經被訪問,但其引用的子對象是尚未被完全處理的對象
- 灰色對象表示“正在處理”的狀態,需要繼續遞歸處理其引用
-
黑色(Black)
- 已經被訪問,且其引用的子對象全部被處理完畢的對象
- 黑色對象表示“完全標記完成”,不會再重新掃描
三色標記的過程
-
初始化階段:
- 所有對象默認為白色
- 將從GC Roots可達的對象放入灰色集合,表示需要處理
-
標記階段
- 從灰色集合中取出一個對象,將其標記為黑色
-
遍歷該對象的所有引用:
- 如果引用的對象是白色,將其標記為灰色,表示需要處理
- 如果引用的對象是灰色和黑色,則無需操作
- 重複該過程,直到灰色集合為空
-
清理階段
- 任何仍然是白色的對象都不可達,標記為垃圾,可以被回收。
三色核心原則
-
三色不變性
-
在標記過程中,三種顏色的對象之間關係遵循不變量:
- 黑色對象不能引用白色對象。
- 如果一個灰色對象引用了白色對象,那麼該白色對象最終會被處理。
-
-
強三色不變性
- 黑色對象不能直接或間接引用白色對象。
- 確保所有白色對象都無法通過黑色對象再次可達。
-
弱三色不變性
- 黑色對象可以直接引用白色對象,但灰色對象必須先處理所有引用。
- 更適合併發場景,因為更寬鬆的約束允許應用線程和標記線程同時運行。
併發標記中的問題
在併發場景下,應用線程和垃圾收集器同時運行可能導致 漏標記問題(對象被誤認為不可達),需要特殊處理。
問題:漂白問題(對象被錯誤回收)
- 如果應用線程在標記階段將某個黑色對象的引用指向一個白色對象,那麼該白色對象可能會跳過標記,最終被錯誤回收。
解決方案:寫屏障
為了避免上述問題,引入寫屏障機制。寫屏障通過攔截對象引用的修改操作,確保引用關係的改變不會破壞三色不變性。
兩種寫屏障策略:
-
增灰策略:
- 當應用線程將一個白色對象引用賦值給黑色對象時,將該白色對象重新標記為灰色。
- 確保其能被繼續處理。
-
增黑策略:
- 禁止應用線程直接修改黑色對象,使其引用白色對象,直到標記完成。
- 這種方式簡單但可能降低併發效率。
三色標記法的優缺點
優點:
-
支持併發標記:
- 標記線程和應用線程可以同時運行,減少停頓時間。
-
適合增量標記:
- 標記過程可以分為多個小步驟執行,避免長時間的暫停。
-
理論清晰:
- 基於可達性分析和三色不變性,邏輯明確,易於擴展。
缺點:
-
需要寫屏障:
- 為了維護三色不變性,需要引入寫屏障,增加了實現複雜度。
-
可能產生額外的開銷:
- 寫屏障和增灰策略會帶來額外的 CPU 負載。