博客 / 詳情

返回

Java學習筆記(8)JVM垃圾回收

垃圾標記

是垃圾回收的第一階段,也是垃圾回收算法中關鍵的一步。它的主要目的是通過遍歷內存中存活的對象,來標記哪些對象仍是可達的(即活躍的),哪些對象是不可達的(既可以被垃圾回收)。

標記的結果,將作為下一步(清理,壓縮)的依據,確定哪些對象需要被釋放或者移動

垃圾回收策略

  1. 引用計數法

原理:

  • 每個對象維護一個 引用計數器,用於記錄該對象被引用的次數。
  • 當對象被創建或被引用時,計數器增加。
  • 當引用被移除或銷燬時,計數器減少。
  • 如果計數器為零,説明該對象不可達,可以被回收。

實現流程:

  • 初始化時,計數器設為 0。
  • 每當有新的引用指向該對象時,計數器 +1。
  • 每當引用消失時,計數器 -1。
  • 如果計數器為 0,該對象會被立即回收。

缺點:

  • 循環引用問題:

    • 如果兩個或多個對象相互引用,但沒有其他外部對象引用它們,則它們的計數器始終大於零,導致無法被回收。
    • 例如:
class Node {
    Node next;
}
Node a = new Node();
Node b = new Node();
a.next = b;
b.next = a;
  • 維護開銷大:

    • 每次引用變化都需要更新計數器,可能會影響性能。
  1. 根搜索法

原理:

  • 從一組稱為 GC Roots 的根對象開始,通過引用關係遍歷對象圖。
  • 如果某個對象可以通過 GC Roots 找到,則認為它是可達的;否則認為它是不可達的,可以被回收。

實現流程:

  • 從 GC Roots 出發,標記所有可以訪問到的對象。
  • 遍歷整個堆,將未被標記的對象視為垃圾,進行回收。

優點:

  • 無循環引用問題:

    • 可達性分析僅關注對象是否能從根集合到達,不會因循環引用無法回收。
  • 適用於複雜的引用關係:

    • 能處理對象圖中存在大量交叉引用的複雜場景。
  • 分代垃圾回收的基礎:

    • 新生代、老年代的分代策略都基於此方法。

缺點:

  • 暫停應用線程:

    • 傳統的垃圾收集器需要暫停所有應用線程(Stop-the-World),執行可達性分析,可能導致停頓時間較長。
  • 實現複雜:

    • 需要額外的輔助數據結構和更復雜的遍歷邏輯。

分代回收策略

  1. 新生代 GC(Minor GC)
  • 觸發條件:

    • 新生代的 Eden 區滿時觸發。
  • 回收策略:

    • 採用 複製算法:
    • 將 Eden 區和一個 Survivor 區中的存活對象複製到另一個 Survivor 區或老年代。
    • 減少內存碎片問題,適合對象存活時間短的特點。
  • 特點:

    • 回收速度快,代價低。
  1. 老年代 GC(Major GC / Full GC)
  • 觸發條件:

    • 老年代內存不足時觸發。
  • 回收策略:

    • 通常採用 標記-清除算法 或 標記-整理算法:

      • 標記-清除算法:標記存活對象,清除無用對象,但可能會產生碎片。
      • 標記-整理算法:在標記存活對象後,將存活對象移動到一側,整理內存,避免碎片。
    • 特點:

      • 回收速度較慢,代價高,可能會導致應用長時間停頓。

垃圾回收器

  1. Serial GC
  • 特點:

    • 單線程工作
    • 適用於單核環境和小堆內存(100MB)
    • 新生代使用複製算法,老年代使用標記-整理算法
  • 優點:

    • 實現簡單,適合小型應用
  • 缺點

    • 垃圾回收期間會停止所有應用線程(STW),延遲較高
  • 適用環境

    • 單線程,內存較小的應用
  1. Parallel GC
  • 特點:

    • 多線程執行垃圾回收,提升回收速度
    • 注意吞吐量(應用運行時間與垃圾回收時間的比值)
    • 新生代使用複製算法,老年代使用標記-整理算法
  • 優點:

    • 吞吐量高,適合多核CPU
  • 缺點:

    • STW時間比較長,不適合低延遲的場景
  • 使用場景:

    • 後台計算型任務,大批量數據處理應用
  1. CMS GC(Concurrent Mark-Sweep)
  • 特點

    • 專注於低延遲,適合對響應時間要求高的應用
    • 老年代使用標記-清理算法,新生代使用複製算法
    • 老年代回收分為以下階段

      • 初始階段:標記GC Roots可達的對象(短暫暫停)
      • 併發標記:多線程遍歷對象圖
      • 重新標記:修正併發標記階段的變動對象(短暫暫停
      • 併發清楚:清楚不可達對象
  • 優點

    • 停頓時間短,適合交互型應用。
  • 缺點

    • 容易產生內存碎片
    • 併發清理階段可能導致系統吞吐量下降
  • 使用場景

    • 低延遲、高響應的應用,如Web服務器
  1. G1 GC(Garbage First)
  • 特點:

    • 通過將堆劃分為多個區域,以區域為單位回收
    • 同時支持新生代和老年代的回收(混合回收)
    • 停頓可控:允許用户設置最大停頓時間目標
  • 回收過程

    • 初始標記:標記 GC Roots 直接可達的對象。
    • 併發標記:遍歷堆中的對象圖,標記存活對象。
    • 篩選回收:按回收收益優先級回收部分區域。
    • 清理:整理內存,釋放區域。
  • 優點:

    • 減少老年代碎片。
    • 可控的停頓時間。
  • 缺點:

    • 相比 CMS,初期調優難度較高。
  • 使用場景:

    • 大堆內存、多核 CPU 的低延遲場景。
  1. ZGC(Z Garbage Collector)
  • 特點:

    • 超低延遲垃圾回收器,目標是將停頓時間控制在10ms以下
    • 使用染色指針標記對象狀態
    • 回收過程

      • 併發標記:找到存活對象
      • 併發轉移:將存活對象複製到新區域
      • 併發清除:清除垃圾對象
    • 支持非常大的堆內存(TB 級別)
  • 優點:

    • 停頓時間極低,適合實時性要求高的場景
  • 缺點:

    • 初期內存佔用比較高
    • 僅支持64位系統
  • 使用場景:

    • 超低延遲需求的應用,如金融交易系統
  1. Shenandoah GC
  • 特點:

    • 和ZGC類似,目標是低延遲,減少STW時間
    • 通過併發整理老年代對象,避免碎片化
  • 優點:

    • 和 G1 相比,停頓時間更短
  • 缺點:

    • 內存開銷高
  • 使用場景:

    • 延遲敏感的服務端應用

垃圾標記的過程

  1. 從根集合(GC Roots)出發:
  2. 垃圾標記階段以一組成為GC Roots的引用集合為起點。
  3. 常見的GC Roots包括:

    • Java棧中的局部變量
    • 方法區的靜態變量和常量
    • 本地方法棧中的JNI引用
  • 通過引用關係遍歷對象圖:

    • 以GC Roots為起點,通過深度優先或者廣度優先的方式遍歷所有可達的對象。
    • 遍歷到的對象會被標記為“存活”狀態
  • 標記不可達對象:

    • 未被訪問到的對象會被認為是垃圾,標記為“不可達”

垃圾標記的方式

垃圾標記的具體實現方式可能因垃圾回收算法不同而異。常見的標記方式有:

  1. 標記-清除算法(Mark-Sweep)

    • 標記階段:從GC Roots出發,遍歷並標記所有存活的對象
    • 清除階段:掃描堆內存,清理未被標記的對象
    • 缺點:

      • 標記和清理後會產生很多的碎片
  2. 標記-複製算法(Mark-Copy)

    • 將可達對象複製到新的區域,未被複制的對象被認為是垃圾
    • 優點:

      • 避免內存碎片,但需要額外的內存空間
  3. 標記-整理算法(Mark-Compact)

    • 標記階段:標記所有存活的對象
    • 整理階段:將存活的對象壓到堆的一端,清理無用對象,釋放空間
    • 優點:

      • 解決了內存碎片的問題
  4. 三色標記法(多線程GC的經典方式)

    • 白色:尚未訪問的對象(默認狀態,潛在垃圾)
    • 灰色:已訪問但未處理完引用的對象
    • 黑色:已訪問且其引用已處理完的對象
    • 標記完成後,白色對象即為垃圾,可以被回收

GC Roots的作用

  • GC Roots是垃圾標記的起點,決定了哪些對象是根可達的
  • 可通過以下途徑訪問到的對象會被視為存活:

    • 棧中的局部變量
    • 靜態屬性引用的對象
    • 常量引用的對象
    • JNI(本地方法接口)中引用的對象

標記技術的優化

現代 GC 使用了多種技術優化垃圾標記的性能:

  1. 增量標記

    • 講標記過程拆分成多個小步驟,減少GC的事件
  2. 併發標記

    • 標記階段與應用線程一同進行,進一步降低停頓時間(G1和ZGC)
  3. 分代標記

    • 對象分代後,新生代和老年代按照不同的標記策略,提升性能

加餐知識 三色標記法

是垃圾回收(GC)中的一種對象標記算法,常用於併發垃圾收集器(如 G1、ZGC)中。它通過將對象分為三種顏色(白色、灰色和黑色)來表示其標記狀態,能夠有效地支持併發標記和增量標記,從而減少 GC 暫停時間。

三色標記法的基本概念

  1. 白色(White)

    • 未被標記到的對象
    • 在標記階段開始時,所有對象都是白色
    • 最終未被標記為其他顏色的白色對象會被回收
  2. 灰色(Gray)

    • 已經被訪問,但其引用的子對象是尚未被完全處理的對象
    • 灰色對象表示“正在處理”的狀態,需要繼續遞歸處理其引用
  3. 黑色(Black)

    • 已經被訪問,且其引用的子對象全部被處理完畢的對象
    • 黑色對象表示“完全標記完成”,不會再重新掃描

三色標記的過程

  1. 初始化階段:

    • 所有對象默認為白色
    • 將從GC Roots可達的對象放入灰色集合,表示需要處理
  2. 標記階段

    • 從灰色集合中取出一個對象,將其標記為黑色
    • 遍歷該對象的所有引用:

      • 如果引用的對象是白色,將其標記為灰色,表示需要處理
      • 如果引用的對象是灰色和黑色,則無需操作
    • 重複該過程,直到灰色集合為空
  3. 清理階段

    • 任何仍然是白色的對象都不可達,標記為垃圾,可以被回收。

三色核心原則

  1. 三色不變性

    • 在標記過程中,三種顏色的對象之間關係遵循不變量:

      • 黑色對象不能引用白色對象。
      • 如果一個灰色對象引用了白色對象,那麼該白色對象最終會被處理。
  2. 強三色不變性

    • 黑色對象不能直接或間接引用白色對象。
    • 確保所有白色對象都無法通過黑色對象再次可達。
  3. 弱三色不變性

    • 黑色對象可以直接引用白色對象,但灰色對象必須先處理所有引用。
    • 更適合併發場景,因為更寬鬆的約束允許應用線程和標記線程同時運行。

併發標記中的問題

在併發場景下,應用線程和垃圾收集器同時運行可能導致 漏標記問題(對象被誤認為不可達),需要特殊處理。

問題:漂白問題(對象被錯誤回收)

  • 如果應用線程在標記階段將某個黑色對象的引用指向一個白色對象,那麼該白色對象可能會跳過標記,最終被錯誤回收。

解決方案:寫屏障

為了避免上述問題,引入寫屏障機制。寫屏障通過攔截對象引用的修改操作,確保引用關係的改變不會破壞三色不變性。

兩種寫屏障策略:

  1. 增灰策略:

    • 當應用線程將一個白色對象引用賦值給黑色對象時,將該白色對象重新標記為灰色。
    • 確保其能被繼續處理。
  2. 增黑策略:

    • 禁止應用線程直接修改黑色對象,使其引用白色對象,直到標記完成。
    • 這種方式簡單但可能降低併發效率。

三色標記法的優缺點

優點:

  1. 支持併發標記:

    • 標記線程和應用線程可以同時運行,減少停頓時間。
  2. 適合增量標記:

    • 標記過程可以分為多個小步驟執行,避免長時間的暫停。
  3. 理論清晰:

    • 基於可達性分析和三色不變性,邏輯明確,易於擴展。

缺點:

  1. 需要寫屏障:

    • 為了維護三色不變性,需要引入寫屏障,增加了實現複雜度。
  2. 可能產生額外的開銷:

    • 寫屏障和增灰策略會帶來額外的 CPU 負載。
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.