JVM Garbage First(G1) 垃圾收集器

同優秀的CMS垃圾回收器同樣,算法

  • G1也是關注最小時延的垃圾回收器,
  • 也一樣適合大尺寸堆內存的垃圾收集,
  • 官方也推薦使用G1來代替選擇CMS。

G1最大的特色是引入分區的思路,安全

  • 弱化了分代的概念,
  • 合理利用垃圾收集各個週期的資源,
  • 解決了其餘收集器甚至CMS的衆多缺陷。

串行收集器

  • 串行收集器組合 Serial + Serial Old
  • 開啓選項:-XX:+SerialGC
  • 串行收集器是最基本、發展時間最長、久經考驗的垃圾收集器,
    • 也是client模式下的默認收集器配置
  • 串行收集器採用單線程stop-the-world(STW)的方式進行收集。
    • 當內存不足時,
      • 串行GC設置停頓標識,待全部線程都進入安全點(Safepoint)時,
        • 應用線程暫停,串行GC開始工做,
        • 採用單線程方式回收空間並整理內存。
      • 單線程也意味着
        • 複雜度更低、
        • 佔用內存更少,
        • 但同時也意味着不能有效利用多核優點。
      • 事實上,串行收集器特別適合堆內存不高、單核甚至雙核CPU的場合。

並行收集器

  • 並行收集器組合 Parallel Scavenge + Parallel Old
  • 開啓選項:-XX:+UseParallelGC-XX:+UseParallelOldGC(可互相激活)
  • 並行收集器是以關注吞吐量爲目標的垃圾收集器
    • 也是server模式下的默認收集器配置,
    • 對吞吐量的關注主要體如今年輕代Parallel Scavenge收集器上。

併發標記清除收集器

  • 併發標記清除收集器組合 ParNew + CMS + Serial Old
  • 開啓選項:-XX:+UseConcMarkSweepGC
  • 併發標記清除(CMS)是以關注延遲爲目標
    • 十分優秀的垃圾回收算法,
    • 開啓後,年輕代使用STW式的並行收集,
    • 老年代回收採用CMS進行垃圾回收,
    • 對延遲的關注也主要體如今老年代CMS上。
  • 年輕代ParNew與並行收集器相似,
  • 而老年代CMS每一個收集週期都要經歷:
    • 初始標記、
      • 以STW的方式標記全部的根對象
    • 併發標記、
      • 併發標記則同應用線程一塊兒並行,標記出根對象的可達路徑
    • 從新標記、
      • 標記那些應用線程修改而引發的可能錯過的可達對象
    • 併發清除
      • 最後獲得的不可達對象將在併發清除階段進行回收
    • 值得注意的是,初始標記和從新標記都已優化爲多線程執行
  • CMS很是適合堆內存大、
    • CPU核數多的服務器端應用,
    • 也是G1出現以前大型應用的首選收集器
  • 可是CMS並不完美,它有如下缺點:
    • (1)因爲併發進行,
      • CMS在收集與應用線程會同時會增長對堆內存的佔用,
        • 也就是說,CMS必需要在老年代堆內存用盡以前完成垃圾回收,
        • 不然CMS回收失敗時,將觸發擔保機制,
          • 串行老年代收集器將會以STW(stop-the-world)的方式進行一次GC,從而形成較大停頓時間;
    • (2)標記清除算法沒法整理空間碎片,
      • 老年代空間會隨着應用時長被逐步耗盡,
        • 最後將不得不經過擔保機制對堆內存進行壓縮。
      • CMS也提供了參數-XX:CMSFullGCsBeForeCompaction(默認0,即每次都進行內存整理)來指定多少次CMS收集以後,進行一次壓縮的Full GC。

Garbage First

  • Garbage First (G1)
    • 開啓選項:-XX:+UseG1GC
  • 以前介紹的幾組垃圾收集器組合,都有幾個共同點:
    • 年輕代、老年代是獨立且連續的內存塊;
    • 年輕代收集使用單eden、雙survivor進行復制算法;
    • 老年代收集必須掃描整個老年代區域;
    • 都是以儘量少而快地執行GC爲設計原則。
  • G1垃圾收集器也是
    • 以關注延遲爲目標、
    • 服務器端應用的垃圾收集器,
    • 被HotSpot團隊寄予取代CMS的使命
  • G1也有相似CMS的收集動做:
    • 初始標記、併發標記、從新標記、清除、轉移回收
    • 而且也以一個串行收集器作擔保機制
  • G1收集與以上三組收集器有很大不一樣:
    • G1的設計原則是"首先收集儘量多的垃圾(Garbage First)"。
      • G1並不會等內存耗盡(串行、並行)或者快耗盡(CMS)的時候開始垃圾收集,
        • 而是在內部採用了啓發式算法,
        • 在老年代找出具備高收集收益的分區進行收集。
      • 同時G1能夠根據用戶設置的暫停時間目標自動調全年輕代和總堆大小,
        • 暫停目標越短年輕代空間越小、總空間就越大;
    • G1採用內存分區(Region)的思路,
      • 將內存劃分爲一個個相等大小的內存分區,
        • 回收時則以分區爲單位進行回收,
        • 存活的對象複製到另外一個空閒分區中。
      • 因爲都是以相等大小的分區爲單位進行操做,
        • 所以G1自然就是一種壓縮方案(局部壓縮)
    • G1雖然也是分代收集器
      • 但整個內存分區不存在物理上的年輕代與老年代的區別,
      • 也不須要徹底獨立的survivor(to space)堆作複製準備。
      • G1只有邏輯上的分代概念,
      • 或者說每一個分區均可能隨G1的運行在不一樣代之間先後切換;
    • G1的收集都是STW(stop-the-world)的,
      • 但年輕代和老年代的收集界限比較模糊,
      • 採用了混合(mixed)收集的方式。
        • 即每次收集既可能只收集年輕代分區(年輕代收集),
        • 也可能在收集年輕代的同時,包含部分老年代分區(混合收集),
        • 這樣即便堆內存很大時,也能夠限制收集範圍,從而下降停頓。

G1的內存模型

  • 分區概念

  • 分區

    • G1採用了分區(Region)的思路,
      • 將整個堆空間分紅若干個大小相等的內存區域,
      • 每次分配對象空間將逐段地使用內存。
    • 所以,在堆的使用上,
      • G1並不要求對象的存儲必定是物理上連續的,
      • 只要邏輯上連續便可;
    • 每一個分區也不會肯定地爲某個代服務
      • 能夠按需在年輕代和老年代之間切換。
      • 啓動時能夠經過參數-XX:G1HeapRegionSize=n
      • 可指定分區大小(1MB~32MB,且必須是2的冪),
      • 默認將整堆劃分爲2048個分區
  • 卡片

    • 在每一個分區內部又被分紅了若干個大小爲512 Byte卡片(Card),
      • 堆內存最小可用粒度
    • 全部分區的卡片將會記錄在全局卡片表(Global Card Table)中,
      • 分配的對象會佔用物理上連續的若干個卡片,
      • 當查找對分區內對象的引用時即可經過記錄卡片來查找該引用對象(見RSet)。
      • 每次對內存的回收,都是對指定分區的卡片進行處理。
    • G1一樣能夠經過-Xms/-Xmx來指定堆空間大小。
    • 當發生年輕代收集或混合收集時,
      • 經過計算GC與應用的耗費時間比,
      • 自動調整堆空間大小
    • 若是GC頻率過高,則經過增長堆尺寸,來減小GC頻率,
      • 相應地GC佔用的時間也隨之下降;
    • 目標參數-XX:GCTimeRatio即爲GC與應用的耗費時間比,
      • G1默認爲9,而CMS默認爲99,
      • 由於CMS的設計原則是耗費在GC上的時間儘量的少。
    • 另外,當空間不足,如對象空間分配或轉移失敗時,
      • G1會首先嚐試增長堆空間
      • 若是擴容失敗,則發起擔保的Full GC
      • Full GC後,堆尺寸計算結果也會調整堆空間。
  • 分代模型

  • 分代

    • G1將內存在邏輯上劃分爲年輕代和老年代,
      • 其中年輕代又劃分爲Eden空間和Survivor空間。
    • 但年輕代空間並非固定不變的,
      • 當現有年輕代分區佔滿時,JVM會分配新的空閒分區加入到年輕代空間。
    • 整個年輕代內存會在初始空間-XX:G1NewSizePercent(默認整堆5%)與最大空間-XX:G1MaxNewSizePercent(默認60%)之間動態變化,
      • 且由參數目標暫停時間-XX:MaxGCPauseMillis(默認200ms)、須要擴縮容的大小以及分區的已記憶集合(RSet)計算獲得。
      • 固然,G1依然能夠設置固定的年輕代大小(參數-XX:NewRatio、-Xmn),但同時暫停目標將失去意義。
  • 本地分配緩衝

    • 本地分配緩衝 Local allocation buffer (Lab)
    • 因爲分區的思想,
      • 每一個線程都可以"認領"某個分區用於線程本地的內存分配,
      • 而不須要顧及分區是否連續。
    • 所以,每一個應用線程和GC線程都會獨立的使用分區,
      • 進而減小同步時間,提高GC效率,這個分區稱爲本地分配緩衝區(Lab)
    • 其中,應用線程能夠獨佔一個本地緩衝區(TLAB)來建立的對象,
    • 而大部分都會落入Eden區域(巨型對象或分配失敗除外),
      • 所以TLAB的分區屬於Eden空間;
    • 而每次垃圾收集時,每一個GC線程一樣能夠獨佔一個本地緩衝區(GCLAB)用來轉移對象,
      • 每次回收會將對象複製到Suvivor空間或老年代空間;
    • 對於從Eden/Survivor空間晉升(Promotion)到Survivor/老年代空間的對象,
      • 一樣有GC獨佔的本地緩衝區進行操做,
      • 該部分稱爲晉升本地緩衝區(PLAB)。
  • 分區模型

    • G1對內存的使用以分區(Region)爲單位,而對對象的分配以卡片(Card)爲單位
  • 巨型對象

    • 巨型對象 Humongous Region
      • 一個大小達到甚至超過度區大小一半的對象稱爲巨型對象(Humongous Object)。
    • 由於巨型對象的移動成本很高,
      • 並且有可能一個分區不能容納巨型對象。
    • 所以,巨型對象會直接在老年代分配,
      • 所佔用的連續空間稱爲巨型分區(Humongous Region)。
    • 巨型對象會獨佔一個、或多個連續分區,
      • 其中第一個分區被標記爲開始巨型(StartsHumongous),
      • 相鄰連續分區被標記爲連續巨型(ContinuesHumongous)。
  • 已記憶集合

    • 已記憶集合 Remember Set (RSet)
    • 在串行和並行收集器中,
      • GC經過整堆掃描,來肯定對象是否處於可達路徑中
    • G1爲了不STW式的整堆掃描,
      • 在每一個分區記錄了一個已記憶集合(RSet),
      • 內部相似一個反向指針,
      • 記錄引用分區內對象的卡片索引。
    • 當要回收該分區時,經過掃描分區的RSet,
      • 來肯定引用本分區內的對象是否存活,
      • 進而肯定本分區內的對象存活狀況。
    • 事實上,並不是全部的引用都須要記錄在RSet中,
      • 若是一個分區肯定須要掃描,那麼無需RSet也能夠無遺漏的獲得引用關係。
        • 那麼引用源自本分區的對象,固然不用落入RSet中;
        • 同時,G1 GC每次都會對年輕代進行總體收集,所以引用源自年輕代的對象,也不須要在RSet中記錄。
        • 最後只有老年代的分區可能會有RSet記錄,
          • 這些分區稱爲擁有RSet分區(an RSet’s owning region)。
  • Per Region Table

    • RSet在內部使用Per Region Table(PRT)記錄分區的引用狀況
    • 因爲RSet的記錄要佔用分區的空間,
      • 若是一個分區很是"受歡迎",
      • 那麼RSet佔用的空間會上升,從而下降分區的可用空間。
      • G1應對這個問題採用了改變RSet的密度的方式
        • 在PRT中將會以三種模式記錄引用:
          • 稀少:直接記錄引用對象的卡片索引
          • 細粒度:記錄引用對象的分區索引
          • 粗粒度:只記錄引用狀況,每一個分區對應一個比特位
        • 粗粒度的PRT只是記錄了引用數量
          • 須要經過整堆掃描才能找出全部引用,
          • 所以掃描速度也是最慢的。
  • 收集集合 (CSet)

    • 收集集合(CSet)表明每次GC暫停時回收的一系列目標分區。
    • 年輕代收集集合

      • 年輕代收集集合 CSet of Young Collection
      • 當JVM分配對象到Eden區域失敗(Eden區已滿)時,
        • 便會觸發一次STW式的年輕代收集。
      • 在年輕代收集中,
        • Eden分區存活的對象將被拷貝到Survivor分區;
        • 原有Survivor分區存活的對象,
          • 將根據任期閾值(tenuring threshold)分別晉升到PLAB中,
          • 新的survivor分區和老年代分區。
        • 而原有的年輕代分區將被總體回收掉。
      • 同時,年輕代收集還負責維護對象的年齡(存活次數),
        • 輔助判斷老化(tenuring)對象晉升的時候是到Survivor分區仍是到老年代分區。
        • 年輕代收集首先先將晉升對象尺寸總和、對象年齡信息維護到年齡表中,再根據年齡表、Survivor尺寸、Survivor填充容量-XX:TargetSurvivorRatio(默認50%)、最大任期閾值-XX:MaxTenuringThreshold(默認15),
        • 計算出一個恰當的任期閾值,凡是超過任期閾值的對象都會被晉升到老年代。
    • 混合收集集合

      • 混合收集集合 CSet of Mixed Collection
      • 當老年代佔用空間超過整堆比IHOP閾值-XX:InitiatingHeapOccupancyPercent(默認45%)時,
        • G1就會啓動一次混合垃圾收集週期
      • 爲了知足暫停目標(暫停的時間限制),
        • G1可能不能一口氣將全部的候選分區收集掉,
        • 所以G1可能會產生連續屢次的混合收集與應用線程交替執行,
        • 每次STW的混合收集與年輕代收集過程相相似。
相關文章
相關標籤/搜索