天天看點

JVM垃圾收集器概述第一章 概述第二章 JVM GC收集器的回顧與比較第三章 G1的記憶體模型

第一章 概述

G1(Garbage First)垃圾收集器是當今垃圾回收技術最前沿的成果之一。早在JDK7就已加入JVM的收集器大家庭中,成為HotSpot重點發展的垃圾回收技術。同優秀的CMS垃圾回收器一樣,G1也是關注最小時延的垃圾回收器,也同樣适合大尺寸堆記憶體的垃圾收集,官方也推薦使用G1來代替選擇CMS。G1最大的特點是引入分區的思路,弱化了分代的概念,合理利用垃圾收集各個周期的資源,解決了其他收集器甚至CMS的衆多缺陷。

第二章 JVM GC收集器的回顧與比較

JVM垃圾收集器概述第一章 概述第二章 JVM GC收集器的回顧與比較第三章 G1的記憶體模型

從JDK3(1.3)開始,HotSpot團隊一直努力朝着高效收集、減少停頓(STW: Stop The World)的方向努力,也貢獻了從串行到CMS乃至最新的G1在内的一系列優秀的垃圾收集器。上圖展示了JDK的垃圾回收大家庭,以及互相之間的組合關系,下面就幾種典型的組合應用進行簡單的介紹。

串行收集器

JVM垃圾收集器概述第一章 概述第二章 JVM GC收集器的回顧與比較第三章 G1的記憶體模型

串行收集器組合 Serial + Serial Old

開啟選項:

-XX:+SerialGC

串行收集器是最基本、發展時間最長、久經考驗的垃圾收集器,也是client模式下的預設收集器配置。

串行收集器采用單線程stop-the-world的方式進行收集。當記憶體不足時,串行GC設定停頓辨別,待所有線程都進入安全點(Safepoint)時,應用線程暫停,串行GC開始工作,采用單線程方式回收空間并整理記憶體。單線程也意味着複雜度更低、占用記憶體更少,但同時也意味着不能有效利用多核優勢。事實上,串行收集器特别适合堆記憶體不高、單核甚至雙核CPU的場合。

并行收集器 

并行收集器組合 Parallel Scavenge + Parallel Old

JVM垃圾收集器概述第一章 概述第二章 JVM GC收集器的回顧與比較第三章 G1的記憶體模型

并行收集器是以關注吞吐量為目标的垃圾收集器,也是server模式下的預設收集器配置,對吞吐量的關注主要展現在年輕代Parallel Scavenge收集器上。

parallel scanvenge 的關鍵點在于它關注的是吞吐量,

吞吐量 = 使用者代碼運作時間 / (使用者代碼運作時間 + 垃圾收集時間)

并行收集器與串行收集器工作模式相似,都是stop-the-world方式,隻是暫停時并行地進行垃圾收集。年輕代采用複制算法,老年代采用标記-整理,在回收的同時還會對記憶體進行壓縮。關注吞吐量主要指年輕代的Parallel Scavenge收集器,通過兩個目标參數-XX:MaxGCPauseMills和-XX:GCTimeRatio,調整新生代空間大小,來降低GC觸發的頻率。并行收集器适合對吞吐量要求遠遠高于延遲要求的場景,并且在滿足最差延時的情況下,并行收集器将提供最佳的吞吐量。

主要是有三個參數:

-XX:MaxGcPauserMillis:收集器盡可能保證記憶體回收花費時間不超過設定值,但也不是越小越好,會犧牲吞吐量和新生代空間

-XX:GCTimeRatio:吞吐量的倒數,比如 如果是 19 那麼允許的最大GC時間比例為 1/(1 + 19) =5 %

-XX:+UseAdaptiveSizePolicy:不需要手動指定新生代大小,虛拟機會自動調整參數(GC自适應)

并發标記清除收集器

JVM垃圾收集器概述第一章 概述第二章 JVM GC收集器的回顧與比較第三章 G1的記憶體模型

并發标記清除收集器組合 ParNew + CMS + Serial Old

開啟選項:

-XX:+UseConcMarkSweepGC

并發标記清除(CMS)是以關注延遲為目标、十分優秀的垃圾回收算法,開啟後,年輕代使用STW式的并行收集,老年代回收采用CMS進行垃圾回收,對延遲的關注也主要展現在老年代CMS上。

年輕代ParNew與并行收集器類似,而老年代CMS每個收集周期都要經曆:初始标記、并發标記、重新标記、并發清除。其中,初始标記以STW的方式标記所有的根對象;并發标記則同應用線程一起并行,标記出根對象的可達路徑;在進行垃圾回收前,CMS再以一個STW進行重新标記,标記那些由mutator線程(指引起資料變化的線程,即應用線程)修改而可能錯過的可達對象;最後得到的不可達對象将在并發清除階段進行回收。值得注意的是,初始标記和重新标記都已優化為多線程執行。CMS非常适合堆記憶體大、CPU核數多的伺服器端應用,也是G1出現之前大型應用的首選收集器。

優勢:并發收集,低停頓,CMS收集分為如下四個步驟

  • 初始标記:标記GCroot直接關聯到的對象
  • 并發标記
  • 重新标記:修正并發标記期間因使用者程式繼續運作導緻标記産生變動的對象的标記記錄
  • 并發清除

缺點:

  • 對CPU資源敏感,預設啟動回收線程數(CPU + 3)/ 4 當CPU數量在4個一下時,如2個,CMS影響變得太大
  • 無法處理浮動垃圾,也就是在并發清除的時候,由于使用者線程還在運作,會産生新的垃圾,是以CMS不能等到老年代滿了再進收集,需要預留白間,可以通過參數配置,提高觸發gc百分比
  • CMS基于标記清除,會有大量碎片,可能出現配置設定大對象時明明還有空間,卻找不到連續空間,提前出發fullGc。但是現在CMS提供了一個參數

    -XX: +UseCMSCompactAtFullCollection開關參數,用于 在CMS收集器頂不住要進行fullgc的時候開啟記憶體碎片的合并整理過程

 Garbage First

JVM垃圾收集器概述第一章 概述第二章 JVM GC收集器的回顧與比較第三章 G1的記憶體模型

之前介紹的幾組垃圾收集器組合,都有幾個共同點:

  1. 年輕代、老年代是獨立且連續的記憶體塊;
  2. 年輕代收集使用單eden、雙survivor進行複制算法;
  3. 老年代收集必須掃描整個老年代區域;
  4. 都是以盡可能少而塊地執行GC為設計原則。

G1垃圾收集器也是以關注延遲為目标、伺服器端應用的垃圾收集器,被HotSpot團隊寄予取代CMS的使命,也是一個非常具有調優潛力的垃圾收集器。雖然G1也有類似CMS的收集動作:初始标記、并發标記、重新标記、清除、轉移回收,并且也以一個串行收集器做擔保機制,但單純地以類似前三種的過程描述顯得并不是很妥當。事實上,G1收集與以上三組收集器有很大不同:

  1. G1的設計原則是”首先收集盡可能多的垃圾(Garbage First)“。是以,G1并不會等記憶體耗盡(串行、并行)或者快耗盡(CMS)的時候開始垃圾收集,而是在内部采用了啟發式算法,在老年代找出具有高收集收益的分區進行收集。同時G1可以根據使用者設定的暫停時間目标自動調整年輕代和總堆大小,暫停目标越短年輕代空間越小、總空間就越大;
  2. G1采用記憶體分區(Region)的思路,将記憶體劃分為一個個相等大小的記憶體分區,回收時則以分區為機關進行回收,存活的對象複制到另一個空閑分區中。由于都是以相等大小的分區為機關進行操作,是以G1天然就是一種壓縮方案(局部壓縮);
  3. G1雖然也是分代收集器,但整個記憶體分區不存在實體上的年輕代與老年代的差別,也不需要完全獨立的survivor(to space)堆做複制準備。G1隻有邏輯上的分代概念,或者說每個分區都可能随G1的運作在不同代之間前後切換;
  4. G1的收集都是STW的,但年輕代和老年代的收集界限比較模糊,采用了混合(mixed)收集的方式。即每次收集既可能隻收集年輕代分區(年輕代收集),也可能在收集年輕代的同時,包含部分老年代分區(混合收集),這樣即使堆記憶體很大時,也可以限制收集範圍,進而降低停頓。

第三章 G1的記憶體模型

分區概念
JVM垃圾收集器概述第一章 概述第二章 JVM GC收集器的回顧與比較第三章 G1的記憶體模型

為解決CMS算法産生空間碎片和其它一系列的問題缺陷,HotSpot提供了另外一種垃圾回收政策,G1(Garbage First)算法,通過參數-XX:+UseG1GC來啟用,該算法在JDK 7u4版本被正式推出,官網對此描述如下:

The Garbage-First (G1) collector is a server-style garbage collector, targeted for multi-processor machines with large memories. It meets garbage collection (GC) pause time goals with a high probability, while achieving high throughput. The G1 garbage collector is fully supported in Oracle JDK 7 update 4 and later releases. The G1 collector is designed for applications that:
  • Can operate concurrently with applications threads like the CMS collector.
  • Compact free space without lengthy GC induced pause times.
  • Need more predictable GC pause durations.
  • Do not want to sacrifice a lot of throughput performance.
  • Do not require a much larger Java heap.

G1垃圾收集算法主要應用在多CPU大記憶體的服務中,在滿足高吞吐量的同時,竟可能的滿足垃圾回收時的暫停時間,該設計主要針對如下應用場景:

  • 垃圾收集線程和應用線程并發執行,和CMS一樣
  • 空閑記憶體壓縮時避免冗長的暫停時間
  • 應用需要更多可預測的GC暫停時間
  • 不希望犧牲太多的吞吐性能
  • 不需要很大的Java堆 (翻譯的有點虛,多大才算大?)

堆記憶體結構

1、以往的垃圾回收算法,如CMS,使用的堆記憶體結構如下:

JVM垃圾收集器概述第一章 概述第二章 JVM GC收集器的回顧與比較第三章 G1的記憶體模型
  • 新生代:eden space + 2個survivor
  • 老年代:old space
  • 持久代:1.8之前的perm space
  • 元空間:1.8之後的metaspace

這些space必須是位址連續的空間。

2、在G1算法中,采用了另外一種完全不同的方式組織堆記憶體,堆記憶體被劃分為多個大小相等的記憶體塊(Region),每個Region是邏輯連續的一段記憶體,結構如下:

JVM垃圾收集器概述第一章 概述第二章 JVM GC收集器的回顧與比較第三章 G1的記憶體模型

每個Region被标記了E、S、O和H,說明每個Region在運作時都充當了一種角色,其中H是以往算法中沒有的,它代表Humongous,這表示這些Region存儲的是巨型對象(humongous object,H-obj),當建立對象大小超過Region大小一半時,直接在新的一個或多個連續Region中配置設定,并标記為H。

Region

堆記憶體中一個Region的大小可以通過-XX:G1HeapRegionSize參數指定,大小區間隻能是1M、2M、4M、8M、16M和32M,總之是2的幂次方,如果G1HeapRegionSize為預設值,則在堆初始化時計算Region的實踐大小,具體實作如下:

JVM垃圾收集器概述第一章 概述第二章 JVM GC收集器的回顧與比較第三章 G1的記憶體模型

預設把堆記憶體按照2048份均分,最後得到一個合理的大小。

GC模式

G1中提供了三種模式垃圾回收模式,young gc、mixed gc 和 full gc,在不同的條件下被觸發。

young gc

發生在年輕代的GC算法,一般對象(除了巨型對象)都是在eden region中配置設定記憶體,當所有eden region被耗盡無法申請記憶體時,就會觸發一次young gc,這種觸發機制和之前的young gc差不多,執行完一次young gc,活躍對象會被拷貝到survivor region或者晉升到old region中,空閑的region會被放入空閑清單中,等待下次被使用。

參數 含義
-XX:MaxGCPauseMillis 設定G1收集過程目标時間,預設值200ms
-XX:G1NewSizePercent 新生代最小值,預設值5%
-XX:G1MaxNewSizePercent 新生代最大值,預設值60%

mixed gc

當越來越多的對象晉升到老年代old region時,為了避免堆記憶體被耗盡,虛拟機會觸發一個混合的垃圾收集器,即mixed gc,該算法并不是一個old gc,除了回收整個young region,還會回收一部分的old region,這裡需要注意:是一部分老年代,而不是全部老年代,可以選擇哪些old region進行收集,進而可以對垃圾回收的耗時時間進行控制。

那麼mixed gc什麼時候被觸發?

先回顧一下cms的觸發機制,如果添加了以下參數:

-XX:CMSInitiatingOccupancyFraction=80

-XX:+UseCMSInitiatingOccupancyOnly

當老年代的使用率達到80%時,就會觸發一次cms gc。相對的,mixed gc中也有一個門檻值參數 -XX:InitiatingHeapOccupancyPercent,當老年代大小占整個堆大小百分比達到該門檻值時,會觸發一次mixed gc.

mixed gc的執行過程有點類似cms,主要分為以下幾個步驟:

  1. initial mark: 初始标記過程,整個過程STW,标記了從GC Root可達的對象
  2. concurrent marking: 并發标記過程,整個過程gc collector線程與應用線程可以并行執行,标記出GC Root可達對象衍生出去的存活對象,并收集各個Region的存活對象資訊
  3. remark: 最終标記過程,整個過程STW,标記出那些在并發标記過程中遺漏的,或者内部引用發生變化的對象
  4. clean up: 垃圾清除過程,如果發現一個Region中沒有存活對象,則把該Region加入到空閑清單中

full gc

如果對象記憶體配置設定速度過快,mixed gc來不及回收,導緻老年代被填滿,就會觸發一次full gc,G1的full gc算法就是單線程執行的serial old gc,會導緻異常長時間的暫停時間,需要進行不斷的調優,盡可能的避免full gc.