JVM(五)G1垃圾收集器詳解


一、G1垃圾收集器簡介

  為什么單獨寫一篇文章來記錄G1垃圾收集器的學習過程呢?因為上一篇文章主要都是針對8G內存以下的服務器來進行總結的,G1的特點主要是針對大內存的機器,講道理一般的公司也基本上用不到那么大的內存,所以這篇文章先單獨記錄一下吧~

  簡介:G1 (Garbage-First)是一款面向服務器的垃圾收集器,主要針對配備多核處理器及大容量內存的機器

  特點:STW停頓時間敏感,提升用戶體驗,高吞吐量,CPU利用率高。

  從上圖看,G1將Java堆划分為多個大小相等的獨立區域(Region),每一個小方格代表一個Region,JVM最多可以有2048個Region。

  一般Region大小等於堆大小除以2048,比如堆大小為4096M,則Region大小為2M,當然也可以用參數-XX:G1HeapRegionSize手動指定Region大小,但是推薦默認的計算方式

  G1保留了年輕代和老年代的概念,但不再是物理隔閡了,它們都是(可以不連續)Region的集合

  一個Region可能之前是年輕代,如果Region進行了垃圾回收,之后可能又會變成老年代,也就是說Region的區域功能可能會動態變化

  默認年輕代對堆內存的占比是5%,在系統運行中,JVM會不停的給年輕代增加更多的Region,但是最多新生代的占比不會超過60%。

  PS:年輕代中的Eden和Survivor對應的region也跟之前一樣,默認8:1:1

Humongous區

  G1垃圾收集器對於對象什么時候會轉移到老年代跟之前講過的原則一樣,唯一不同的是對大對象的處理,G1有專門分配大對象的Region叫Humongous區,而不是讓大對象直接進入老年代的Region中。在G1中,大對象的判定規則就是一個大對象超過了一個Region大小的50%,比如每個Region是2M,只要一個對象超過了1M,就會被放入Humongous中,而且一個大對象如果太大,可能會橫跨多個Region來存放。

  作用:Humongous區專門存放短期巨型對象,不用直接進老年代,可以節約老年代的空間,避免因為老年代空間不夠的GC開銷

  PS:Full GC的時候除了收集年輕代和老年代之外,也會將Humongous區一並回收。

二、G1垃圾收集器GC步驟

初始標記【STW】

  暫停所有的其他線程(STW),並記錄下gc roots直接能引用的對象,速度很快。(同CMS)

並發標記

  並發標記階段就是從GC Roots的直接關聯對象開始遍歷整個對象集合的過程, 這個過程耗時較長但是不需要停頓用戶線程, 可以與垃圾收集線程一起並發運行。(同CMS)

重新標記/最終標記【STW】

  重新標記階段就是為了修正並發標記期間因為用戶程序繼續運行而導致標記產生變動的那一部分對象的標記記錄,這個階段的停頓時間一般會比初始標記階段的時間稍長,遠遠比並發標記階段時間短。(同CMS)

篩選回收【STW】

  定義:篩選回收階段首先對各個Region的回收價值和成本進行排序,根據用戶所期望的GC停頓時間(可以用JVM參數 -XX:MaxGCPauseMillis指定)來制定回收計划

  舉個🌰:比如說老年代此時有1000個Region都滿了,但是因為根據預期停頓時間,本次垃圾回收可能只能停頓200毫秒,通過回收成本計算得知,可能回收其中800個Region剛好需要200ms,那么就只會回收800個Region,盡量把GC導致的停頓時間控制在我們指定的范圍內。

  這個階段其實也可以做到與用戶程序一起並發執行,但是因為只回收一部分Region,時間是用戶可控制的,而且停頓用戶線程將大幅提高收集效率。

  回收算法:不管是年輕代或是老年代,回收算法主要用的是復制算法,將一個region中的存活對象復制到另一個region中。

  PS:這種不會像CMS那樣回收完因為有很多內存碎片還需要整理一次,G1采用復制算法回收幾乎不會有太多內存碎片。

  PS:CMS回收階段是跟用戶線程一起並發執行的,G1因為內部實現太復雜暫時沒實現並發回收,不過到了Shenandoah就實現了並發收集,Shenandoah可以看成是G1的升級版本

  篩選回收如何實現?:G1收集器在后台維護了一個優先列表,每次根據允許的收集時間,優先選擇回收價值最大的Region。比如一個Region花200ms能回收10M垃圾,另外一個Region花50ms能回收20M垃圾,在回收時間有限情況下,G1當然會優先選擇后面這個Region回收。這種使用Region划分內存空間以及有優先級的區域回收方式,保證了G1收集器在有限時間內可以盡可能高的收集效率。

並發重置

  重置本次GC過程中的標記數據

三、G1垃圾收集器的特點

並行與並發

  G1能充分利用CPU、多核環境下的硬件優勢,使用多個CPU(CPU或者CPU核心)來縮短STW停頓時間。部分其他收集器原本需要停頓Java線程來執行GC動作,G1收集器仍然可以通過並發的方式讓java程序繼續執行。

分代收集

  雖然G1可以不需要其他收集器配合就能獨立管理整個GC堆,但是還是保留了分代的概念。

空間整合

  與CMS的標記-清理算法不同,G1從整體來看是基於標記-整理算法實現的收集器,從局部上來看是基於標記-復制算法實現的。

可預測的停頓

  這是G1相對於CMS的另一個大優勢,降低停頓時間是G1和CMS共同的關注點,但G1除了追求低停頓外,還能建立可預測的停頓時間模型【后台維護的優先列表】,能讓使用者明確指定在一個長度為M毫秒的時間片段(通過參數-XX:MaxGCPauseMillis指定)內完成垃圾收集。

小結

  毫無疑問, 可以由用戶指定期望的停頓時間是G1收集器很強大的一個功能, 設置不同的期望停頓時間, 可使得G1在不同應用場景中取得關注吞吐量和關注延遲之間的最佳平衡。 默認的停頓目標為兩百毫秒,一般不需要調整,即使調整也應盡可能使其合理,不能太短,如果我們把停頓時間調得非常低,譬如設置為二十毫秒, 很可能每次只能回收很小的一部分內存, 收集器收集的速度逐漸跟不上分配器分配的速度, 導致垃圾慢慢堆積。 很可能一開始收集器還能從空閑的堆內存中獲得一些喘息的時間, 但應用運行時間一長就不行了,最終占滿堆引發Full GC反而降低性能, 所以通常把期望停頓時間設置為一兩百毫秒或者兩三百毫秒會是比較合理的。

四、G1垃圾收集分類

YoungGC

  YoungGC並不是說現有的Eden區放滿了就會馬上觸發,G1會計算下現在Eden區回收大概要多久時間,如果回收時間遠遠小於參數 -XX:MaxGCPauseMills 設定的值,那么增加年輕代的region,繼續給新對象存放,不會馬上做Young GC,直到下一次Eden區放滿,G1計算回收時間接近參數 -XX:MaxGCPauseMills 設定的值,那么就會觸發Young GC。

  PS:所以G1垃圾收集器剛開始年輕代只占堆內存百分之5,會隨着每次計算回收時間而增加,最多不超過百分之60。

MixedGC【混合收集】

  不是FullGC,老年代的堆占有率達到參數-XX:InitiatingHeapOccupancyPercent設定的值則觸發,回收所有的年輕代和部分老年代(根據篩選回收階段計算優先級后排序)以及大對象區,正常情況G1的垃圾收集是先做MixedGC,主要使用復制算法,需要把各個region中存活的對象拷貝到別的region里去,拷貝過程中如果發現沒有足夠的空region能夠承載拷貝對象就會觸發一次Full GC。

Full GC

  停止系統程序,然后采用單線程進行標記、清理、壓縮整理以空閑出來一批Region來供下一次MixedGC使用,這個過程是非常耗時的。(Shenandoah優化成多線程收集了)

五、什么場景適合使用G1

50%以上的堆被存活對象占用

  使用G1,就不用特意預留出很大的老年代空間,G1會根據對象存活狀態,動態分配每種不同代對象需要占用的空間。

對象分配和晉升的速度變化非常大

  前提還是大內存機器才使用G1,大內存的主機如果對象分配和晉升的速度變化非常快的話,G1的這種內存設計可以很快的划分出對應所需的區域【區域占比動態增長,不像CMS等垃圾收集器要划分固定的空間來區分年輕代和老年代】,但因為G1算法比較復雜,在小內存機器里面性能不如CMS等主流垃圾收集器。

垃圾回收時間特別長,超過1秒

  G1有一大好處就是可以設置我們每次想要回收的停頓時間【-XX:MaxGCPauseMillis】,可以有效提升用戶體驗。

8GB以上的堆內存(建議值)

  G1適合8G以上內存的機器使用【結構設計,2048個Region,內存太小的話每個Region也很小,很容易就超過Region的一半被識別為超大對象,這樣Humongous區東西會很多,反而不能很好的進行GC收集】

停頓時間是500ms以內

  和第三點類似,G1分段收集【每次不一定全部回收完所有的垃圾對象】,並且可以手動設置我們每次GC想要STW的時間。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM