G1 GC
G1GC基本概念
- G1 GC可以看做是CMS GC的重大升級改造
- G1 GC的全稱是Garbage-First,意為垃圾優先,哪一塊的垃圾最多就優先清理他。
- G1 GC最主要的設計目標是:將STW停頓的時間和分布,變成可預期且可配置的。(默認200ms)
- 垃圾回收過程中,一般垃圾的量在單位時間內都是固定的。
- 那么一下子把所有垃圾都處理完,就會導致了GC暫停的時間可能特別長。
- 如果每次處理一點,就會導致雖然暫停的時間比較短,但是整體垃圾回收的成本就會相對比較高。
- 為了平衡中間這個值,就產生了G1 GC的垃圾回收的算法。
- 設置了預期值之后,G1GC就會在自己執行了多次GC以后,調整自己GC策略:執行的頻率、每次清理的垃圾數量等,慢慢地盡量控制在預期值以內。
- 這樣的話就可以做到讓GC暫停的時間跟整體的GC效率,比如吞吐量和延遲中間找到一個平衡點,這就是G1GC設計的一個出發點。
- 為了做到這件事情,G1GC打破了原先的串行并行CMS里堆內存整體的進行分區的年輕代(Eden、Servivor)、老年代(Old)的分代模式。
- G1 GC 堆不在分成年輕代和老年代,而是劃分為多個(默認是2048個)小塊:Region。
每個小塊,可能一會被定義成Eden區,一會被指定為Survivor區或者Old區。
這樣內存上的管理:
- 啟動參數:-XX:+UserG1GC?? -XX:MaxGCPauseMillis=50
- 這樣劃分以后,使得G1不必每次都區收集整個堆空間,而是以增量的方式來進行處理:每次只處理一部分內存塊,稱為此次GC的回收集(collection
set)。
- 每次GC暫停都會收集所有年輕代的內存塊,但一般只包含部分老年代的內存
塊。
- G1的另一個創新是,在并發階段估算每個小堆塊存活對象的總數。
依據這些垃圾的數量,就可以計算出那些小塊現在垃圾比較多,就可以優先去處理垃圾比較多的。進而提高整個垃圾回收的效率。
- 構建回收集的原則是:垃圾最多的小塊會被優先收集。這也是G1名稱的由
來。
G1 GC--配置參數
- -XX:+UseG1GC:啟用 G1 GC;
- -XX:G1NewSizePercent:初始年輕代占整個 Java Heap 的大小,默認值為 5%;
- 最開始只是用非常少量的這種小的塊來作為整個Young區。
- -XX:G1MaxNewSizePercent:最大年輕代占整個 Java Heap 的大小,默認值為 60%;
- -XX:G1HeapRegionSize:設置每個 Region 的大小,單位 MB,需要為 1、2、4、8、16、32 中的某個值,默認是堆內存的1/2000。如果這個值設置比較大,那么大對象就可以進入 Region 了;
- -XX:ConcGCThreads:與 Java 應用一起執行的 GC 線程數量,默認是 Java 線程的 1/4,減少這個參數的數值可能會提升并行回收的效率,提高系統內部吞吐量。如果這個數值過低,參與回收垃圾的線程不足,也會導致并行回收機制耗時加長;
- -XX:+InitiatingHeapOccupancyPercent(簡稱 IHOP):G1 內部并行回收循環啟動的閾值,默認為 Java Heap的 45%(高水位)。這個可以理解為老年代使用大于等于 45% 的時候,JVM 會啟動垃圾回收。這個值非常重要,它決定了在什么時間啟動老年代的并行回收;
- 為何重要:因為如果老年代只使用了比較少的時候就做回收,這時要回收的整個對象比較少,垃圾也相對比較少,就會執行的特別快。如果這個比例特別大,整個老年代現在使用的量特別大,這時做垃圾回收時間就會特別長。
- -XX:G1HeapWastePercent:G1停止回收的最小內存大小,默認是堆大小的 5%(低水位)。GC 會收集所有的 Region 中的對象,但是如果下降到了 5%,就會停下來不再收集了。就是說,不必每次回收就把所有的垃圾都處理完,可以遺留少量的下次處理,這樣也降低了單次消耗的時間;
- -XX:G1MixedGCCountTarget:設置并行循環之后需要有多少個混合 GC 啟動,默認值是 8 個。老年代 Regions的回收時間通常比年輕代的收集時間要長一些。所以如果混合收集器比較多,可以允許 G1 延長老年代的收集時間。
- -XX:+G1PrintRegionLivenessInfo:這個參數需要和
- -XX:+UnlockDiagnosticVMOptions 配合啟動,打印 JVM 的調試信息,每個Region 里的對象存活信息。
- -XX:G1ReservePercent:G1 為了保留一些空間用于年代之間的提升,默認值是堆空間的 10%。因為大量執行回收的地方在年輕代(存活時間較短),所以如果你的應用里面有比較大的堆內存空間、比較多的大對象存活,這里需要保留一些內存。
- -XX:+G1SummarizeRSetStats:這也是一個 VM 的調試信息。如果啟用,會在 VM 退出的時候打印出 Rsets 的詳細總結信息。如果啟用
- -XX:G1SummaryRSetStatsPeriod 參數,就會階段性地打印 Rsets 信息。
- -XX:+G1TraceConcRefinement:這個也是一個 VM 的調試信息,如果啟用,并行回收階段的日志就會被詳細打印出來。
- -XX:+GCTimeRatio:這個參數就是計算花在 Java 應用線程上和花在 GC 線程上的時間比率,默認是 9,跟新生代內存的分配比例一致。這個參數主要的目的是讓用戶可以控制花在應用上的時間,G1 的計算公式是 100/(1+GCTimeRatio)。這樣如果參數設置為9,則最多 10% 的時間會花在 GC 工作上面。Parallel GC 的默認值是 99,表示 1% 的時間被用在 GC 上面,這是因為 Parallel GC 貫穿整個 GC,而 G1 則根據 Region 來進行劃分,不需要全局性掃描整個內存堆。
- -XX:+UseStringDeduplication:手動開啟 Java String 對象的去重工作,這個是 JDK8u20 版本之后新增的參數,主要用于相同String 避免重復申請內存,節約 Region 的使用。
- -XX:MaxGCPauseMills:預期 G1 每次執行 GC 操作的暫停時間,單位是毫秒,默認值是 200 毫秒,G1 會盡量保證控制在這個范圍內。
G1GC的處理步驟 1
G1GC會通過前面一段時間的運行情況來不斷的調整自己的回收策略和行為,以此來比較穩定地控制暫停時間,在應用程序剛啟動時,G1還沒有采集到什么足夠的信息,這時候就處于初始的fully-young模式,當年輕代空間用滿后,應用線程會被暫停,年輕代內存塊中的存活對象被拷貝到存貨區。如果還沒有存活區,則任意選擇一部分空閑的內存塊作為存活區。
拷貝的過程成為轉移(Evacuation),這和前面介紹的其他年輕代收集器是一樣的工作原理。
同時我們可以看到,G1GC的很多概念建立在CMS的基礎上,所以下面的內容需要對CMS有一定的理解。
G1并發標記的過程與CMS基本上是一樣的。G1的并發標記通過Snapshot-At-The-Beginning(起始快照)的方式,在標記階段開始時記下所有的存活對象。即時在標記的同時又有一些變成了垃圾。通過對象的存活信息,可以構建出每個小堆塊的存活狀態,以便回收收集能高效地進行選擇。
這些信息在接下來的階段會用來執行老年代區域的垃圾收集。
有兩種情況是可以完全并發執行的:
當堆內存的總體使用比例達到一定數值,就會觸發并發標記。這個默認比例是45%,但也可以通過JVM參數InitiatingHeapOccupancyPercent來設置。和CMS一樣,G1的并發標記也是由多個階段組成,其中一些階段是完全并發的,還有一些階段則會暫停應用線程。
G1GC的處理步驟 2
階段1:Initial Mark(初始標記)
此階段標記所有從GC根對象直接可達的對象。
階段2:Root Region Scan(Root區掃描)
此階段標記所有從”根區域“可達的存活對象。根區域包括:非空的區域,以及在標記過程中不得不收集的區域。
階段3:Concurrent Mark(并發標記)
此階段和CMS的并發標記階段非常類似:只遍歷對象圖,并在一個特殊的位圖中標記能訪問到的對象。
階段4:Remark(再次標記)
和CSM類似,這是一次STW停頓(因為不是并發的階段),以完成標記過程。G1收集器會短暫地停止應用線程,停止并發更新信息的寫入,處理其中的少量信息,并標記所有在并發標記開始時未被標記的存活對象。
階段5:Cleanup(清理)
最后這個清理階段為即將到來的轉移階段做準備,統計小堆塊中所有存活的對象,并將小堆塊進行排序,以提升GC的效率,維護并發標記的內部狀態。所有不包括存活對象的小堆塊在此階段都被回收了。有一部分任務是并發的:例如空堆區的回收,還有大部分的存活率計算。此階段也需要一個短暫的STW暫停。
G1GC的處理步驟 3
轉移暫停:混合模式(Evacuation Pause(mixed))
并發標記完成之后,G1將完成一次混合收集(mixed collection),就是不只清理年輕代,還將一部分老年代區域也加入到回收集中。混合模式的轉移暫停不一定緊跟并發標記階段。有很多規則和歷史數據會影響混合模式的啟動時機。比如,假若在老年代中可以并發地騰出很多的小堆塊,就沒有了必要啟動混合模式。
因此,在并發標記與混合轉移暫停之間,很可能會存在多次young模式的轉移暫停。
具體添加到回收集的老年代小堆塊的大小及順序,也是基于許多規則來判定的。其中包括指定的軟實時性能指標,存活性,以及在并發標記期間收集的GC效率等數據,外加一些可配置的JVM選項。混合收集的過程,很大程度上和前面的fully-young gc是一樣的。
G1GC的注意事項
特別需要注意的是,某些情況下G1觸發了Full GC,這時G1會退化使用Serial收集器來完成垃圾的清理工作,它僅僅使用單線程來完成GC工作,GC暫停時間將達到秒級別的。會觸發這種情況的條件如下:
G1啟動標記周期,但在Mix GC之前,老年代就被填滿,這時候G1會放棄標記周期。
解決辦法:增加堆大小,或者調整周期(例如增加線程數-XX:ConcGCThreads等)。
沒有足夠的內存供存活對象或晉級對象使用,由此觸發了Full GC(to-space exhausted/to-space overflow)。
補充:當G1GC做垃圾回收的時候,有很多對象需要從新生代晉升到老年代,晉升的時候發現沒有足夠的內存供這次晉升使用,這時候也會觸發Full GC,然后進行GC退化。
解決辦法:
補充:G1GC內部為了更有效的做GC,其實默認是保留一部分的內存的,用來做對象的復制。
當巨型對象找不到合適的空間進行分配時,就會啟動Full GC,來釋放空間。
解決辦法:增加內存或者增大-XX:G1HeapRegionSize
各個GC對比
GC如何選擇
選擇正確的 GC 算法,唯一可行的方式就是去嘗試,一般性的指導原則:
對于內存大小的考量:
最后討論一個很多開發者經常忽視的問題,也是面試大廠常問的問題:JDK8 的默認 GC 是什么?
JDK9,JDK10,JDK11…等等默認的 GC 是什么?
總結
- 上一篇: D触发器的二分频电路
- 下一篇: xposed伪造收到短信