天天看點

Java Hotspot G1 GC的一些關鍵技術

前言

G1 GC,全稱Garbage-First Garbage Collector,通過-XX:+UseG1GC參數來啟用,作為體驗版随着JDK 6u14版本面世,在JDK 7u4版本發行時被正式推出,相信熟悉JVM的同學們都不會對它感到陌生。在JDK 9中,G1被提議設定為預設垃圾收集器(JEP 248)。在官網中,是這樣描述G1的:

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是一種伺服器端的垃圾收集器,應用在多處理器和大容量記憶體環境中,在實作高吞吐量的同時,盡可能的滿足垃圾收集暫停時間的要求。它是專門針對以下應用場景設計的:

  • 像CMS收集器一樣,能與應用程式線程并發執行。
  • 整理空閑空間更快。
  • 需要GC停頓時間更好預測。
  • 不希望犧牲大量的吞吐性能。
  • 不需要更大的Java Heap。

G1收集器的設計目标是取代CMS收集器,它同CMS相比,在以下方面表現的更出色:

  • G1是一個有整理記憶體過程的垃圾收集器,不會産生很多記憶體碎片。
  • G1的Stop The World(STW)更可控,G1在停頓時間上添加了預測機制,使用者可以指定期望停頓時間。

有了以上的特性,難怪有人說它是一款駕馭一切的垃圾收集器(G1: One Garbage Collector To Rule Them All)。本文帶大家來了解一下G1 GC的一些關鍵技術,為能正确的使用它,做好理論基礎的鋪墊。

G1中幾個重要概念

在G1的實作過程中,引入了一些新的概念,對于實作高吞吐、沒有記憶體碎片、收集時間可控等功能起到了關鍵作用。下面我們就一起看一下G1中的這幾個重要概念。

Region

傳統的GC收集器将連續的記憶體空間劃分為新生代、老年代和永久代(JDK 8去除了永久代,引入了元空間Metaspace),這種劃分的特點是各代的存儲位址(邏輯位址,下同)是連續的。如下圖所示:

Java Hotspot G1 GC的一些關鍵技術

而G1的各代存儲位址是不連續的,每一代都使用了n個不連續的大小相同的Region,每個Region占有一塊連續的虛拟記憶體位址。如下圖所示:

Java Hotspot G1 GC的一些關鍵技術

在上圖中,我們注意到還有一些Region标明了H,它代表Humongous,這表示這些Region存儲的是巨大對象(humongous object,H-obj),即大小大于等于region一半的對象。H-obj有如下幾個特征:

  • H-obj直接配置設定到了old gen,防止了反複拷貝移動。
  • H-obj在global concurrent marking階段的cleanup 和 full GC階段回收。
  • 在配置設定H-obj之前先檢查是否超過 initiating heap occupancy percent和the marking threshold, 如果超過的話,就啟動global concurrent marking,為的是提早回收,防止 evacuation failures 和 full GC。

為了減少連續H-objs配置設定對GC的影響,需要把大對象變為普通的對象,建議增大Region size。

一個Region的大小可以通過參數-XX:G1HeapRegionSize設定,取值範圍從1M到32M,且是2的指數。如果不設定,那麼G1會根據Heap大小自動決定。相關的設定代碼如下:

// share/vm/gc_implementation/g1/heapRegion.cpp// Minimum region size; we won't go lower than that.// We might want to decrease this in the future, to deal with small// heaps a bit more efficiently.#define MIN_REGION_SIZE  (      1024 * 1024 )// Maximum region size; we don't go higher than that. There's a good// reason for having an upper bound. We don't want regions to get too// large, otherwise cleanup's effectiveness would decrease as there// will be fewer opportunities to find totally empty regions after// marking.#define MAX_REGION_SIZE  ( 32 * 1024 * 1024 )// The automatic region size calculation will try to have around this// many regions in the heap (based on the min heap size).#define TARGET_REGION_NUMBER          2048void HeapRegion::setup_heap_region_size(size_t initial_heap_size, size_t max_heap_size) {
  uintx region_size = G1HeapRegionSize;  if (FLAG_IS_DEFAULT(G1HeapRegionSize)) {    size_t average_heap_size = (initial_heap_size + max_heap_size) / 2;
    region_size = MAX2(average_heap_size / TARGET_REGION_NUMBER,
                       (uintx) MIN_REGION_SIZE);
  }  int region_size_log = log2_long((jlong) region_size);  // Recalculate the region size to make sure it's a power of
  // 2. This means that region_size is the largest power of 2 that's
  // <= what we've calculated so far.
  region_size = ((uintx)1 << region_size_log);  // Now make sure that we don't go over or under our limits.
  if (region_size < MIN_REGION_SIZE) {
    region_size = MIN_REGION_SIZE;
  } else if (region_size > MAX_REGION_SIZE) {
    region_size = MAX_REGION_SIZE;
  }
}           

SATB

全稱是Snapshot-At-The-Beginning,由字面了解,是GC開始時活着的對象的一個快照。它是通過Root Tracing得到的,作用是維持并發GC的正确性。

那麼它是怎麼維持并發GC的正确性的呢?根據三色标記算法,我們知道對象存在三種狀态:

  • 白:對象沒有被标記到,标記階段結束後,會被當做垃圾回收掉。
  • 灰:對象被标記了,但是它的field還沒有被标記或标記完。
  • 黑:對象被标記了,且它的所有field也被标記完了。

由于并發階段的存在,Mutator和Garbage Collector線程同時對對象進行修改,就會出現白對象漏标的情況,這種情況發生的前提是:

  • Mutator賦予一個黑對象該白對象的引用。
  • Mutator删除了所有從灰對象到該白對象的直接或者間接引用。

對于第一個條件,在并發标記階段,如果該白對象是new出來的,并沒有被灰對象持有,那麼它會不會被漏标呢?Region中有兩個top-at-mark-start(TAMS)指針,分别為prevTAMS和nextTAMS。在TAMS以上的對象是新配置設定的,這是一種隐式的标記。對于在GC時已經存在的白對象,如果它是活着的,它必然會被另一個對象引用,即條件二中的灰對象。如果灰對象到白對象的直接引用或者間接引用被替換了,或者删除了,白對象就會被漏标,進而導緻被回收掉,這是非常嚴重的錯誤,是以SATB破壞了第二個條件。也就是說,一個對象的引用被替換時,可以通過write barrier 将舊引用記錄下來。

//  share/vm/gc_implementation/g1/g1SATBCardTableModRefBS.hpp// This notes that we don't need to access any BarrierSet data// structures, so this can be called from a static context.template <class T> static void write_ref_field_pre_static(T* field, oop newVal) {
  T heap_oop = oopDesc::load_heap_oop(field);  if (!oopDesc::is_null(heap_oop)) {
    enqueue(oopDesc::decode_heap_oop(heap_oop));
  }
}// share/vm/gc_implementation/g1/g1SATBCardTableModRefBS.cppvoid G1SATBCardTableModRefBS::enqueue(oop pre_val) {  // Nulls should have been already filtered.
  assert(pre_val->is_oop(true), "Error");  if (!JavaThread::satb_mark_queue_set().is_active()) return;
  Thread* thr = Thread::current();  if (thr->is_Java_thread()) {
    JavaThread* jt = (JavaThread*)thr;
    jt->satb_mark_queue().enqueue(pre_val);
  } else {    MutexLockerEx x(Shared_SATB_Q_lock, Mutex::_no_safepoint_check_flag);
    JavaThread::satb_mark_queue_set().shared_satb_queue()->enqueue(pre_val);
  }
}           

SATB也是有副作用的,如果被替換的白對象就是要被收集的垃圾,這次的标記會讓它躲過GC,這就是float garbage。因為SATB的做法精度比較低,是以造成的float garbage也會比較多。

RSet

全稱是Remembered Set,是輔助GC過程的一種結構,典型的空間換時間工具,和Card Table有些類似。還有一種資料結構也是輔助GC的:Collection Set(CSet),它記錄了GC要收集的Region集合,集合裡的Region可以是任意年代的。在GC的時候,對于old->young和old->old的跨代對象引用,隻要掃描對應的CSet中的RSet即可。

邏輯上說每個Region都有一個RSet,RSet記錄了其他Region中的對象引用本Region中對象的關系,屬于points-into結構(誰引用了我的對象)。而Card Table則是一種points-out(我引用了誰的對象)的結構,每個Card 覆寫一定範圍的Heap(一般為512Bytes)。G1的RSet是在Card Table的基礎上實作的:每個Region會記錄下别的Region有指向自己的指針,并标記這些指針分别在哪些Card的範圍内。 這個RSet其實是一個Hash Table,Key是别的Region的起始位址,Value是一個集合,裡面的元素是Card Table的Index。

下圖表示了RSet、Card和Region的關系(出處):

Java Hotspot G1 GC的一些關鍵技術

上圖中有三個Region,每個Region被分成了多個Card,在不同Region中的Card會互相引用,Region1中的Card中的對象引用了Region2中的Card中的對象,藍色實線表示的就是points-out的關系,而在Region2的RSet中,記錄了Region1的Card,即紅色虛線表示的關系,這就是points-into。

而維系RSet中的引用關系靠post-write barrier和Concurrent refinement threads來維護,操作僞代碼如下(出處):

void oop_field_store(oop* field, oop new_value) {
  pre_write_barrier(field);             // pre-write barrier: for maintaining SATB invariant
  *field = new_value;                   // the actual store
  post_write_barrier(field, new_value); // post-write barrier: for tracking cross-region reference}           

post-write barrier記錄了跨Region的引用更新,更新日志緩沖區則記錄了那些包含更新引用的Cards。一旦緩沖區滿了,Post-write barrier就停止服務了,會由Concurrent refinement threads處理這些緩沖區日志。

RSet究竟是怎麼輔助GC的呢?在做YGC的時候,隻需要標明young generation region的RSet作為根集,這些RSet記錄了old->young的跨代引用,避免了掃描整個old generation。 而mixed gc的時候,old generation中記錄了old->old的RSet,young->old的引用由掃描全部young generation region得到,這樣也不用掃描全部old generation region。是以RSet的引入大大減少了GC的工作量。

Pause Prediction Model

Pause Prediction Model 即停頓預測模型。它在G1中的作用是:

G1 uses a pause prediction model to meet a user-defined pause time target and selects the number of regions to collect based on the specified pause time target.

G1 GC是一個響應時間優先的GC算法,它與CMS最大的不同是,使用者可以設定整個GC過程的期望停頓時間,參數-XX:MaxGCPauseMillis指定一個G1收集過程目标停頓時間,預設值200ms,不過它不是硬性條件,隻是期望值。那麼G1怎麼滿足使用者的期望呢?就需要這個停頓預測模型了。G1根據這個模型統計計算出來的曆史資料來預測本次收集需要選擇的Region數量,進而盡量滿足使用者設定的目标停頓時間。

停頓預測模型是以衰減标準偏差為理論基礎實作的:

//  share/vm/gc_implementation/g1/g1CollectorPolicy.hppdouble get_new_prediction(TruncatedSeq* seq) {    return MAX2(seq->davg() + sigma() * seq->dsd(),
                seq->davg() * confidence_factor(seq->num()));
}           

在這個預測計算公式中:davg表示衰減均值,sigma()傳回一個系數,表示信賴度,dsd表示衰減标準偏差,confidence_factor表示可信度相關系數。而方法的參數TruncateSeq,顧名思義,是一個截斷的序列,它隻跟蹤了序列中的最新的n個元素。

在G1 GC過程中,每個可測量的步驟花費的時間都會記錄到TruncateSeq(繼承了AbsSeq)中,用來計算衰減均值、衰減變量,衰減标準偏差等:

// src/share/vm/utilities/numberSeq.cppvoid AbsSeq::add(double val) {  if (_num == 0) {    // if the sequence is empty, the davg is the same as the value
    _davg = val;    // and the variance is 0
    _dvariance = 0.0;
  } else {    // otherwise, calculate both
    _davg = (1.0 - _alpha) * val + _alpha * _davg;    double diff = val - _davg;
    _dvariance = (1.0 - _alpha) * diff * diff + _alpha * _dvariance;
  }
}           

比如要預測一次GC過程中,RSet的更新時間,這個操作主要是将Dirty Card加入到RSet中,具體原理參考前面的RSet。每個Dirty Card的時間花費通過_cost_per_card_ms_seq來記錄,具體預測代碼如下:

//  share/vm/gc_implementation/g1/g1CollectorPolicy.hpp

 double predict_rs_update_time_ms(size_t pending_cards) {    return (double) pending_cards * predict_cost_per_card_ms();
 } double predict_cost_per_card_ms() {    return get_new_prediction(_cost_per_card_ms_seq);
 }           

get_new_prediction就是我們開頭說的方法,現在大家應該基本明白停頓預測模型的實作原理了。

GC過程

講完了一些基本概念,下面我們就來看看G1的GC過程是怎樣的。

G1 GC模式

G1提供了兩種GC模式,Young GC和Mixed GC,兩種都是完全Stop The World的。

  • Young GC:標明所有年輕代裡的Region。通過控制年輕代的region個數,即年輕代記憶體大小,來控制young GC的時間開銷。
  • Mixed GC:標明所有年輕代裡的Region,外加根據global concurrent marking統計得出收集收益高的若幹老年代Region。在使用者指定的開銷目标範圍内盡可能選擇收益高的老年代Region。

由上面的描述可知,Mixed GC不是full GC,它隻能回收部分老年代的Region,如果mixed GC實在無法跟上程式配置設定記憶體的速度,導緻老年代填滿無法繼續進行Mixed GC,就會使用serial old GC(full GC)來收集整個GC heap。是以我們可以知道,G1是不提供full GC的。

上文中,多次提到了global concurrent marking,它的執行過程類似CMS,但是不同的是,在G1 GC中,它主要是為Mixed GC提供标記服務的,并不是一次GC過程的一個必須環節。global concurrent marking的執行過程分為四個步驟:

  • 初始标記(initial mark,STW)。它标記了從GC Root開始直接可達的對象。
  • 并發标記(Concurrent Marking)。這個階段從GC Root開始對heap中的對象标記,标記線程與應用程式線程并行執行,并且收集各個Region的存活對象資訊。
  • 最終标記(Remark,STW)。标記那些在并發标記階段發生變化的對象,将被回收。
  • 清除垃圾(Cleanup)。清除空Region(沒有存活對象的),加入到free list。

第一階段initial mark是共用了Young GC的暫停,這是因為他們可以複用root scan操作,是以可以說global concurrent marking是伴随Young GC而發生的。第四階段Cleanup隻是回收了沒有存活對象的Region,是以它并不需要STW。

Young GC發生的時機大家都知道,那什麼時候發生Mixed GC呢?其實是由一些參數控制着的,另外也控制着哪些老年代Region會被選入CSet。

  • G1HeapWastePercent:在global concurrent marking結束之後,我們可以知道old gen regions中有多少空間要被回收,在每次YGC之後和再次發生Mixed GC之前,會檢查垃圾占比是否達到此參數,隻有達到了,下次才會發生Mixed GC。
  • G1MixedGCLiveThresholdPercent:old generation region中的存活對象的占比,隻有在此參數之下,才會被選入CSet。
  • G1MixedGCCountTarget:一次global concurrent marking之後,最多執行Mixed GC的次數。
  • G1OldCSetRegionThresholdPercent:一次Mixed GC中能被選入CSet的最多old generation region數量。

除了以上的參數,G1 GC相關的其他主要的參數有:

參數 含義
-XX:G1HeapRegionSize=n 設定Region大小,并非最終值
-XX:MaxGCPauseMillis 設定G1收集過程目标時間,預設值200ms,不是硬性條件
-XX:G1NewSizePercent 新生代最小值,預設值5%
-XX:G1MaxNewSizePercent 新生代最大值,預設值60%
-XX:ParallelGCThreads STW期間,并行GC線程數
-XX:ConcGCThreads=n 并發标記階段,并行執行的線程數
-XX:InitiatingHeapOccupancyPercent 設定觸發标記周期的 Java 堆占用率門檻值。預設值是45%。這裡的java堆占比指的是non_young_capacity_bytes,包括old+humongous

GC日志

G1收集器的日志與其他收集器有很大不同,源于G1獨立的體系架構和資料結構,下面這兩段日志來源于美團點評的CRM系統線上生産環境。

Young GC日志

我們先來看看Young GC的日志:

{Heap before GC invocations=12 (full 1):
 garbage-first heap   total 3145728K, used 336645K [0x0000000700000000, 0x00000007c0000000, 0x00000007c0000000)
  region size 1024K, 172 young (176128K), 13 survivors (13312K)
 Metaspace       used 29944K, capacity 30196K, committed 30464K, reserved 1077248K
  class space    used 3391K, capacity 3480K, committed 3584K, reserved 1048576K
2014-11-14T17:57:23.654+0800: 27.884: [GC pause (G1 Evacuation Pause) (young)
Desired survivor size 11534336 bytes, new threshold 15 (max 15)
- age   1:    5011600 bytes,    5011600 total
 27.884: [G1Ergonomics (CSet Construction) start choosing CSet, _pending_cards: 1461, predicted base time: 35.25 ms, remaining time: 64.75 ms, target pause time: 100.00 ms]
 27.884: [G1Ergonomics (CSet Construction) add young regions to CSet, eden: 159 regions, survivors: 13 regions, predicted young region time: 44.09 ms]
 27.884: [G1Ergonomics (CSet Construction) finish choosing CSet, eden: 159 regions, survivors: 13 regions, old: 0 regions, predicted pause time: 79.34 ms, target pause time: 100.00 ms]
, 0.0158389 secs]
   [Parallel Time: 8.1 ms, GC Workers: 4]
      [GC Worker Start (ms): Min: 27884.5, Avg: 27884.5, Max: 27884.5, Diff: 0.1]
      [Ext Root Scanning (ms): Min: 0.4, Avg: 0.8, Max: 1.2, Diff: 0.8, Sum: 3.1]
      [Update RS (ms): Min: 0.0, Avg: 0.3, Max: 0.6, Diff: 0.6, Sum: 1.4]
         [Processed Buffers: Min: 0, Avg: 2.8, Max: 5, Diff: 5, Sum: 11]
      [Scan RS (ms): Min: 0.0, Avg: 0.1, Max: 0.1, Diff: 0.1, Sum: 0.3]
      [Code Root Scanning (ms): Min: 0.0, Avg: 0.1, Max: 0.2, Diff: 0.2, Sum: 0.6]
      [Object Copy (ms): Min: 4.9, Avg: 5.1, Max: 5.2, Diff: 0.3, Sum: 20.4]
      [Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [GC Worker Other (ms): Min: 0.0, Avg: 0.4, Max: 1.3, Diff: 1.3, Sum: 1.4]
      [GC Worker Total (ms): Min: 6.4, Avg: 6.8, Max: 7.8, Diff: 1.4, Sum: 27.2]
      [GC Worker End (ms): Min: 27891.0, Avg: 27891.3, Max: 27892.3, Diff: 1.3]
   [Code Root Fixup: 0.5 ms]
   [Code Root Migration: 1.3 ms]
   [Code Root Purge: 0.0 ms]
   [Clear CT: 0.2 ms]
   [Other: 5.8 ms]
      [Choose CSet: 0.0 ms]
      [Ref Proc: 5.0 ms]
      [Ref Enq: 0.1 ms]
      [Redirty Cards: 0.0 ms]
      [Free CSet: 0.2 ms]
   [Eden: 159.0M(159.0M)->0.0B(301.0M) Survivors: 13.0M->11.0M Heap: 328.8M(3072.0M)->167.3M(3072.0M)]
Heap after GC invocations=13 (full 1):
 garbage-first heap   total 3145728K, used 171269K [0x0000000700000000, 0x00000007c0000000, 0x00000007c0000000)
  region size 1024K, 11 young (11264K), 11 survivors (11264K)
 Metaspace       used 29944K, capacity 30196K, committed 30464K, reserved 1077248K
  class space    used 3391K, capacity 3480K, committed 3584K, reserved 1048576K
}
 [Times: user=0.05 sys=0.01, real=0.02 secs]           

每個過程的作用如下:

  • garbage-first heap total 3145728K, used 336645K [0x0000000700000000, 0x00000007c0000000, 0x00000007c0000000)

    這行表示使用了G1垃圾收集器,total heap 3145728K,使用了336645K。

  • region size 1024K, 172 young (176128K), 13 survivors (13312K)

    Region大小為1M,青年代占用了172個(共176128K),幸存區占用了13個(共13312K)。

  • Metaspace used 29944K, capacity 30196K, committed 30464K, reserved 1077248K

    class space used 3391K, capacity 3480K, committed 3584K, reserved 1048576K

    java 8的新特性,去掉永久區,添加了中繼資料區,這塊不是本文重點,不再贅述。需要注意的是,之是以有committed和reserved,是因為沒有設定MetaspaceSize=MaxMetaspaceSize。

  • [GC pause (G1 Evacuation Pause) (young)

    GC原因,新生代minor GC。

  • [G1Ergonomics (CSet Construction) start choosing CSet, _pending_cards: 1461, predicted base time: 35.25 ms, remaining time: 64.75 ms, target pause time: 100.00 ms]

    發生minor GC和full GC時,所有相關region都是要回收的。而發生并發GC時,會根據目标停頓時間動态選擇部分垃圾對并多的Region回收,這一步就是選擇Region。_pending_cards是關于RSet的Card Table。predicted base time是預測的掃描card table時間。

  • [G1Ergonomics (CSet Construction) add young regions to CSet, eden: 159 regions, survivors: 13 regions, predicted young region time: 44.09 ms]

    這一步是添加Region到collection set,新生代一共159個Region,13個幸存區Region,這也和之前的(172 young (176128K), 13 survivors (13312K))吻合。預計收集時間是44.09 ms。

  • [G1Ergonomics (CSet Construction) finish choosing CSet, eden: 159 regions, survivors: 13 regions, old: 0 regions, predicted pause time: 79.34 ms, target pause time: 100.00 ms]

    這一步是對上面兩步的總結。預計總收集時間79.34ms。

  • [Parallel Time: 8.1 ms, GC Workers: 4]

    由于收集過程是多線程并行(并發)進行,這裡是4個線程,總共耗時8.1ms(wall clock time)

  • [GC Worker Start (ms): Min: 27884.5, Avg: 27884.5, Max: 27884.5, Diff: 0.1]

    收集線程開始的時間,使用的是相對時間,Min是最早開始時間,Avg是平均開始時間,Max是最晚開始時間,Diff是Max-Min(此處的0.1貌似有問題)

  • [Ext Root Scanning (ms): Min: 0.4, Avg: 0.8, Max: 1.2, Diff: 0.8, Sum: 3.1]

    掃描Roots花費的時間,Sum表示total cpu time,下同。

  • [Update RS (ms): Min: 0.0, Avg: 0.3, Max: 0.6, Diff: 0.6, Sum: 1.4] [Processed Buffers: Min: 0, Avg: 2.8, Max: 5, Diff: 5, Sum: 11]

    Update RS (ms)是每個線程花費在更新Remembered Set上的時間。

  • [Scan RS (ms): Min: 0.0, Avg: 0.1, Max: 0.1, Diff: 0.1, Sum: 0.3]

    掃描CS中的region對應的RSet,因為RSet是points-into,是以這樣實作避免了掃描old generadion region,但是會産生float garbage。

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

    掃描code root耗時。code root指的是經過JIT編譯後的代碼裡,引用了heap中的對象。引用關系儲存在RSet中。

  • [Object Copy (ms): Min: 4.9, Avg: 5.1, Max: 5.2, Diff: 0.3, Sum: 20.4]

    拷貝活的對象到新region的耗時。

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

    線程結束,在結束前,它會檢查其他線程是否還有未掃描完的引用,如果有,則"偷"過來,完成後再申請結束,這個時間是線程之前互相同步所花費的時間。

  • [GC Worker Other (ms): Min: 0.0, Avg: 0.4, Max: 1.3, Diff: 1.3, Sum: 1.4]

    花費在其他工作上(未列出)的時間。

  • [GC Worker Total (ms): Min: 6.4, Avg: 6.8, Max: 7.8, Diff: 1.4, Sum: 27.2]

    每個線程花費的時間和。

  • [GC Worker End (ms): Min: 27891.0, Avg: 27891.3, Max: 27892.3, Diff: 1.3]

    每個線程結束的時間。

  • [Code Root Fixup: 0.5 ms]

    用來将code root修正到正确的evacuate之後的對象位置所花費的時間。

  • [Code Root Migration: 1.3 ms]

    更新code root 引用的耗時,code root中的引用因為對象的evacuation而需要更新。

  • [Code Root Purge: 0.0 ms]

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

  • [Clear CT: 0.2 ms]

    清除card table的耗時。

  • [Other: 5.8 ms]

    [Choose CSet: 0.0 ms]

    [Ref Proc: 5.0 ms]

    [Ref Enq: 0.1 ms]

    [Redirty Cards: 0.0 ms]

    [Free CSet: 0.2 ms]

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

  • [Eden: 159.0M(159.0M)->0.0B(301.0M) Survivors: 13.0M->11.0M Heap: 328.8M(3072.0M)->167.3M(3072.0M)]

    新生代清空了,下次擴容到301MB。

global concurrent marking 日志

對于global concurrent marking過程,它的日志如下所示:

66955.252: [G1Ergonomics (Concurrent Cycles) request concurrent cycle initiation, reason: occupancy higher than threshold, occupancy: 1449132032 bytes, allocation request: 579608 bytes, threshold: 1449
551430 bytes (45.00 %), source: concurrent humongous allocation]
2014-12-10T11:13:09.532+0800: 66955.252: Application time: 2.5750418 seconds
 66955.259: [G1Ergonomics (Concurrent Cycles) request concurrent cycle initiation, reason: requested by GC cause, GC cause: G1 Humongous Allocation]
{Heap before GC invocations=1874 (full 4):
 garbage-first heap   total 3145728K, used 1281786K [0x0000000700000000, 0x00000007c0000000, 0x00000007c0000000)
  region size 1024K, 171 young (175104K), 27 survivors (27648K)
 Metaspace       used 116681K, capacity 137645K, committed 137984K, reserved 1171456K
  class space    used 13082K, capacity 16290K, committed 16384K, reserved 1048576K
 66955.259: [G1Ergonomics (Concurrent Cycles) initiate concurrent cycle, reason: concurrent cycle initiation requested]
2014-12-10T11:13:09.539+0800: 66955.259: [GC pause (G1 Humongous Allocation) (young) (initial-mark)
…….
2014-12-10T11:13:09.597+0800: 66955.317: [GC concurrent-root-region-scan-start]
2014-12-10T11:13:09.597+0800: 66955.318: Total time for which application threads were stopped: 0.0655753 seconds
2014-12-10T11:13:09.610+0800: 66955.330: Application time: 0.0127071 seconds
2014-12-10T11:13:09.614+0800: 66955.335: Total time for which application threads were stopped: 0.0043882 seconds
2014-12-10T11:13:09.625+0800: 66955.346: [GC concurrent-root-region-scan-end, 0.0281351 secs]
2014-12-10T11:13:09.625+0800: 66955.346: [GC concurrent-mark-start]
2014-12-10T11:13:09.645+0800: 66955.365: Application time: 0.0306801 seconds
2014-12-10T11:13:09.651+0800: 66955.371: Total time for which application threads were stopped: 0.0061326 seconds
2014-12-10T11:13:10.212+0800: 66955.933: [GC concurrent-mark-end, 0.5871129 secs]
2014-12-10T11:13:10.212+0800: 66955.933: Application time: 0.5613792 seconds
2014-12-10T11:13:10.215+0800: 66955.935: [GC remark 66955.936: [GC ref-proc, 0.0235275 secs], 0.0320865 secs]
 [Times: user=0.05 sys=0.00, real=0.03 secs]
2014-12-10T11:13:10.247+0800: 66955.968: Total time for which application threads were stopped: 0.0350098 seconds
2014-12-10T11:13:10.248+0800: 66955.968: Application time: 0.0001691 seconds
2014-12-10T11:13:10.250+0800: 66955.970: [GC cleanup 1178M->632M(3072M), 0.0060632 secs]
 [Times: user=0.02 sys=0.00, real=0.01 secs]
2014-12-10T11:13:10.256+0800: 66955.977: Total time for which application threads were stopped: 0.0088462 seconds
2014-12-10T11:13:10.257+0800: 66955.977: [GC concurrent-cleanup-start]
2014-12-10T11:13:10.259+0800: 66955.979: [GC concurrent-cleanup-end, 0.0024743 secs           

這次發生global concurrent marking的原因是:humongous allocation,上面提過在巨大對象配置設定之前,會檢測到old generation 使用占比是否超過了 initiating heap occupancy percent(45%),因為

1449132032(used)+ 579608(allocation request:) > 1449551430(threshold),是以觸發了本次global concurrent marking。對于具體執行過程,上面的表格已經詳細講解了。值得注意的是上文中所說的initial mark往往伴随着一次YGC,在日志中也有展現:GC pause (G1 Humongous Allocation) (young) (initial-mark)。

後記

因為篇幅的關系,也受限于能力水準,本文隻是簡單了介紹了G1 GC的基本原理,很多細節沒有涉及到,是以說隻能算是為研究和使用它的同學打開了一扇門。一個日本人專門寫了一本書《徹底解剖「G1GC」 アルゴリズ》詳細的介紹了G1 GC,這本書也被作者放到了GitHub上,詳見參考文獻5。另外,莫樞在這方面也研究的比較多,讀者可以去進階語言虛拟機論壇向他請教,本文的很多内容也是我在此論壇上請教過後整理的。總而言之,G1是一款非常優秀的垃圾收集器,盡管還有些不完美(預測模型還不夠智能),但是希望有更多的同學來使用它,研究它,提出好的建議,讓它變的更加完善。

參考文獻

  1. Getting Started with the G1 Garbage Collector
  2. 請教G1算法的原理
  3. 關于incremental update與SATB的一點了解
  4. Tips for Tuning the Garbage First Garbage Collector
  5. g1gc-impl-book
  6. 垃圾優先型垃圾回收器調優
  7. Understanding G1 GC Logs
  8. G1: One Garbage Collector To Rule Them All

繼續閱讀