JAVA虛擬機之四:G1垃圾收集器

一、關於G1

G1 (Garbage-First)是一款面向服務器的垃圾收集器,主要針對配備多顆處理器及大容量內存的機器. 以極高概率滿足GC停頓時間要求的同時,還具備高吞吐量性能特徵. 在Oracle JDK 7 update 4 及以上版本中得到完全支持, 專爲以下應用程序設計:

 

  • 可以像CMS收集器一樣,GC操作與應用的線程一起併發執行 
  • 緊湊的空閒內存區間且沒有很長的GC停頓時間. 
  • 需要可預測的GC暫停耗時. 
  • 不想犧牲太多吞吐量性能. 
  • 啓動後不需要請求更大的Java堆. 

 

G1的長期目標是取代CMS(Concurrent Mark-Sweep Collector, 併發標記-清除). 因爲特性的不同使G1成爲比CMS更好的解決方案. 一個區別是,G1是一款壓縮型的收集器.G1通過有效的壓縮完全避免了對細微空閒內存空間的分配,不用依賴於regions,這不僅大大簡化了收集器,而且還消除了潛在的內存碎片問題。除壓縮以外,G1的垃圾收集停頓也比CMS容易估計,也允許用戶自定義所希望的停頓參數(pause targets)。

 

 

在G1中,堆被劃分成 許多個連續的區域(region)。每個區域大小相等,在1M~32M之間。JVM最多支持2000個區域,可推算G1能支持的最大內存爲2000*32M=62.5G。區域(region)的大小在JVM初始化的時候決定,也可以用-XX:G1HeapReginSize設置。在G1中沒有物理上的Yong(Eden/Survivor)/Old Generation,它們是邏輯的,使用一些非連續的區域(Region)組成的。這在內存使用上提供了更多的靈活性。 

 

G1執行垃圾回收的處理方式與CMS相似. G1在全局標記階段(global marking phase)併發執行, 以確定堆內存中哪些對象是存活的。標記階段完成後,G1就可以知道哪些heap區的empty空間最大。它會首先回收這些區,通常會得到大量的自由空間. 這也是爲什麼這種垃圾收集方法叫做Garbage-First(垃圾優先)的原因。顧名思義, G1將精力集中放在可能佈滿可收回對象的區域, 可回收對象(reclaimable objects)也就是所謂的垃圾. G1使用暫停預測模型(pause prediction model)來達到用戶定義的目標暫停時間,並根據目標暫停時間來選擇此次進行垃圾回收的heap區域數量.

被G1標記爲適合回收的heap區將使用轉移(evacuation)的方式進行垃圾回收. G1將一個或多個heap區域中的對象拷貝到其他的單個區域中,並在此過程中壓縮和釋放內存. 在多核CPU上轉移是並行執行的(parallel on multi-processors), 這樣能減少停頓時間並增加吞吐量. 因此,每次垃圾收集時, G1都會持續不斷地減少碎片, 並且在用戶給定的暫停時間內執行. 這比以前的方法強大了很多. CMS垃圾收集器(Concurrent Mark Sweep,併發標記清理)不進行壓縮. ParallelOld 垃圾收集只對整個堆執行壓縮,從而導致相當長的暫停時間。

需要強調的是, G1並不是一款實時垃圾收集器(real-time collector). 能以極高的概率在設定的目標暫停時間內完成,但不保證絕對在這個時間內完成。 基於以前收集的各種監控數據, G1會根據用戶指定的目標時間來預估能回收多少個heap區. 因此,收集器有一個相當精確的heap區耗時計算模型,並根據該模型來確定在給定時間內去回收哪些heap區.

注意 G1分爲兩個階段: 併發階段(concurrent, 與應用線程一起運行, 如: 細化 refinement、標記 marking、清理 cleanup) 和 並行階段(parallel, 多線程執行, 如: 停止所有JVM線程, stop the world). 而 FullGC(完整垃圾收集)仍然是單線程的, 但如果進行適當的調優,則應用程序應該能夠避免 full GC。

G1 的內存佔用(Footprint)

如果從 ParallelOldGC 或者 CMS收集器遷移到 G1, 您可能會看到JVM進程佔用更多的內存(a larger JVM process size). 這在很大程度上與 “accounting” 數據結構有關, 如 Remembered Sets 和 Collection Sets.

  1. Remembered Sets 簡稱 RSets, 跟蹤指向某個heap區內的對象引用. 堆內存中的每個區都有一個 RSet. RSet 使heap區能並行獨立地進行垃圾集合. RSets的總體影響小於5%. 
  2. Collection Sets 簡稱 CSets, 收集集合, 在一次GC中將執行垃圾回收的heap區. GC時在CSet中的所有存活數據(live data)都會被轉移(複製/移動). 集合中的heap區可以是 Eden, survivor, 和/或 old generation. CSets所佔用的JVM內存小於1%. 

 

 

推薦使用 G1 的場景(Recommended Use Cases)

G1的首要目標是爲需要大量內存的系統提供一個保證GC低延遲的解決方案. 也就是說堆內存在6GB及以上,穩定和可預測的暫停時間小於0.5秒.

如果應用程序具有如下的一個或多個特徵,那麼將垃圾收集器從CMS或ParallelOldGC切換到G1將會大大提升性能.

  • Full GC 次數太頻繁或者消耗時間太長. 
  • 對象分配的頻率或代數提升(promotion)顯著變化. 
  • 受夠了太長的垃圾回收或內存整理時間(超過0.5~1秒) 

 

注意: 如果正在使用CMS或ParallelOldGC,而應用程序的垃圾收集停頓時間並不長,那麼繼續使用現在的垃圾收集器是個好主意. 使用最新的JDK時並不要求切換到G1收集器。

 

二、G1 GC

1、新生代收集

G1的新生代收集跟ParNew類似,當新生代佔用達到一定比例的時候,開始出發收集。

被圈起的綠色部分爲新生代的區域(region),經過Young GC後存活的對象被複制到一個或者多個區域空閒中,這些被填充的區域將是新的新生代;當新生代對象的年齡(逃逸過一次Young GC年齡增加1)已經達到某個閾值(ParNew默認15),被複制到老年代的區域中。

回收過程是停頓的(STW,Stop-The-Word);回收完成之後根據Young GC的統計信息調整Eden和Survivor的大小,有助於合理利用內存,提高回收效率。回收的過程多個回收線程併發收集。

 

2、老年代收集

和CMS類似,G1收集器收集老年代對象會有短暫停頓。

  1. 標記階段,首先初始標記(Initial-Mark),這個階段是停頓的(Stop the World Event),並且會觸發一次普通Mintor GC。對應GC log:GC pause (young) (inital-mark)
  2. Root Region Scanning,程序運行過程中會回收survivor區(存活到老年代),這一過程必須在young GC之前完成。
  3. Concurrent Marking,在整個堆中進行併發標記(和應用程序併發執行),此過程可能被young GC中斷。在併發標記階段,若發現區域對象中的所有對象都是垃圾,那個這個區域會被立即回收(圖中打X)。同時,併發標記過程中,會計算每個區域的對象活性(區域中存活對象的比例)。

     
  4. Remark, 再標記,會有短暫停頓(STW)。再標記階段是用來收集 併發標記階段 產生新的垃圾(併發階段和應用程序一同運行);G1中採用了比CMS更快的初始快照算法:snapshot-at-the-beginning (SATB)。
  5. Copy/Clean up,多線程清除失活對象,會有STW。G1將回收區域的存活對象拷貝到新區域,清除Remember Sets,併發清空回收區域並把它返回到空閒區域鏈表中。
  6. 複製/清除過程後。回收區域的活性對象已經被集中回收到深藍色和深綠色區域。

 3、測試收集

    代碼如下:

package com.tools138.com;

/**
 * 
 * @author alaric
 * 
 */
public class Test3 {

	private static int n = 20;

	/**
	 * @param args
	 * @throws InterruptedException
	 */
	public static void main(String[] args) throws InterruptedException {
		// TODO Auto-generated method stub

		byte[] b1 = getM(50);
		byte[] b2 = getM(50);
		byte[] b3 = getM(50);
		byte[] b4 = getM(50);
		byte[] b5 = getM(50);
		byte[] b6 = getM(50);
		byte[] b7 = getM(5);
		byte[] b8 = getM(5);
		byte[] b9 = getM(5);
		byte[] b10 = getM(5);
		byte[] b11 = getM(5);
		byte[] b12 = getM(5);
		byte[] b13 = getM(5);
		byte[] b14 = getM(5);
		byte[] b15 = getM(5);
		byte[] b16 = getM(5);
		byte[] b17 = getM(5);
		byte[] b18 = getM(5);
		byte[] b19 = getM(5);
		byte[] b20 = getM(100);
		byte[] b21 = getM(100);
		byte[] b22 = getM(100);
		byte[] b23 = getM(100);

	}

	public static byte[] getM(int m) {
		return new byte[1024 * 1024 * m];
	}

}

 

 

 vm參數配置如下:

 

-server -verbose:gc -Xms512m -Xmx512m -Xmn192m -XX:+UseG1GC -XX:MaxGCPauseMillis=100 -XX:InitiatingHeapOccupancyPercent=40  -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -XX:G1HeapRegionSize=2  -Xloggc:D:/logs/gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:/logs/HeapDumpOnOutOfMemoryError.log -XX:+DisableExplicitGC -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps

 代碼和參數配置的意思是直接讓堆內存溢出,可以直接觀察到G1 GC的收集過程,gc.log如下:

 

0.245: [GC pause (G1 Humongous Allocation) (young) (initial-mark), 0.0011950 secs]
   [Parallel Time: 1.0 ms, GC Workers: 4]
      [GC Worker Start (ms): Min: 245.0, Avg: 245.0, Max: 245.1, Diff: 0.0]
      [Ext Root Scanning (ms): Min: 0.3, Avg: 0.5, Max: 0.9, Diff: 0.7, Sum: 1.8]
      [Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
         [Processed Buffers: Min: 0, Avg: 0.0, Max: 0, Diff: 0, Sum: 0]
      [Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Object Copy (ms): Min: 0.0, Avg: 0.5, Max: 0.6, Diff: 0.6, Sum: 1.8]
      [Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1]
         [Termination Attempts: Min: 1, Avg: 1.0, Max: 1, Diff: 0, Sum: 4]
      [GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1]
      [GC Worker Total (ms): Min: 0.9, Avg: 1.0, Max: 1.0, Diff: 0.0, Sum: 3.9]
      [GC Worker End (ms): Min: 246.0, Avg: 246.0, Max: 246.0, Diff: 0.0]
   [Code Root Fixup: 0.0 ms]
   [Code Root Purge: 0.0 ms]
   [Clear CT: 0.0 ms]
   [Other: 0.2 ms]
      [Choose CSet: 0.0 ms]
      [Ref Proc: 0.0 ms]
      [Ref Enq: 0.0 ms]
      [Redirty Cards: 0.0 ms]
      [Humongous Register: 0.0 ms]
      [Humongous Reclaim: 0.0 ms]
      [Free CSet: 0.0 ms]
   [Eden: 2048.0K(192.0M)->0.0B(191.0M) Survivors: 0.0B->1024.0K Heap: 202.0M(512.0M)->200.6M(512.0M)]
 [Times: user=0.00 sys=0.00, real=0.00 secs]
0.246: [GC concurrent-root-region-scan-start]
0.247: [GC concurrent-root-region-scan-end, 0.0006084 secs]
0.247: [GC concurrent-mark-start]
0.247: [GC concurrent-mark-end, 0.0000903 secs]
0.259: [GC remark 0.259: [Finalize Marking, 0.0000809 secs] 0.259: [GC ref-proc, 0.0000390 secs] 0.259: [Unloading, 0.0005304 secs], 0.0022501 secs]
 [Times: user=0.00 sys=0.00, real=0.00 secs]
0.274: [GC cleanup 300M->300M(512M), 0.0003358 secs]
 [Times: user=0.00 sys=0.00, real=0.00 secs] 
黑色粗體說明一個完整的老年代GC過程,藍色代表一次新生代GC過程。一個大對象進入後,觸發新生代垃圾回收,然後進入老年代,同時老年代也開始初始標記-併發根掃描-併發標記-重新標記-清除。詳細的日誌理解請看https://blogs.oracle.com/poonam/entry/understanding_g1_gc_logs

 

三、垃圾收集器配置參數:

Option and Default Value Description
-XX:+UseG1GC Use the Garbage First (G1) Collector
-XX:MaxGCPauseMillis=n Sets a target for the maximum GC pause time. This is a soft goal, and the JVM will make its best effort to achieve it.
-XX:InitiatingHeapOccupancyPercent=n Percentage of the (entire) heap occupancy to start a concurrent GC cycle. It is used by GCs that trigger a concurrent GC cycle based on the occupancy of the entire heap, not just one of the generations (e.g., G1). A value of 0 denotes 'do constant GC cycles'. The default value is 45.
-XX:NewRatio=n Ratio of old/new generation sizes. The default value is 2.
-XX:SurvivorRatio=n Ratio of eden/survivor space size. The default value is 8.
-XX:MaxTenuringThreshold=n Maximum value for tenuring threshold. The default value is 15.
-XX:ParallelGCThreads=n Sets the number of threads used during parallel phases of the garbage collectors. The default value varies with the platform on which the JVM is running.
-XX:ConcGCThreads=n Number of threads concurrent garbage collectors will use. The default value varies with the platform on which the JVM is running.
-XX:G1ReservePercent=n Sets the amount of heap that is reserved as a false ceiling to reduce the possibility of promotion failure. The default value is 10.
-XX:G1HeapRegionSize=n With G1 the Java heap is subdivided into uniformly sized regions. This sets the size of the individual sub-divisions. The default value of this parameter is determined ergonomically based upon heap size. The minimum value is 1Mb and the maximum value is 32Mb.

 

 

 

參考資料:
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章