不管卷不卷,面試還是得問問你G1原理!

所有的垃圾回收器的目的都是朝著減少STW的目的而前進,G1(Garbage First)回收器的出現(xiàn)顛覆了之前版本CMS、Parallel等垃圾回收器的分代收集方式,從2004年Sun發(fā)布第一篇關于G1的論文后,直到2012年JDK7發(fā)布更新版本,花了將近10年的時間G1才達到商用的程度,而到JDK9發(fā)布之后,G1成為了默認的垃圾回收器,CMS也變相地相當于被淘汰了。

G1結構

G1拋棄了之前的分代收集的方式,面向整個堆內存進行回收,把內存劃分為多個大小相等的獨立區(qū)域Region。

一共有4種Region:

  1. 自由分區(qū)Free Region
  2. 年輕代分區(qū)Young Region,年輕代還是會存在Eden和Survivor的區(qū)分
  3. 老年代分區(qū)Old Region
  4. 大對象分區(qū)Humongous Region

每個Region的大小通過-XX:G1HeapRegionSize來設置,大小為1~32MB,默認最多可以有2048個Region,那么按照默認值計算G1能管理的最大內存就是32MB*2048=64G。

對于大對象的存儲,存在Humongous概念,對G1來說,超過一個Region一半大小的對象都被認為大對象,將會被放入Humongous Region,而對于超過整個Region的大對象,則用幾個連續(xù)的Humongous來存儲(如下圖H區(qū)域)。

image

G1優(yōu)勢

上面我們也提到,垃圾回收器的最終目的都是為了減少STW造成的停頓,比如之前老的垃圾回收器CMS這種帶來的停頓時間是不可預估的。

而G1最大的優(yōu)勢就在于可預測的停頓時間模型,我們可以自己通過參數(shù)-XX:MaxGCPauseMillis來設置允許的停頓時間(默認200ms),G1會收集每個Region的回收之后的空間大小、回收需要的時間,根據(jù)評估得到的價值,在后臺維護一個優(yōu)先級列表,然后基于我們設置的停頓時間優(yōu)先回收價值收益最大的Region。

那么,這個可預測的停頓時間模型怎么計算和建立的?主要是基于衰減平均值的理論基礎,衰減平均是一種數(shù)學方法,用來計算一個數(shù)列的平均值,給近期的數(shù)據(jù)更高的權重,強調近期數(shù)據(jù)對結果的影響,代碼如下:

hotspot/src/share/vm/gc_implementation/g1/g1CollectorPolicy.hpp
double get_new_prediction(TruncatedSeq* seq) {
  return MAX2(seq->davg() + sigma() * seq->dsd(),
              seq->davg() * confidence_factor(seq->num()));
}

davg表示衰減值

sigma表示一個系數(shù),代表信貸度,默認值為0.5

dsd表示衰減標準偏差

confidence_factor表示可信度系數(shù),用于當樣本數(shù)據(jù)不足(小于5個)時取一個大于1的值,樣本數(shù)據(jù)越少該值越大。

基于這個模型,G1希望根據(jù)用戶設置的停頓時間(只是期望時間,盡量努力在這個范圍內完成GC)來選擇需要對哪些Region進行回收,能回收多大空間。

比如過去10次回收10G內存花費1s,如果預設的停頓時間是200ms,那么就最多可以回收2G的內存空間。

空間分配&擴展

既然G1還是存在新生代和老年代的概念,那么新生代和老年代的空間是怎么劃分的呢?

在G1中,新增了兩個參數(shù)G1MaxNewSizePercent、G1NewSizePercent,用來控制新生代的大小,默認的情況下G1NewSizePercent為5,也就是占整個堆空間的5%,G1MaxNewSizePercent默認為60,也就是堆空間的60%。

假設現(xiàn)在我們的堆空間大小是4G,按照默認最大2048個Region計算,每個Region的大小就是2M。

初始新生代的大小那么就是200M,大約100個Region格子,動態(tài)擴展最大就是60%*4G=2.4G大小。

image

不過顯然,事情不是這么簡單,實際上初始化新生代的空間大小邏輯還是挺復雜的。

首先,我們通過原有參數(shù)-Xms設置初始堆的大小,-Xmx設置最大堆的大小還是生效的,可以設置堆的大小。

  1. 可以通過原有參數(shù)-Xmn或者新的參數(shù)G1NewSizePercent、G1MaxNewSizePercent來設置年輕代的大小,如果設置了-Xmn相當于設置G1NewSizePercent=G1MaxNewSizePercent。

  2. 接著看是不是設置了-XX:NewRatio(表示年輕代與老年代比值,默認值為2,代表年輕代老年代大小為1:2),如果1都設置了,那么忽略NewRatio,反之則代表G1NewSizePercent=G1MaxNewSizePercent,并且分配規(guī)則還是按照NewRatio的規(guī)則。

  3. 如果只是設置了G1NewSizePercent、G1MaxNewSizePercent中的一個,那么就按照這兩個參數(shù)的默認值5%和60%來設置。

  4. 如果設置了-XX:SurvivorRatio,默認為8,那么Eden和Survivor還是按照這個比例來分配

按照這個規(guī)則,我們新生代和老年代的空間分配基本就完成,如果說新生代走默認的規(guī)則,每次動態(tài)擴展空間大小怎么辦?

有一個參數(shù)叫做-XX:GCTimeRatio表示GC時間與應用耗費時間比,默認為9,就是說GC時間和應用時間占比超過10%才進行擴展,擴展比例為20%,最小不能小于1M。

回收過程

G1的回收過程分為以下四個步驟:

  1. 初始標記:標記GC ROOT能關聯(lián)到的對象,需要STW
  2. 并發(fā)標記:從GCRoots的直接關聯(lián)對象開始遍歷整個對象圖的過程,掃描完成后還會重新處理并發(fā)標記過程中產生變動的對象
  3. 最終標記:短暫暫停用戶線程,再處理一次,需要STW
  4. 篩選回收:更新Region的統(tǒng)計數(shù)據(jù),對每個Region的回收價值和成本排序,根據(jù)用戶設置的停頓時間制定回收計劃。再把需要回收的Region中存活對象復制到空的Region,同時清理舊的Region。需要STW。

總的來說這是一個偏向記憶的回收過程,知道就行了。

相對于之前我們存在分代概念的GC來說,G1其實也是類似的過程,總體可以分為這兩種:

  1. 年輕代GC,年輕代Region在超過我們默認設置的最大大小之后就會觸發(fā)GC,還是用的我們熟悉的復制算法,Eden和Survivor來回倒騰,這里不再贅述。
  2. Mixed GC混合回收,混合回收類似于之前我們的Full GC概念,既會回收年輕代的Region,也會回收老年代的Region,還有我們新的Humongous大對象區(qū)域。觸發(fā)規(guī)則根據(jù)參數(shù)-XX:InitiatingHeapOccupancyPercent(默認45%)值,也就是說老年代Region達到整個堆內存的45%時觸發(fā)Mixed GC。

其他問題

上面應該把基本概念都解釋完了。

比如什么是G1?G1有什么特點?他的優(yōu)點是什么?劃分Region后怎么分配空間?怎么進行垃圾回收?什么時候進行YGC?什么時候進行FGC?可靠的停頓時間模型建立方式?

除此之外,其實還有一些較為復雜的問題,比如之前我們說分代收集有跨代引用的問題,劃分Region之后應該也有對不對,那怎么解決的?

還有之前我們說并發(fā)收集階段怎么解決用戶線程和收集線程互不干擾的?

這些更深一點的問題其實在現(xiàn)在已經卷到需要問三色標記了嗎?已經說到了很多了,下面我們再詳細點說明下在G1中的一些不同點。

記憶集

在這篇文章中我們提到過一次關于Remembered Set的概念,為了避免GC時掃描整個堆內存,用來標志哪些區(qū)域存在跨代引用,對于G1來說也一樣,只不過G1的記憶集會更復雜一點。

每個Region中都存在一個Hash Table結構的記憶集,Key為其他Region的起始地址,Value是其他Card Table卡表的索引集合。

原來我們的卡表指向的是卡頁的內存地址段,代表我引用了誰,現(xiàn)在的記憶集則是代表著誰引用了我,因此收集的過程會更復雜一點,并且需要額外的10%~20%的堆內存空間來維持。

維護記憶集的方式也和卡表類似,通過寫屏障來實現(xiàn)。

原始快照SATB

在三色標記中我們也提到過,并發(fā)標記用戶線程和收集線程一起工作會產生問題,解決方案CMS使用的是增量更新,G1則是用原始快照。

總結

寫這些東西比較費勁,因為總在想在理解的基礎上怎么寫的更通俗易懂,但是發(fā)現(xiàn)好像并不容易,因為自己也都是看完沒過多久就忘記了,所以記錄下來,能看懂就行了,實在不行就去看書。

周老師的深入Java虛擬機寫的比較簡單,很多東西要去搜資料和書結合看才能看明白,另外一本書寫的也不是很好,作者感覺只是堆砌知識點,看起來很費勁,美團寫的那篇文章也是一大堆名詞,不知道的人看的簡直蛋疼。

我應該,比他們寫的更通俗一點就好了?

參考:

彭成寒《JVM G1源碼分析和調優(yōu)》

周志明《深入理解Java虛擬機第三版》

美團:Java Hotspot G1 GC的一些關鍵技術

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容