垃圾回收(3)G1的結構和概念

G1介紹

G1(Garbage First)算法,經過參數-XX:+UseG1GC來啓用,該算法在JDK 7u4版本被正式推出,G1能夠經過參數-XX:MaxGCPauseMillis控制GC暫停時間。算法

Region

在G1算法中,採用了另一種徹底不一樣的方式組織堆內存,堆內存被劃分爲多個大小相等的內存塊(Region),每一個Region是邏輯連續的一段內存,結構以下:
垃圾回收(3)G1的結構和概念數組

每一個Region被標記了E、S、O和H,說明每一個Region在運行時都充當了一種角色,其中H是以往算法中沒有的,它表明Humongous,這表示這些Region存儲的是巨型對象(humongous object,H-obj),當新建對象大小超過Region大小一半時,直接在新的一個或多個連續Region中分配,並標記爲H。數據結構

堆內存中一個Region的大小能夠經過-XX:G1HeapRegionSize參數指定,大小區間只能是1M、2M、4M、8M、16M和32M,總之是2的冪次方,默認把堆內存按照2048份均分,也就是默認2048個Region。好比-Xmx16g -Xms16g,G1就會採用16G / 2048 = 8M 的Region。併發

垃圾回收(3)G1的結構和概念

GC模式

G1中提供了三種模式垃圾回收模式,Young gc、Mixed gc 和 Full gc,在不一樣的條件下被觸發。ide

一、Young gc

發生在年輕代的GC算法,通常對象(除了巨型對象)都是在eden region中分配內存,當全部eden region被耗盡沒法申請內存時,就會觸發一次young gc,這種觸發機制和以前的young gc差很少,執行完一次young gc,活躍對象會被拷貝到survivor region或者晉升到old region中。
年輕代,默認佔用堆大小由如下參數控制:
垃圾回收(3)G1的結構和概念post

二、Mixed gc

當愈來愈多的對象晉升到老年代old region時,爲了不堆內存被耗盡,虛擬機會觸發一個混合的垃圾收集器,即mixed gc,該算法並非一個old gc,除了回收整個young region,還會回收一部分的old region,這裏須要注意:是一部分老年代,而不是所有老年代,能夠選擇哪些old region進行收集,通常是選擇回收效益最高的,也就是垃圾最多的region,從而能夠對垃圾回收的耗時時間進行控制。性能

mixed gc中也有一個閾值參數 -XX:InitiatingHeapOccupancyPercent,當老年代大小佔整個堆大小百分比達到該閾值時,會觸發一次mixed gc.優化

三、Full gc

若是對象內存分配速度過快,mixed gc來不及回收,致使老年代被填滿,就會觸發一次full gc,G1的full gc算法就是單線程執行的serial old gc,會致使異常長時間的暫停時間,須要進行不斷的調優,儘量的避免full gc.線程

G1中的數據結構

一、TLAB(線程本地分配緩衝區Thread Local Allocation Buffer)

因爲堆內存是應用程序共享的,應用程序的多個線程在分配內存的時候須要加鎖以進行同步。爲了不加鎖,提升性能每個應用程序的線程會被分配一個TLAB。TLAB中的內存來自於G1年輕代中的內存分段。當對象不是Humongous對象,TLAB也能裝的下的時候,對象會被優先分配於建立此對象的線程的TLAB中。這樣分配會很快,由於TLAB隸屬於線程,因此不須要加鎖。當TLAB的剩餘空間不知足分配需求,則從新申請一塊TLAB空間。設計

二、Card (卡表)

一、很小的內存區域,G1將Java堆劃分爲相等大小的一個個區域,這個小的區域大小是512 Byte,稱爲Card
二、Card Table維護着全部的Card。Card Table的結構是一個字節數組,Card Table用這個數組映射着每個Card
三、Card中對象的引用發生改變時,Card在Card Table數組中對應的值被標記爲dirty,就稱這個Card被髒化了
四、分配對象會佔用物理上連續若干個卡片

三、Rset(已記憶集合)

一、每一個Region初始化時,會初始化一個remembered set
二、RSet裏面記錄了引用——就是其餘Region中指向本Region中全部對象的全部引用,也就是誰引用了個人對象即RSet須要記錄的東西應該是 xx Region的 xx Card。
三、RSet實際上是一個Hash Table,Key是其餘的Region的起始地址,Value是一個集合,裏面的元素是Card Table 數組中的index,既Card對應的Index,映射到對象的Card地址。
垃圾回收(3)G1的結構和概念
Region1和Region3中有對象引用了Region2的對象,則在Region2的Rset中記錄了這些引用。

Rset實現過程

爲了維護這些RSet,若是每次給引用類型的字段賦值都要更新RSet,這帶來的額外開銷實在太大,G1中採用post-write barrier(寫後柵欄)和concurrent refinement threads(併發後臺線程)實現了RSet的更新。

//假設對象young和old分別在不一樣的Region中
Object young = new Object();
old.p = young;

Java層面給old對象的p字段賦值young對象的先後,JVM會插入一個pre-write barrier(寫前柵欄)和post-write barrier(寫後柵欄)。

一、寫前柵欄 Pre-Write Barrrier:即將執行一段賦值語句時,等式左側對象將修改引用到另外一個對象,那麼JVM就須要在賦值語句生效以前,記錄喪失引用的對象。
二、寫後柵欄 Post-Write Barrrier:當執行一段賦值語句後,等式右側對象獲取了左側對象的引用,一樣須要記錄

其中post-write barrier的最終動做以下:
一、找到該字段所在的位置(Card),並設置爲dirty_card
二、若是當前是應用線程,每一個Java線程有一個dirty card queue,把該card插入隊列
三、除了每一個線程自帶的dirty card queue,還有一個全局共享的queue

賦值動做到此結束,接下來的RSet更新操做交由多個ConcurrentG1RefineThread併發完成,每當全局隊列集合超過必定閾值後,ConcurrentG1RefineThread會取出若干個隊列,遍歷每一個隊列中記錄的card,並進行處理,邏輯以下:

一、根據card的地址,計算出card所在的Region
二、若是Region不存在,或者Region是Young區,或者該Region在回收集合(CSet)中,則不進行處理
三、處理該card中的對象,將應用關係寫入Rset中。

RSet有什麼好處?
進行垃圾回收時,若是Region1有根對象A引用了Region2的對象B,顯然對象B是活的,若是沒有Rset,就須要掃描整個Region1或者其它Region,才能肯定對象B是活躍的,有了Rset能夠避免對整個堆進行掃描。

四、CSet(回收集合Collection Set)

一、它記錄了GC要收集的Regions集合
二、在任意一次收集暫停中,CSet全部分區都會被釋放,內部存活的對象都會被轉移到分配的空閒Region中。
三、CSet包括須要收集的Eden Regions、Survivor Regions,並且還包括部分(1/8)Old Regions(混合收集的時候,收集年輕代的時候,會收集一部分老年代的Region)。

五、SATB(snapshot-at-the-beginning)

爲了解決在併發標記過程當中,存活對象漏標的狀況,GC HandBook把對象分紅三種顏色:

一、黑色:自身以及可達對象都已經被標記
二、灰色:自身被標記,可達對象還未標記
三、白色:還未被標記

因此,漏標的狀況只會發生在白色對象中,且知足如下任意一個條件:

一、併發標記時,應用線程給一個黑色對象的引用類型字段賦值了該白色對象
二、併發標記時,應用線程刪除全部灰色對象到該白色對象的引用

對於第一種狀況,利用post-write barrier,記錄全部新增的引用關係,而後根據這些引用關係爲根從新掃描一遍
對於第二種狀況,利用pre-write barrier,將全部即將被刪除的引用關係的舊引用記錄下來,最後以這些舊引用爲根從新掃描一遍

一、SATB的概念

一、SATB是增量式標記清除垃圾收集器設計的一個標記算法。
二、SATB的標記優化主要針對標記-清除垃圾收集器的併發標記階段。
三、CMS的i設計使得它在remark階段必須從新掃描全部線程棧和整個young gen做爲root。G1的SATB設計在remark階段則只須要掃描剩下的satb_mark_queue。

二、SATB的做用

一、SATB保證了在併發標記過程當中新分配對象不會漏標。
二、SATB中的stab_mark_queue,在引用關係發生變化時,利用pre-write barrier,把原引用保存到stab_mark_queue中,每一個應用線程都有一個stab_mark_queue。

總結

SATB的算法,每一個region有5個指針,比較複雜,沒有找到太好的文章,沒有太理解,還有待進一步瞭解~~~

相關文章
相關標籤/搜索