深刻學習 G1回收器和JVM:混合回收(6)

混合回收能夠總結爲兩個階段:
  • 併發標記:算法

    • 目的是識別老生代分區中的活躍對象,並計算分區中的垃圾對象佔空間的多少,用於垃圾回收過程當中判斷是否回收分區。
  • 垃圾回收:併發

    • 和新生代回收步驟一致,重用了新生代回收的代碼,最大的不一樣就是回收的時候不只僅回收新生代分區,同時回收併發標記中識別的垃圾多的老生代分區。

併發標記算法詳解

併發標記算法是混合回收中最重要的算法。併發標記指的是標記線程和mutator線程併發運行。
併發標記算法設計了4個指針
  1. Bottom:底部位置
  2. Prev:指向上次併發處理後的地址
  3. Next:指向併發標記開始以前內存已經分配成功的地址
  4. Top:在併發標記開始後,若是有新的對象分配,能夠移動top指針,使top指針指向當前內存分配成功的地址。
  • Next和Top之間就是Mutator線程新增的對象使用的地址。
  • 假設Prev以前的對象已經標記成功,在併發標記的時候從根出發,不只僅標記Prev和Next之間的對象,還標記Prev以前的活躍對象。當併發標記結束以後,只需將- Prev指針設置爲Next指針便可開始新一輪的標記處理。
  • 併發標記引入兩個位圖:性能

    • PrevBitMap:記錄Prev指針以前的內存標記情況
    • NextBitMap:表示整個內存到next指針以前的標記狀態
併發標記開始以前:
併發標記開始以前

TAMS指的是Top-at-Mark-Start,併發標記結束後,NextBitMap標記了分區對象的存活狀況。假定位圖中黑色區域表示堆分區中對應的對象還活着,在併發標記的同時Mutator繼續運行,因此Top會繼續增加。優化

第二次標記開始,將NextBitMap值賦給PrevBitMap,將Next指針位置設置爲Prev,將Top指針位置設置爲Next指針。spa

併發標記結束狀態
併發標記結束狀態

併發標記第二次開始前的狀態
併發標記第二次開始前的狀態線程

併發標記第二次結束狀態
併發標記第二次結束狀態設計

併發標記算法的難點

主要是GC在標記的時候,mutator線程可能正在改變對象的引用關係圖,從而形成漏標和錯標。3d

  • 錯標:不會影響程序正確性,只是會產生浮動垃圾
  • 漏標:可能會致使可達對象被當成垃圾回收掉,從而影響程序的正確性。

三色標記法

三色標記法是一個邏輯上的抽象,將對象分紅三種顏色
  • 白色:表示尚未被收集器標記的對象
  • 灰色:表示自身已經被標記到,但其擁有的field字段引用到的其餘對象還沒被處理
  • 黑色:表示自身已經被標記到,且對象自己全部的field引用到的對象也已經被標記。
對象在併發標記階段會被漏標的充分必要條件是:
  1. Mutator插入了一個從黑色對象到該白色對象的新引用,由於黑色對象已經被標記,若是不對黑色對象從新處理,那麼白色對象將會被漏標,形成錯誤。
  2. Mutator刪除了從灰色對象到該白色對象的直接或間接引用,由於灰色對象正在標記,字段引用的對象沒有被標記,若是這個引用的白色對象被刪除了(引用發生了變化,那麼這個引用也有可能被漏標)。

要避免漏標,只要打破上面任意一個便可指針

  • 經過增量更新算法關注對象的引用插入,把被更新的黑色活着白色的對象標記爲灰色,打破第一個條件。
  • SATB關注引用的刪除,即在對象被複制前,把老的被引用對象記錄下來,而後根據這些對象爲根從新標記一遍,打破第二個條件。

混合回收的步驟

  1. 第一階段:併發標記code

    1. 初始標記子階段
    2. 併發標記子階段
    3. 再標記子階段
    4. 清理子階段
  2. 第二階段:垃圾回收

    1. 初始標記子階段:負責標記全部直接可達的根對象(棧對象,全局對象,JNI對象等),根是對象圖的起點,所以須要將Mutator線程暫停,須要一次STW
    2. 併發標記子階段:當YGC結束後,若是發現知足併發標記的條件,併發線程就開始併發標記。根據新生代的Survivor分區以及老生代的RSet開始併發標記。併發標記的時機是在YGC後,只有達到InitiatingHeapOccupancyPercent閾值後,纔會觸發併發標記。InitiatingHeapOccupancyPercent默認值是45,表示的是當已經分配的內存加上即將分配的內存超過內存總容量的45%就能夠開始併發標記,併發標記會對全部分區進行標記,且並不須要STW
    3. 再標記子階段:是最後一個標記階段,須要一個STW,找出全部未被訪問的存活對象,同時完成存活內存數據計算。要結束標記,須要知足三個條件:

      1. 從根(Survivor)出發,併發標記子階段已經追蹤了全部的存活對象。
      2. 標記棧是空的。
      3. 全部的引用變動都被處理了;這裏的引用變動包括新增空間的分配和引用變動,新增的空間全部對象都認爲是活的,引用變動處理SATB。
最後一個階段很難達成,若是不STW,應用會不斷的更新引用,產生新的引用變動,則會永遠沒法結束標記。
  1. 清理子階段:須要一個STW,清理子階段主要執行如下操做:

    1. 統計存活對象:這是利用RSet和BitMap完成的,統計的結果將會用來排序分區gion,以用於下一次CSet的選擇;根據SATB算法,須要把新分配的對象,都視爲活躍對象。
    2. 交換標記位圖:爲下次併發準備
    3. 重製RSet:此時老年代分區已經標記完成,若是標記後的分區沒有引用對象,這說明引用已經改變,這個時候能夠刪除原來的RSet裏的引用關係。
    4. 把空閒分區放入分區列表中:這裏的空間指的是全都是垃圾對象的分區,若是分區還有任何分區活躍對象都不會釋放,真正的釋放是在混合GC中。
清理操做不會清理垃圾對象,也不會執行存活對象的拷貝。
  1. 混合回收階段的分析:選出若干個分區,將這些分區存活的對象複製到空閒的分區去,同時把這些已經被回收的分區放入空閒分區列表
  2. 併發標記的正確性分析:目的是爲了識別老生代分區使用的狀況,在下一次回收的時候優先選擇垃圾比較多的分區進行回收。

併發標記流程

GC活動圖

GC線程活動圖

併發標記是依賴於YGC,即併發標記發生前必定有一次YGC。在併發標記結束以後,會更新CSetChooser,此時若是在發生GC,則判斷是否可以進行混合GC,混合GC的條件是上次發生的YGC不包含初始標記,而且CSetChooser包含有效的分區。

參數優化

  • 參數InitiatingHeapOccupancyPercent(簡稱爲IHOP,默認值爲45,這個值是啓動併發標記的先決條件,只有當老生代內存佔總空間45%以後纔會啓動併發標記任務。增長該值,將致使併發標記可能花費更多的時間,也會致使YGC或者混合GC收集時收集的分區變少,但另外一方面就有可能致使FGC。根據經驗這個值一般根據總體應用佔用的平均內存來設置,能夠把該值設置得比平均內存稍高一些,此時性能最好(即YGC/混合GC比較快,且FGC比較少)。那麼如何獲得應用程序在運行時的內存使用狀況?能夠打開G1PrintHeapRegions觀察內存的分配和使用狀況,另外JVM提供了一個診斷選項G1PrintRegionLivenessInfo,打開該選項,能夠查看到內存的使用狀況。IHOP的設置很是有用,可是設置合理的IHOP並不容易,須要不斷地嘗試。
  • 參數G1ReservePercent,默認值爲10當發現GC晉升失敗致使FGC,能夠增大該值
  • 參數ConcGCThreads爲併發線程數,默認值爲0,若是沒有設置則動態調整;使用ParallelGCThreads(前文介紹過推斷依據)爲依據來推斷。ConcGCThreads=(ParallelGCThreads+2)/4,最小值爲1,若是發現併發標記耗時較多能夠增大該值,注意增大該值會致使Mutator執行的吞吐量變小。
  • 參數HeapSizePerGCThread,默認值爲64M,能夠簡單地理解爲每64M分配一個線程
  • 參數UseDynamicNumberOfGCThreads,默認爲false,打開該值表示能夠動態調整線程數;調整的依據會根據最大線程數、HeapSizePerGCThread等肯定。
  • 參數ForceDynamicNumberOfGCThreads,默認爲false,打開該值表示能夠動態調整,和UseDynamicNumberOfGCThreads功能相似。
  • 參數G1SATBBufferSize,默認值爲1K,表示每一個STAB隊列最多存放1000個灰色對象,注意這裏不是SATBqueueset的大小。
  • 參數G1SATBBufferEnqueueingThresholdPercent(默認值是60),表示當一個隊列滿了以後,首先進行過濾處理,過濾後若是使用率超過這個閾值把隊列送入到queueset並新分配一個隊列。
  • 參數MarkStackSize和MarkStackSizeMax,在32位JVM中設置爲32k和4M,64位JVM中設置爲4M和512M。若是沒有設置能夠啓發式推斷參數,確保MarkStackSize最小爲32k(或者和併發線程參數ParallelGCThreads正相關,如ParallelGCThreads=8,則32位JVM中MarkStackSize=8×16k=128K,其中16k是隊列的大小),這個參數是併發標記子階段中用到的標記棧的大小。
  • 參數GCDrainStackTargetSize,默認值爲64,表示併發標記子階段處理時爲了保證處理的性能,一次標記的最多對象個數。
  • 參數G1MixedGCLiveThresholdPercent,默認值85,用於判斷分區可否被加入到CSet中,低於該值將會被加入。
  • 參數G1HeapWastePercent,默認值5,即當CSet中可回收空間的佔總空間的比例大於G1HeapWastePercent纔會開始混合收集。
  • 參數G1MixedGCCountTarget,默認值爲8,這個參數越大,收集老生代的分區越少,反之收集的分區越多。要保持老生代分區在CSet中的比例超過1/G1MixedGCCountTarget。
  • 參數G1OldCSetRegionThresholdPercent,參數默認值是10,即一次最多收集10%的分區。
  • 參數G1ConcMarkStepDurationMillis,默認值爲10,表示每一個併發標記子階段每次最多執行10ms。
  • 參數G1UseConcMarkReferenceProcessing,默認值爲true,打開表示在併發標記的時候能夠標記引用。
  • 在打開引用處理時,每次標記處理引用的對象數由G1RefProcDrainInterval控制,默認值爲10。
  • 參數ClassUnloadingWithConcurrentMark,默認值爲true,打開表示在併發標記的時候能夠卸載已經加載的類。
相關文章
相關標籤/搜索