3.JVM系列-G1垃圾收集器

目錄

一、背景

二、G1垃圾收集器特性

三、G1執行步驟 ?

四、G1基本參數

四、G1日志解釋

六、基本原理

七、G1優化

八、元空間擴容引起MIXGC實踐

一、背景

G1垃圾收集器也是以關注延遲為目標、服務器端應用的垃圾收集器,被HotSpot團隊寄予取代CMS。本文都是以JDK1.8說明。

二、G1垃圾收集器特性

G1特性說明

特點同CMS獲得最低的停頓為目的

利用分而治之的思想來獲得可預測的停頓

使用場景1.服務端多核CPU、JVM內存>=6G。

2.應用在運行過程中會產生大量內存碎片、需要經常壓縮空間。

3.想要更可控、可預期的GC停頓周期;防止高并發下應用雪崩現象。

4.降低停頓時間

5.FULL GC太頻繁

基于算法整體使用標記-整理

局部使用復制

優點1.不會產生內存碎片,有利于程序長時間運行

2.切分堆內存為多個Region每次回收垃圾最多的部分Region,從而降低停頓.

3.適用于大內存,即使很大也僅是對 Region 進行掃描,性能還是很高的。

4.可設置最大停頓時間。

5.每塊區域既有可能屬于O區、也有可能是Y區,每類區域空間可以是不連續的。

6.Eden, Survivor, Old區不再固定、在內存使用效率上來說更靈活。

7.G1在回收內存后會馬上同時做合并空閑內存的工作、而CMS默認是在STW的時候做。

8.G1會在Young GC中使用、而CMS只能在O區使用。

基本原理把整個 堆劃分為若干Region 。每個 Region 大小為2的倍數,范圍在 1MB-32MB 之間,所有的 Region 相同在JVM 生命周期內不會被改變。最多2048個region。

第一時間處理垃圾最多的區塊。

GC模式Young GC

Mixed GC

Young GC1.年輕代收集進行時,整個年輕代會被回收,所有的應用程序線程會被中斷,采用復制算法。

Mixed GC之所以叫混合是因為回收所有的年輕代的Region+部分老年代的Region

大對象(超過region的50%)G1會挑選出1組物理連續的可用 Region, 相加后只要能夠確保總大小可以存放這個大對象,就會分配給這個大對象。反之如果沒有能夠找到符合條件的連續可用Region ,會執行1次Full GC壓縮堆。

G1 Full GCFull GC會對整個Java堆進行壓縮 G1 Full GC 是單線程的(轉換為serial old),會引起較長的停頓時間。

Young GC觸發條件當E區不能再分配新的對象

Mixed GC觸發條件

1.整個堆占用率大于InitiatingHeapOccupancyPercent

2.元空間超過MetaspaceSize發生擴容

3.調用System.gc且開啟ExplicitGCInvokesConcurrent

Full GC觸發條件(轉換為serial old收集器)

1.沒有連續的可用region存放大對象

2.調用System.gc且未開啟ExplicitGCInvokesConcurrent

3.沒有足夠的內存供存活對象或晉升對象使用(報錯Allocation Failure)

4.元空間達到MaxMetaspaceSize之后

三、G1執行步驟

1.Mixed GC執行步驟

步驟名稱簡述是否STW

initial-mark初始標記標記了從GC Root開始直接可達的對象,Mixed GC開始會觸發一次完整的ygc,在ygc過程中完整初始標記是

concurrent-root-region-scan根區域掃描這個階段從GC Root開始對heap中的對象標記,標記線程與應用程序線程并行執行,并且收集各個Region的存活對象信息,

concurrent-mark并發標記由前階段標記過的對象出發,所有可到達的對象都在本階段中標記

這個階段可以被年輕代的垃圾收集打斷

remark重新標記完成堆內存活對象的標記。使用了一個叫開始前快照snapshot-at-the-beginning (SATB)的算法,這個會比CMS collector使用的算法快。是

Unloading卸載類卸載class是

cleanup清理清除空Region

對存活的對象和完全空的區域進行統計

刷新RSets

concurrent-cleanup重置空的區域,把他們放到free列表否

四、G1基本參數

參數意義

-XX:+UseG1GC啟動G1

-XX:G1HeapRegionSize自定義region大小。

-XX:G1HeapRegionSize=2M

-XX:G1HeapWastePercent在global concurrent marking結束之后,我們可以知道old gen regions中有多少空間要被回收,在每次YGC之后和再次發生Mixed GC之前,會檢查垃圾占比是否達到此參數,只有達到了,下次才會發生Mixed GC

-XX:G1MixedGCCountTarget一次global concurrent marking之后,最多執行Mixed GC的次數

-XX:MaxGCPauseMillis設置G1收集過程目標時間,默認值200ms,不是硬性條件

-XX:ConcGCThreads=n并發標記階段,并行執行的線程數

-XX:InitiatingHeapOccupancyPercent設置觸發標記周期的 Java 堆占用率閾值。默認值是45%。這里的java堆占比指的是non_young_capacity_bytes,包括old+humongous

ResizeTLAB即線程本地分配緩存區線程私有的。

JVM使用TLAB來避免多線程沖突,在給對象分配內存時,每個線程使用自己的TLAB,這樣可以避免線程同步,提高了對象分配的效率。

ResizePLABGC 使用的本地線程分配緩存塊采用動態值還是靜態值進行設置是由這個選項決定的,

GCTimeRatio這個選項代表 Java 應用線程花費的時間與 GC 線程花費時間的比率。通過這個比率值可以 調節 Java 應用錢程或者 GC 線程的工作時間,保障兩者的執行時間

G1ReservePercent預留多少內存,防止晉升失敗的情況

四、G1日志解釋

1.參數:

-Xmx2000m -Xms2000m -XX:+UseG1GC -XX:+PrintGC -XX:+PrintGCCause -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC ?-Xloggc:/tmp/g1test.log

2.

3.樣例程序

4.日志

5.完整的一次YGC日志

{Heap before GC invocations=1 (full 0):

?invocations=1第一次發生調用,full 0代表:目前發生full gc(也就是使用serial old gc,整個過程是STW的)次數是0

garbage-first heap ? total 2048000K, used 116463K [0x0000000743000000, 0x0000000743103e80, 0x00000007c0000000)

使用G1收集器,total 2048000K:整個堆2048000K,used 116463K,包括新生代老年代總共使用 116463K。

region size 1024K, 100 young (102400K), 13 survivors (13312K)

region為1024K,100 young (102400K):整個年輕代有100個region總共占用了102400K,13 survivors (13312K):其中13 個survivors (占用了13312K)

Metaspace ? ? ? used 3359K, capacity 4500K, committed 4864K, reserved 1056768K

Metaspace:存放類的元數據,如Klass的method 屬性等。used 3359K,使用了3359K;總容量capacity 4500K, committed 4864K:JVM向操做系統實際分配的內存4864K,當 committed不會超過MaxMetaspaceSize的值,reserved:JVM向OS申請的虛擬地址空間,保證了其他進程不會被占用。

class space ? ?used 363K, capacity 388K, committed 512K, reserved 1048576K

類的信息存放于此。

2018-11-06T23:02:18.786-0800: 6.107: [GC pause (G1 Evacuation Pause) (young), 0.0849028 secs]

2018-11-06T23:02:18.786-0800:GC發生的時間(通過設置-XX:+PrintGCDateStamps打印),6.107:相對JVM啟動的時間, [GC pause (G1 Evacuation Pause) (young), 0.0849028 secs]:GC類型,表示這是evacuation停頓,并且是Young GC,耗時0.0849028 secs。

[Parallel Time: 84.0 ms, GC Workers: 8]

? ?并行任務花費的STW的時間,從收集開始到最后一個GC線程結束。 GC Workers: 8:并行收集的線程數量。通過 -XX:ParallelGCThreads。當CPU數量小于8時,該值為CPU個數,最大設置成8,對于多于8個的CPU,將默認取CPU個數的5/8。

[GC Worker Start (ms): Min: 6107.4, Avg: 6107.5, Max: 6107.8, Diff: 0.4]

?收集線程啟動的時間,使用的是相對JVM啟動的時間,Min是最早開始時間,Avg是平均開始時間,Max是最晚開始時間,Diff是Max-Min

[Ext Root Scanning (ms): Min: 0.0, Avg: 0.4, Max: 1.8, Diff: 1.8, Sum: 3.5]

? ? ? ?掃描Roots花費的時間,Sum表示total cpu time

[Update RS (ms): Min: 4.1, Avg: 7.3, Max: 10.0, Diff: 5.9, Sum: 58.6]

? ? ?? 線程花費在更新Remembered Set上的時間

[Processed Buffers: Min: 1, Avg: 1.4, Max: 2, Diff: 1, Sum: 11]

?處理緩存的時間

[Scan RS (ms): Min: 12.2, Avg: 15.1, Max: 18.2, Diff: 6.0, Sum: 120.5]

? ? ?掃描每個CSet中Region的RSet,避免了掃描整個老年代

[Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.2, Diff: 0.2, Sum: 0.2]

? ? ?掃描code root耗時。Code Root是JIT編譯后的代碼里引用了heap中的對象,引用關系保存在RSet中.

[Object Copy (ms): Min: 59.8, Avg: 60.8, Max: 61.2, Diff: 1.3, Sum: 486.7]

? ?? ?拷貝活的對象到新region的耗時.

[Termination (ms): Min: 0.0, Avg: 0.1, Max: 0.1, Diff: 0.1, Sum: 0.9]

[Termination Attempts: Min: 1, Avg: 1.9, Max: 4, Diff: 3, Sum: 15]

? ? ? ? 當GC線程完成任務之后嘗試結束到真正結束耗時。因為在結束前他會檢查其他線程是否有未完成的任務,幫助完成之后再結束。

[GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.2]

? ? ?線程花費在其他工作上的時間

[GC Worker Total (ms): Min: 83.5, Avg: 83.8, Max: 83.9, Diff: 0.4, Sum: 670.7]

每個線程花費的時間總和。

[GC Worker End (ms): Min: 6191.3, Avg: 6191.3, Max: 6191.3, Diff: 0.0]

每個線程的結束時間。

[Code Root Fixup: 0.0 ms]

? 用來將code root修正到正確的evacuate之后的對象位置所花費的時間。

[Code Root Purge: 0.0 ms]

?清除code root的耗時,code root中的引用已經失效,不再指向Region中的對象,所以需要被清除。

[Clear CT: 0.2 ms]

?清除card tables 中的dirty card的耗時

[Other: 0.7 ms]

[Choose CSet: 0.0 ms]

[Ref Proc: 0.2 ms]

[Ref Enq: 0.0 ms]

[Redirty Cards: 0.2 ms]

[Humongous Register: 0.0 ms]

[Humongous Reclaim: 0.0 ms]

[Free CSet: 0.1 ms]

? ? 其他事項共耗時0.7ms,其他事項包括選擇CSet,處理已用對象,引用入ReferenceQueues,釋放CSet中的region。

[Eden: 87.0M(87.0M)->0.0B(87.0M) Survivors: 13.0M->13.0M Heap: 113.7M(2000.0M)->41.5M(2000.0M)]

? ?Eden從占87M(總87M)清理后占0M(總87M) ?Survivors從13清理后13M 整個Heap占113.7M(總2000.0M)-清理后占41.5M(總2000.0M)]

Heap after GC invocations=2 (full 0):

GC后的情況

garbage-first heap ? total 2048000K, used 42492K [0x0000000743000000, 0x0000000743103e80, 0x00000007c0000000)

region size 1024K, 13 young (13312K), 13 survivors (13312K)

Metaspace ? ? ? used 3359K, capacity 4500K, committed 4864K, reserved 1056768K

class space ? ?used 363K, capacity 388K, committed 512K, reserved 1048576K

}

[Times: user=0.52 sys=0.01, real=0.09 secs]

整個YGC耗時:0.09 secs,STW=0.09 secs。

6.下面是完整一次MIXGC的日志

mixgc之所以叫mix是因為會發生一次YGC中就開始發生一次老年代的回收,老年代的回收也是從一次YGC開始的。第一個階段initial-mark在YGC中完整的,約多出平時GC20%的時間。

{Heap before GC invocations=22 (full 0):

garbage-first heap ? total 2048000K, used 1320223K [0x0000000743000000, 0x0000000743103e80, 0x00000007c0000000)

region size 1024K, 371 young (379904K), 43 survivors (44032K)

Metaspace ? ? ? used 3362K, capacity 4500K, committed 4864K, reserved 1056768K

class space ? ?used 364K, capacity 388K, committed 512K, reserved 1048576K

2018-11-08T14:46:14.264-0800: 135.707: [GC pause (G1 Evacuation Pause) (young) (initial-mark), 0.1533342 secs]

解釋:initial-mark階段利用STW停頓期間,跟蹤所有可達對象,該階段和Young GC一起執行

[Parallel Time: 151.8 ms, GC Workers: 8]

[GC Worker Start (ms): Min: 135707.5, Avg: 135707.6, Max: 135707.7, Diff: 0.2]

[Ext Root Scanning (ms): Min: 0.2, Avg: 0.3, Max: 0.4, Diff: 0.2, Sum: 2.2]

[Update RS (ms): Min: 14.9, Avg: 16.5, Max: 18.7, Diff: 3.8, Sum: 132.2]

[Processed Buffers: Min: 4, Avg: 5.1, Max: 6, Diff: 2, Sum: 41]

[Scan RS (ms): Min: 5.9, Avg: 8.2, Max: 9.9, Diff: 3.9, Sum: 65.9]

[Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1]

[Object Copy (ms): Min: 126.3, Avg: 126.4, Max: 126.5, Diff: 0.2, Sum: 1010.9]

[Termination (ms): Min: 0.0, Avg: 0.1, Max: 0.1, Diff: 0.1, Sum: 0.9]

[Termination Attempts: Min: 1, Avg: 87.5, Max: 127, Diff: 126, Sum: 700]

[GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.1, Diff: 0.0, Sum: 0.3]

[GC Worker Total (ms): Min: 151.5, Avg: 151.6, Max: 151.6, Diff: 0.2, Sum: 1212.4]

[GC Worker End (ms): Min: 135859.1, Avg: 135859.1, Max: 135859.2, Diff: 0.0]

[Code Root Fixup: 0.0 ms]

[Code Root Purge: 0.0 ms]

[Clear CT: 0.2 ms]

[Other: 1.3 ms]

[Choose CSet: 0.0 ms]

[Ref Proc: 0.3 ms]

[Ref Enq: 0.0 ms]

[Redirty Cards: 0.3 ms]

[Humongous Register: 0.1 ms]

[Humongous Reclaim: 0.0 ms]

[Free CSet: 0.2 ms]

[Eden: 328.0M(328.0M)->0.0B(382.0M) Survivors: 43.0M->42.0M Heap: 1289.3M(2000.0M)->1004.3M(2000.0M)]

Heap after GC invocations=23 (full 0):

garbage-first heap ? total 2048000K, used 1028383K [0x0000000743000000, 0x0000000743103e80, 0x00000007c0000000)

region size 1024K, 42 young (43008K), 42 survivors (43008K)

Metaspace ? ? ? used 3362K, capacity 4500K, committed 4864K, reserved 1056768K

class space ? ?used 364K, capacity 388K, committed 512K, reserved 1048576K

}

[Times: user=0.83 sys=0.08, real=0.15 secs]

上面是一次完整的YGC

2018-11-08T14:46:14.417-0800: 135.861: [GC concurrent-root-region-scan-start]

掃描初始化標記階段Survivor區的root Region并標記出來

2018-11-08T14:46:14.482-0800: 135.925: [GC concurrent-root-region-scan-end, 0.0641499 secs]

2018-11-08T14:46:14.482-0800: 135.925: [GC concurrent-mark-start]

該階段和應用線程一起執行,并發線程數默認是并行線程數的四分之一,可以通過-XX:ConcGCThreads顯示指定。

2018-11-08T14:46:16.525-0800: 137.968: [GC concurrent-mark-end, 2.0435672 secs]

2018-11-08T14:46:16.525-0800: 137.968: [GC remark 2018-11-08T14:46:16.526-0800: 137.969: [Finalize Marking, 0.0002919 secs] 2018-11-08T14:46:16.526-0800: 137.969: [GC ref-proc, 0.0000682 secs] 2018-11-08T14:46:16.526-0800: 137.969: [Unloading, 0.0093801 secs], 0.0134405 secs]

[Times: user=0.02 sys=0.03, real=0.01 secs]

STW時間是=0.01 secs

2018-11-08T14:46:16.539-0800: 137.982: [GC cleanup 1083M->847M(2000M), 0.0026974 secs]

[Times: user=0.01 sys=0.01, real=0.01 secs]

STW時間是=0.01 secs

2018-11-08T14:46:16.542-0800: 137.985: [GC concurrent-cleanup-start]

2018-11-08T14:46:16.542-0800: 137.985: [GC concurrent-cleanup-end, 0.0001850 secs]

總的STW時間是=0.02 secs

六、基本原理

1.堆邏輯圖

也分成三組(Eden,Survivor,Old)但是這三種區域沒有固定的大小。

2.概念介紹:

為了避免掃整個堆,有點類似數據庫全表掃描的實現,采用索引的方式去優化全表掃描,而G1則是通過Rset+card table的方式實現。

CSet:Collection Set,它記錄了GC要收集的Region集合。

RSet:全稱Remembered Sets, 每個Region維護一個RSet,用來記錄外部指向本Region的所有引用,也就是誰引用了我的對象。是一個hashtable,key是對方region的起始地址,value是card table的index。

Card Table: 記錄我引用了誰的對象。一個card table覆蓋512字節的內存。當該card被引用的時候就會被標記為dirty_card。

RSet與Card Table關系如下圖

這樣每次只需掃描Rset,會將存貨的對象復制到其他region,如果發現RSet為空,則會加入到CSet中在清除階段會被清除。

SATB:并發標記階段采用三色標記算法:對象標記了先記為灰色,字段標記完記為黑色。未被標記的是白色,但是由于是并發的可能會產生漏標。一個對象的引用被替換時,可以通過write barrier 將舊引用記錄下來。在STW的重新標記階段重新標記。

七、G1優化

1.選擇G1收集器不要設置年輕代大小

顯式的使用-Xmn設置年輕代的大小,會干預G1的默認行為。

G1就不會再考慮設定的暫停時間目標,所以本質上說,設定了年輕代大小就相當于禁用了目標暫停時間

G1就無法根據需要增大或者縮小年輕代的小心。既然大小固定了,就無法在大小上做任何改變了。

2.報錯concurrent-mark-reset-for-overflow

這表明全局標記堆棧已滿,棧溢出。并發標記檢測到此溢出,必須重新設置數據結構以再次開始標記,這些過程代價都是很高的。需要嘗試增大堆空間。

GC還必須繼續對內存進行釋放。

拷貝不成功的對象繼續留在原來的區域。

對CSets中對區域的RSets任何更新都要重新生成。

3.元空間的設置

元空間大小由MetaspaceSize和MaxMetaspaceSize控制,JVM啟動元空間大小并不是MetaspaceSize,

而當占用超過MetaspaceSize時會觸發mixGC,。

所以建議MetaspaceSize設置的同MaxMetaspaceSize,防止頻繁擴容引起GC。

可以看出committed最大=MaxMetaspaceSize

八、元空間擴容引起MIXGC實踐

參數:-XX:MetaspaceSize=1m -XX:MaxMetaspaceSize=1000m

日志:可以看出因為元數據導致發生mixGC。

3.下圖可以看出擴容后元數據占用4500,并不是max,以后每次擴容都會發生一次mixGC。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容