通過源碼學(xué)習(xí)G1GC —— 新生代自適應(yīng)策略

0.

最近發(fā)現(xiàn)有個(gè) Java 寫的 sidecar 程序本來運(yùn)行很平穩(wěn)践图,忽然 Young GC 的頻率就開始升高,新生代大小頻繁的調(diào)整喇伯,最終是在一輪 Mixed GC 之后才恢復(fù)平穩(wěn)幼东。

根據(jù)以往學(xué)習(xí)的理論知識(shí)可知 G1 有一個(gè)特性就是自適應(yīng)調(diào)整新生代的大小,避免停頓時(shí)間超過閾值嗅剖,以實(shí)現(xiàn) MaxGCPauseMillis 的軟實(shí)時(shí)目標(biāo)。

但是出現(xiàn)這種頻繁調(diào)整的情況是很不正常的嘁扼,k8s 容器的穩(wěn)定性會(huì)差一些信粮,偶爾會(huì)遇到一些很詭異的 cpu throttle,而這個(gè) sidecar 的 MaxGCPauseMillis 設(shè)置的過型蛋巍(10ms)蒋院,運(yùn)行環(huán)境稍有波動(dòng)亏钩,G1 就會(huì)進(jìn)行不必要的動(dòng)態(tài)調(diào)整,甚至進(jìn)入下圖中的異常震蕩狀態(tài)欺旧。

在不穩(wěn)定環(huán)境中姑丑,MaxGCPauseMillis 這種閾值不能設(shè)置的調(diào)小〈怯眩或者可以通過設(shè)置 Xmn 關(guān)閉自適應(yīng)調(diào)整策略栅哀。

新生代
GC頻率

G1 的源碼之前囫圇吞棗看過一遍,其中大部分細(xì)節(jié)都忽略沒看称龙。

這次準(zhǔn)備把新生代自適應(yīng)調(diào)整的代碼仔細(xì)看看留拾,順便做個(gè)筆記。

代碼的版本為 openjdk/jdk11u

1. MMU target violated

在 GC 日志中有很多這樣的信息:

[info ][gc,mmu        ] GC(50870) MMU target violated: 11.0ms (10.0ms/11.0ms)

何為 MMU target鲫尊?又是如何 violated 的呢痴柔?

根據(jù) GC 日志的信息來看,好像指得是該次 GC 的停頓時(shí)間超過了 MaxGCPauseMillis疫向,這個(gè)理解也對(duì)咳蔚,但是并不準(zhǔn)確。

該行日志是在 G1MMUTrackerQueue::add_pause 方法中打印的搔驼,對(duì)于 Pause Young (Normal)谈火,其調(diào)用棧為:

--> G1CollectedHeap::do_collection_pause_at_safepoint(double target_pause_time_ms)
  --> void G1Policy::record_collection_pause_end(double pause_time_ms, size_t cards_scanned, size_t heap_used_bytes_before_gc)
    --> void G1Policy::record_pause(PauseKind kind, double start, double end)
      --> void G1MMUTrackerQueue::add_pause(double start, double end)

另外對(duì)于 Remark 和 Cleanup 等也會(huì)調(diào)用 G1MMUTrackerQueue::add_pause 方法,換句話說 FullGC 除外的所有 STW 停頓都會(huì)記錄在這個(gè)隊(duì)列中舌涨。

// share/gc/g1/g1MMUTracker.cpp
 78 void G1MMUTrackerQueue::add_pause(double start, double end) {
 79   double duration = end - start;
 80
 81   remove_expired_entries(end);
 82   if (_no_entries == QueueLength) {
.....
 97     _head_index = trim_index(_head_index + 1);
 98     assert(_head_index == _tail_index, "Because we have a full circular buffer");
 99     _tail_index = trim_index(_tail_index + 1);
100   } else {
101     _head_index = trim_index(_head_index + 1);
102     ++_no_entries;
103   }
104   _array[_head_index] = G1MMUTrackerQueueElem(start, end);
105
......
107   double slice_time = calculate_gc_time(end);
108   G1MMUTracer::report_mmu(_time_slice, slice_time, _max_gc_time);
109
110   if (slice_time >= _max_gc_time) {
111     log_info(gc, mmu)("MMU target violated: %.1lfms (%.1lfms/%.1lfms)", slice_time * 1000.0, _max_gc_time * 1000.0, _time_slice * 1000);
112   }
113 }
  • startend 分別表示該次 STW 停頓的起始和結(jié)束時(shí)間
  • _max_gc_time 就是設(shè)置的 MaxGCPauseMillis
  • _time_slice 就是 GCPauseIntervalMillis糯耍,默認(rèn)值為 MaxGCPauseMillis+1

了解了這些信息就很容易理解 g1MMUTracker.hpp 中對(duì) MMU 的注釋了:

Mutator Utilisation:給定一個(gè)時(shí)間片 ts,mutator utilisation = non_gc_time / ts囊嘉,也就是非 GC 時(shí)間的比例温技。
Minimum Mutator Utilisation (MMU): mutator utilisation 的最差情況。

MMU 設(shè)定的目標(biāo)就是: 1 - MaxGCPauseMillis/GCPauseIntervalMillis

L104 行里的 _array 保存的就是歷次 STW 停頓的時(shí)間信息哗伯;L107 行的 calculate_gc_time(end) 方法計(jì)算的 slice_time 即為從現(xiàn)在開始向前推一個(gè)時(shí)間片里停頓時(shí)間的總和荒揣。

// share/gc/g1/g1MMUTracker.cpp
 62 double G1MMUTrackerQueue::calculate_gc_time(double current_time) {
 63   double gc_time = 0.0;
 64   double limit = current_time - _time_slice;
 65   for (int i = 0; i < _no_entries; ++i) {
 66     int index = trim_index(_tail_index + i);
 67     G1MMUTrackerQueueElem *elem = &_array[index];
 68     if (elem->end_time() > limit) {
 69       if (elem->start_time() > limit)
 70         gc_time += elem->duration();
 71       else
 72         gc_time += elem->end_time() - limit;
 73     }
 74   }
 75   return gc_time;
 76 }

套用上面 MMU 的公式,如果過去一個(gè)時(shí)間片的 gc_time 大于等于 _max_gc_time 那么就是 violate 了設(shè)定的 MMU 目標(biāo)焊刹。

在沒有設(shè)置 GCPauseIntervalMillis 的情況下,時(shí)間片為 MaxGCPauseMillis+1恳蹲,所以出現(xiàn) MMU target violated 的情況基本就是該次 GC 停頓時(shí)間超過了 MaxGCPauseMillis虐块。

通常情況下,新生代自適應(yīng)調(diào)整會(huì)保證 GC 停頓不會(huì)超過 MaxGCPauseMillis嘉蕾,如果出現(xiàn) MMU target violated 這行日志一般就是容器環(huán)境有波動(dòng)贺奠,或者是之前的波動(dòng)影響了預(yù)測(cè)的準(zhǔn)確性。

2. 新生代自適應(yīng)調(diào)整

[info ][gc,heap       ] GC(50870) Eden regions: 53->0(24)
[info ][gc,heap       ] GC(50870) Survivor regions: 1->1(7)

從這兩行日志可以辨別新生代的調(diào)整错忱,先看一下這些數(shù)字的含義儡率,該日志是在 G1HeapTransition::print() 方法中打印的挂据。

 81 void G1HeapTransition::print() {
 82   Data after(_g1_heap);
 83
 84   size_t eden_capacity_length_after_gc = _g1_heap->g1_policy()->young_list_target_length() - after._survivor_length;
 85   size_t survivor_capacity_length_after_gc = _g1_heap->g1_policy()->max_survivor_regions();
 86
......
100
101   log_info(gc, heap)("Eden regions: " SIZE_FORMAT "->" SIZE_FORMAT "("  SIZE_FORMAT ")",
102                      _before._eden_length, after._eden_length, eden_capacity_length_after_gc);
103   log_trace(gc, heap)(" Used: 0K, Waste: 0K");
104
105   log_info(gc, heap)("Survivor regions: " SIZE_FORMAT "->" SIZE_FORMAT "("  SIZE_FORMAT ")",
106                      _before._survivor_length, after._survivor_length, survivor_capacity_length_after_gc);
......
121 }

Eden regions: GC 前 eden 大小 -> GC 后 eden 大小 (新設(shè)定的新生代大小 - GC 后 survivor 大小)
Survivor regions: GC 前 survivor 大小 -> GC 后 survivor 大小 (新設(shè)定的 survivor 最大值)

G1 的配置和策略基本都封裝在 G1Policy 這個(gè)類中,其中的 _young_list_target_length 變量表示新生代的大小儿普,該變量更新的邏輯在 G1Policy::update_young_list_target_length 方法中崎逃。

自適應(yīng)調(diào)整新生代的地方比較多:

--> void G1Policy::init(G1CollectedHeap* g1h, G1CollectionSet* collection_set)
--> void G1Policy::record_full_collection_end()
--> void G1Policy::record_collection_pause_end(double pause_time_ms, size_t cards_scanned, size_t heap_used_bytes_before_gc)
  --> uint G1Policy::update_young_list_max_and_target_length()
    --> uint G1Policy::update_young_list_max_and_target_length(size_t rs_lengths)
      --> uint G1Policy::update_young_list_target_length(size_t rs_lengths)
--> void G1YoungRemSetSamplingThread::run_service()
  --> void G1YoungRemSetSamplingThread::sample_young_list_rs_lengths()
    --> void G1Policy::revise_young_list_target_length_if_necessary(size_t rs_lengths)
      --> uint G1Policy::update_young_list_max_and_target_length(size_t rs_lengths)
        --> uint G1Policy::update_young_list_target_length(size_t rs_lengths)

總結(jié)一下,可以分三類:

  1. 初始化
    嚴(yán)格說這不算調(diào)整眉孩,只是計(jì)算個(gè)初始值
  2. GC 結(jié)束后
    a. 處于 in_young_only_phase (也就說 Pause Young (Prepare Mixed) 結(jié)束后不調(diào)整)
    b. FullGC 結(jié)束后
  3. G1YoungRemSetSamplingThread 并發(fā)調(diào)整
    處于 in_young_only_phase个绍,在 GC 停頓之間嘗試調(diào)整

這個(gè) in_young_only_phase 看圖理解比較清楚:

該圖來源于官方文檔 HotSpot Virtual Machine Garbage Collection Tuning Guide

2.1. rs_length

在看具體邏輯之前,先了解一下傳入的參數(shù) rs_length

2.1.1. update_young_list_max_and_target_length

G1Policy::update_young_list_max_and_target_length() 方法中是通過 _analytics->predict_rs_lengths() 獲取的:

// share/gc/g1/g1Analytics.cpp
313 size_t G1Analytics::predict_rs_lengths() const {
314   return get_new_size_prediction(_rs_lengths_seq);
315 }

這個(gè)預(yù)測(cè)的方法在 G1 中是通用的浪汪,具體邏輯在 g1Predictions.hpp 中巴柿,簡(jiǎn)而言之就是通過計(jì)算歷史數(shù)據(jù)的均值和標(biāo)準(zhǔn)差預(yù)測(cè),另外這里多了一個(gè)時(shí)間衰減死遭。

_rs_lengths_seq 這個(gè)隊(duì)列的長(zhǎng)度為 TruncatedSeqLength = 10广恢,存儲(chǔ)的是 Young GC 時(shí) RSet 的長(zhǎng)度。

// share/gc/g1/g1Policy.cpp
 556 void G1Policy::record_collection_pause_end(double pause_time_ms, size_t cards_scanned, size_t heap_used_bytes_before_gc) {
......
 689     // Do not update RS lengths and the number of pending cards with information from mixed gc:
 690     // these are is wildly different to during young only gc and mess up young gen sizing right
 691     // after the mixed gc phase.
 692     // During mixed gc we do not use them for young gen sizing.
 693     if (this_pause_was_young_only) {
 694       _analytics->report_pending_cards((double) _pending_cards);
 695       _analytics->report_rs_lengths((double) _max_rs_lengths);
 696     }
......
 710   size_t last_unrestrained_young_length = update_young_list_max_and_target_length();
 711   update_rs_lengths_prediction();
......
 738 }

計(jì)算 _max_rs_lengths 的邏輯在 G1FreeCollectionSetTask 中呀潭,也就是 G1CollectedHeap::free_collection_set 階段钉迷。

// share/gc/g1/g1CollectedHeap.cpp
4302   void do_parallel_work_for_region(uint region_idx, bool is_young, bool evacuation_failed) {
4303     G1CollectedHeap* g1h = G1CollectedHeap::heap();
4304
4305     HeapRegion* r = g1h->region_at(region_idx);
4306     assert(!g1h->is_on_master_free_list(r), "sanity");
4307
4308     Atomic::add(r->rem_set()->occupied_locked(), &_rs_lengths);
4309
4310     if (!is_young) {
4311       g1h->_hot_card_cache->reset_card_counts(r);
4312     }
4313
4314     if (!evacuation_failed) {
4315       r->rem_set()->clear_locked();
4316     }
4317   }

累加 CSet 的 RSet 中的 card 數(shù)量。

(G1 這個(gè) PointIn 方式的 RSet 實(shí)現(xiàn)較復(fù)雜蜗侈,大致看了一下用了三層數(shù)據(jù)結(jié)構(gòu)篷牌,這里也先忽略細(xì)節(jié)了)

2.1.2. sample_young_list_rs_lengths

G1YoungRemSetSamplingThread::sample_young_list_rs_lengths() 方法中是通過 G1YoungRemSetSamplingClosurecl.sampled_rs_lengths() 獲取的。

// share/gc/g1/g1YoungRemSetSamplingThread.cpp
106 void G1YoungRemSetSamplingThread::sample_young_list_rs_lengths() {
107   SuspendibleThreadSetJoiner sts;
108   G1CollectedHeap* g1h = G1CollectedHeap::heap();
109   G1Policy* g1p = g1h->g1_policy();
110
111   if (g1p->adaptive_young_list_length()) {
112     G1YoungRemSetSamplingClosure cl(&sts);
113
114     G1CollectionSet* g1cs = g1h->collection_set();
115     g1cs->iterate(&cl);
116
117     if (cl.is_complete()) {
118       g1p->revise_young_list_target_length_if_necessary(cl.sampled_rs_lengths());
119     }
120   }
121 }

G1YoungRemSetSamplingThread 是一個(gè)并發(fā)執(zhí)行的線程踏幻,在 GC 停頓之間循環(huán)執(zhí)行枷颊,迭代統(tǒng)計(jì) CSet 當(dāng)前的 RSet 長(zhǎng)度。

// share/gc/g1/g1Policy.cpp
 392 void G1Policy::revise_young_list_target_length_if_necessary(size_t rs_lengths) {
 393   guarantee( adaptive_young_list_length(), "should not call this otherwise" );
 394
 395   if (rs_lengths > _rs_lengths_prediction) {
 396     // add 10% to avoid having to recalculate often
 397     size_t rs_lengths_prediction = rs_lengths * 1100 / 1000;
 398     update_rs_lengths_prediction(rs_lengths_prediction);
 399
 400     update_young_list_max_and_target_length(rs_lengths_prediction);
 401   }
 402 }

如果當(dāng)前 CSet 的 RSet 長(zhǎng)度大于上次的預(yù)測(cè)值该面,那么就重新調(diào)整新生代大小夭苗。

這里的邏輯是,因?yàn)樘幚?RSet 的相關(guān)邏輯比較耗時(shí)隔缀,所以如果上次預(yù)測(cè)的 RSet 長(zhǎng)度小了题造,那么下次 GC 耗時(shí)很可能超過 MaxGCPauseMillis,因此立即再調(diào)整一下猾瘸。

2.2 adaptive_young_list_length

判斷是否開啟自適應(yīng)策略的方法是 G1Policy::adaptive_young_list_length()界赔,其中的標(biāo)識(shí)變量是在 G1YoungGenSizer 初始化時(shí)賦值的。

// share/gc/g1/g1YoungGenSizer.cpp
 30 G1YoungGenSizer::G1YoungGenSizer() : _sizer_kind(SizerDefaults), _adaptive_size(true),
 31         _min_desired_young_length(0), _max_desired_young_length(0) {
 32   if (FLAG_IS_CMDLINE(NewRatio)) {
 33     if (FLAG_IS_CMDLINE(NewSize) || FLAG_IS_CMDLINE(MaxNewSize)) {
 34       log_warning(gc, ergo)("-XX:NewSize and -XX:MaxNewSize override -XX:NewRatio");
 35     } else {
 36       _sizer_kind = SizerNewRatio;
 37       _adaptive_size = false;
 38       return;
 39     }
 40   }
......
 51   if (FLAG_IS_CMDLINE(NewSize)) {
 52     _min_desired_young_length = MAX2((uint) (NewSize / HeapRegion::GrainBytes),
 53                                      1U);
 54     if (FLAG_IS_CMDLINE(MaxNewSize)) {
 55       _max_desired_young_length =
 56                              MAX2((uint) (MaxNewSize / HeapRegion::GrainBytes),
 57                                   1U);
 58       _sizer_kind = SizerMaxAndNewSize;
 59       _adaptive_size = _min_desired_young_length != _max_desired_young_length;
 60     } else {
 61       _sizer_kind = SizerNewSizeOnly;
 62     }
......
 69 }

默認(rèn)開啟自適應(yīng)策略牵触,但是有兩種情況會(huì)關(guān)閉:

  1. 設(shè)置了 NewRatio(但是未設(shè)置 NewSize 和 MaxNewSize)
  2. 設(shè)置了 NewSize 和 MaxNewSize淮悼,并且數(shù)值一致(也就是設(shè)置 Xmn)

2.3 新生代大小自適應(yīng)算法

// share/gc/g1/g1Policy.cpp
 213 G1Policy::YoungTargetLengths G1Policy::young_list_target_lengths(size_t rs_lengths) const {
 214   YoungTargetLengths result;
 215
 216   // Calculate the absolute and desired min bounds first.
 217
 218   // This is how many young regions we already have (currently: the survivors).
 219   const uint base_min_length = _g1h->survivor_regions_count();
 220   uint desired_min_length = calculate_young_list_desired_min_length(base_min_length);
 221   // This is the absolute minimum young length. Ensure that we
 222   // will at least have one eden region available for allocation.
 223   uint absolute_min_length = base_min_length + MAX2(_g1h->eden_regions_count(), (uint)1);
 224   // If we shrank the young list target it should not shrink below the current size.
 225   desired_min_length = MAX2(desired_min_length, absolute_min_length);
 226   // Calculate the absolute and desired max bounds.
 227
 228   uint desired_max_length = calculate_young_list_desired_max_length();
 229
 230   uint young_list_target_length = 0;
 231   if (adaptive_young_list_length()) {
 232     if (collector_state()->in_young_only_phase()) {
 233       young_list_target_length =
 234                         calculate_young_list_target_length(rs_lengths,
 235                                                            base_min_length,
 236                                                            desired_min_length,
 237                                                            desired_max_length);
 238     } else {
 239       // Don't calculate anything and let the code below bound it to
 240       // the desired_min_length, i.e., do the next GC as soon as
 241       // possible to maximize how many old regions we can add to it.
 242     }
 243   } else {
 244     // The user asked for a fixed young gen so we'll fix the young gen
 245     // whether the next GC is young or mixed.
 246     young_list_target_length = _young_list_fixed_length;
 247   }
 248
 249   result.second = young_list_target_length;
 250
 251   // We will try our best not to "eat" into the reserve.
 252   uint absolute_max_length = 0;
 253   if (_free_regions_at_end_of_collection > _reserve_regions) {
 254     absolute_max_length = _free_regions_at_end_of_collection - _reserve_regions;
 255   }
 256   if (desired_max_length > absolute_max_length) {
 257     desired_max_length = absolute_max_length;
 258   }
 259
 260   // Make sure we don't go over the desired max length, nor under the
 261   // desired min length. In case they clash, desired_min_length wins
 262   // which is why that test is second.
 263   if (young_list_target_length > desired_max_length) {
 264     young_list_target_length = desired_max_length;
 265   }
 266   if (young_list_target_length < desired_min_length) {
 267     young_list_target_length = desired_min_length;
 268   }
 269
 270   assert(young_list_target_length > base_min_length,
 271          "we should be able to allocate at least one eden region");
 272   assert(young_list_target_length >= absolute_min_length, "post-condition");
 273
 274   result.first = young_list_target_length;
 275   return result;
 276 }

該方法返回的 result 包含兩個(gè)值,其中 result.first 是新生代調(diào)整后的大小揽思,在 result.second 的基礎(chǔ)上又增加了最大最小閾值限制袜腥。

result.second 用于自適應(yīng) IHOP 計(jì)算,這部分邏輯也挺復(fù)雜钉汗,之后再單獨(dú)開一篇)

自適應(yīng)算法在 calculate_young_list_target_length 方法中羹令,先了解一下參數(shù):

  1. rs_lengths 上面已經(jīng)解釋鲤屡。
  2. base_min_length 當(dāng)前 survivor 的大小,設(shè)置的新生代大小不能小于本次 GC 后的 survivor福侈。
  3. desired_min_length 這個(gè)下限值的計(jì)算用到了上面介紹的 MMU酒来,下面再詳細(xì)看。absolute_min_length 保證至少有一個(gè)可分配的 eden region癌刽。
  4. desired_max_length 新生代的上限值役首,在未配置的情況下為堆大小的 60% (其余情況暫且忽略)。
// share/gc/g1/g1Policy.cpp
 173 uint G1Policy::calculate_young_list_desired_min_length(uint base_min_length) const {
 174   uint desired_min_length = 0;
 175   if (adaptive_young_list_length()) {
 176     if (_analytics->num_alloc_rate_ms() > 3) {
 177       double now_sec = os::elapsedTime();
 178       double when_ms = _mmu_tracker->when_max_gc_sec(now_sec) * 1000.0;
 179       double alloc_rate_ms = _analytics->predict_alloc_rate_ms();
 180       desired_min_length = (uint) ceil(alloc_rate_ms * when_ms);
 181     } else {
 182       // otherwise we don't have enough info to make the prediction
 183     }
 184   }
 185   desired_min_length += base_min_length;
 186   // make sure we don't go below any user-defined minimum bound
 187   return MAX2(_young_gen_sizer.min_desired_young_length(), desired_min_length);
 188 }

這里的 _alloc_rate_ms_seq_rs_lengths_seq 一樣显拜,用于預(yù)測(cè)分配速率衡奥,在 GC 回收之后記錄歷史數(shù)據(jù)。

alloc_rate_ms 是預(yù)測(cè)的分配速率远荠,when_ms 是滿足 MMU 目標(biāo)的最短非停頓時(shí)間矮固,兩者的乘積就表示:要實(shí)現(xiàn) MMU 的目標(biāo),至少需要給出這么多 region 用于接下來的內(nèi)存分配譬淳。在 GCPauseIntervalMillis 默認(rèn)值的情況下档址,這個(gè)值應(yīng)該變化不大,與分配速率成正比邻梆。

_young_gen_sizer.min_desired_young_length() 在未配置的情況下為堆大小的 5%(其余情況暫且忽略)守伸。

接下來就進(jìn)入最復(fù)雜的 calculate_young_list_target_length 方法:

// share/gc/g1/g1Policy.cpp
 278 uint
 279 G1Policy::calculate_young_list_target_length(size_t rs_lengths,
 280                                                     uint base_min_length,
 281                                                     uint desired_min_length,
 282                                                     uint desired_max_length) const {
 283   assert(adaptive_young_list_length(), "pre-condition");
 284   assert(collector_state()->in_young_only_phase(), "only call this for young GCs");
 285
 286   // In case some edge-condition makes the desired max length too small...
 287   if (desired_max_length <= desired_min_length) {
 288     return desired_min_length;
 289   }
 290
 291   // We'll adjust min_young_length and max_young_length not to include
 292   // the already allocated young regions (i.e., so they reflect the
 293   // min and max eden regions we'll allocate). The base_min_length
 294   // will be reflected in the predictions by the
 295   // survivor_regions_evac_time prediction.
 296   assert(desired_min_length > base_min_length, "invariant");
 297   uint min_young_length = desired_min_length - base_min_length;
 298   assert(desired_max_length > base_min_length, "invariant");
 299   uint max_young_length = desired_max_length - base_min_length;
 300
 301   const double target_pause_time_ms = _mmu_tracker->max_gc_time() * 1000.0;
 302   const double survivor_regions_evac_time = predict_survivor_regions_evac_time();
 303   const size_t pending_cards = _analytics->predict_pending_cards();
 304   const size_t adj_rs_lengths = rs_lengths + _analytics->predict_rs_length_diff();
 305   const size_t scanned_cards = _analytics->predict_card_num(adj_rs_lengths, true /* for_young_gc */);
 306   const double base_time_ms =
 307     predict_base_elapsed_time_ms(pending_cards, scanned_cards) +
 308     survivor_regions_evac_time;
 309   const uint available_free_regions = _free_regions_at_end_of_collection;
 310   const uint base_free_regions =
 311     available_free_regions > _reserve_regions ? available_free_regions - _reserve_regions : 0;
 312
 313   // Here, we will make sure that the shortest young length that
 314   // makes sense fits within the target pause time.
 315
 316   G1YoungLengthPredictor p(collector_state()->mark_or_rebuild_in_progress(),
 317                            base_time_ms,
 318                            base_free_regions,
 319                            target_pause_time_ms,
 320                            this);
 321   if (p.will_fit(min_young_length)) {
 322     // The shortest young length will fit into the target pause time;
 323     // we'll now check whether the absolute maximum number of young
 324     // regions will fit in the target pause time. If not, we'll do
 325     // a binary search between min_young_length and max_young_length.
 326     if (p.will_fit(max_young_length)) {
 327       // The maximum young length will fit into the target pause time.
 328       // We are done so set min young length to the maximum length (as
 329       // the result is assumed to be returned in min_young_length).
 330       min_young_length = max_young_length;
 331     } else {
 332       // The maximum possible number of young regions will not fit within
 333       // the target pause time so we'll search for the optimal
 334       // length. The loop invariants are:
 335       //
 336       // min_young_length < max_young_length
 337       // min_young_length is known to fit into the target pause time
 338       // max_young_length is known not to fit into the target pause time
 339       //
 340       // Going into the loop we know the above hold as we've just
 341       // checked them. Every time around the loop we check whether
 342       // the middle value between min_young_length and
 343       // max_young_length fits into the target pause time. If it
 344       // does, it becomes the new min. If it doesn't, it becomes
 345       // the new max. This way we maintain the loop invariants.
 346
 347       assert(min_young_length < max_young_length, "invariant");
 348       uint diff = (max_young_length - min_young_length) / 2;
 349       while (diff > 0) {
 350         uint young_length = min_young_length + diff;
 351         if (p.will_fit(young_length)) {
 352           min_young_length = young_length;
 353         } else {
 354           max_young_length = young_length;
 355         }
 356         assert(min_young_length <  max_young_length, "invariant");
 357         diff = (max_young_length - min_young_length) / 2;
 358       }
 359       // The results is min_young_length which, according to the
 360       // loop invariants, should fit within the target pause time.
 361
 362       // These are the post-conditions of the binary search above:
 363       assert(min_young_length < max_young_length,
 364              "otherwise we should have discovered that max_young_length "
 365              "fits into the pause target and not done the binary search");
 366       assert(p.will_fit(min_young_length),
 367              "min_young_length, the result of the binary search, should "
 368              "fit into the pause target");
 369       assert(!p.will_fit(min_young_length + 1),
 370              "min_young_length, the result of the binary search, should be "
 371              "optimal, so no larger length should fit into the pause target");
 372     }
 373   } else {
 374     // Even the minimum length doesn't fit into the pause time
 375     // target, return it as the result nevertheless.
 376   }
 377   return base_min_length + min_young_length;
 378 }

前半部分也有很多預(yù)測(cè)的代碼,原理與 _alloc_rate_ms_seq 一樣浦妄。

base_time_ms 基本耗時(shí)包含兩部分:

  1. L302 survivor_regions_evac_time 預(yù)測(cè) survivor evac 的時(shí)間
    a. 預(yù)測(cè) rs 掃描的時(shí)間
    b. 預(yù)測(cè)對(duì)象拷貝的時(shí)間
    c. 預(yù)測(cè) other 時(shí)間
  2. L307 處理 cards 的時(shí)間(基于前面的 rs_lengths預(yù)測(cè))
    a. 預(yù)測(cè)處理 pending_cards 更新 rs 的時(shí)間
    b. 預(yù)測(cè)掃描 rs 的時(shí)間
    c. 預(yù)測(cè) other 時(shí)間

base_free_regions 是扣除 _reserve_regions 之外的當(dāng)前可用空間尼摹。

到此先簡(jiǎn)單總結(jié)一下:

  1. min_young_length 是滿足 MMU 目標(biāo)需要分配的最少內(nèi)存
  2. max_young_length 是設(shè)置的最大新生代限制
  3. 預(yù)測(cè)下一次 GC 的基本耗時(shí) base_time_ms
  4. 當(dāng)前可用的空閑空間 base_free_regions
  5. 接下來就是綜合以上信息從 [min, max] 中找到一個(gè)滿足 MaxGCPauseMillis 的最大值

判斷是否滿足條件的邏輯封裝在 G1YoungLengthPredictorwill_fit 方法中:

// share/gc/g1/g1Policy.cpp
 121   bool will_fit(uint young_length) const {
 122     if (young_length >= _base_free_regions) {
 123       // end condition 1: not enough space for the young regions
 124       return false;
 125     }
 126
 127     const double accum_surv_rate = _policy->accum_yg_surv_rate_pred((int) young_length - 1);
 128     const size_t bytes_to_copy =
 129                  (size_t) (accum_surv_rate * (double) HeapRegion::GrainBytes);
 130     const double copy_time_ms =
 131       _policy->analytics()->predict_object_copy_time_ms(bytes_to_copy, _during_cm);
 132     const double young_other_time_ms = _policy->analytics()->predict_young_other_time_ms(young_length);
 133     const double pause_time_ms = _base_time_ms + copy_time_ms + young_other_time_ms;
 134     if (pause_time_ms > _target_pause_time_ms) {
 135       // end condition 2: prediction is over the target pause time
 136       return false;
 137     }
 138
 139     const size_t free_bytes = (_base_free_regions - young_length) * HeapRegion::GrainBytes;
 140
 141     // When copying, we will likely need more bytes free than is live in the region.
 142     // Add some safety margin to factor in the confidence of our guess, and the
 143     // natural expected waste.
 144     // (100.0 / G1ConfidencePercent) is a scale factor that expresses the uncertainty
 145     // of the calculation: the lower the confidence, the more headroom.
 146     // (100 + TargetPLABWastePct) represents the increase in expected bytes during
 147     // copying due to anticipated waste in the PLABs.
 148     const double safety_factor = (100.0 / G1ConfidencePercent) * (100 + TargetPLABWastePct) / 100.0;
 149     const size_t expected_bytes_to_copy = (size_t)(safety_factor * bytes_to_copy);
 150
 151     if (expected_bytes_to_copy > free_bytes) {
 152       // end condition 3: out-of-space
 153       return false;
 154     }
 155
 156     // success!
 157     return true;
 158   }

依次檢查三個(gè)條件:

  1. 超過了當(dāng)前最大可用空間
  2. 如果預(yù)估的停頓時(shí)間大于 MaxGCPauseMillis。此處預(yù)估 pause_time_ms 在基本耗時(shí)的基礎(chǔ)上加上了下次 GC 時(shí) eden 區(qū)的處理時(shí)間剂娄,給定的新生代越大蠢涝,預(yù)估耗時(shí)也會(huì)越大。
  3. 為了避免 To-space exhausted 的情況阅懦,如果 expected_bytes_to_copy 大于剩余可用空間那么也不合適和二。這里對(duì)預(yù)估的 bytes_to_copy 還乘了個(gè)安全系數(shù)。TargetPLABWastePct 默認(rèn)值 10耳胎,G1ConfidencePercent 默認(rèn)值 50惯吕,該系數(shù)默認(rèn)為 2.2。

以上怕午,就是 G1 新生代自適應(yīng)的主干邏輯混埠。

3. 最后

最后還有一個(gè)小問題:G1Policy::init(G1CollectedHeap* g1h, G1CollectionSet* collection_set) 時(shí),新生代設(shè)置的大小是多少呢诗轻?

根據(jù) GC 日志來看,新生代初始值為 min_young_length揭北,也就是默認(rèn)情況下堆大小的 5%扳炬。 但是根據(jù)上面的邏輯算下來吏颖,如果初始的預(yù)測(cè)值都是 0 的話,那么新生代初始值應(yīng)該是 max_young_length恨樟,其中的原因是有些預(yù)測(cè)值有默認(rèn)初始值半醉,比如 _constant_other_time_ms_seq_young_other_cost_per_region_ms_seq劝术、 _cost_per_byte_ms_seq缩多、 _short_lived_surv_rate_group等,大多數(shù)情況下只有最小值合適养晋。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末衬吆,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子绳泉,更是在濱河造成了極大的恐慌逊抡,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件零酪,死亡現(xiàn)場(chǎng)離奇詭異冒嫡,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)四苇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門孝凌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人月腋,你說我怎么就攤上這事蟀架。” “怎么了罗售?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵辜窑,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我寨躁,道長(zhǎng)穆碎,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任职恳,我火速辦了婚禮所禀,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘放钦。我一直安慰自己色徘,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布操禀。 她就那樣靜靜地躺著褂策,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上斤寂,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天耿焊,我揣著相機(jī)與錄音,去河邊找鬼遍搞。 笑死罗侯,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的溪猿。 我是一名探鬼主播钩杰,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼诊县!你這毒婦竟也來了讲弄?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤翎冲,失蹤者是張志新(化名)和其女友劉穎垂睬,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體抗悍,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡驹饺,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了缴渊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片赏壹。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖衔沼,靈堂內(nèi)的尸體忽然破棺而出蝌借,到底是詐尸還是另有隱情,我是刑警寧澤指蚁,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布菩佑,位于F島的核電站,受9級(jí)特大地震影響凝化,放射性物質(zhì)發(fā)生泄漏稍坯。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一搓劫、第九天 我趴在偏房一處隱蔽的房頂上張望瞧哟。 院中可真熱鬧,春花似錦枪向、人聲如沸勤揩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)陨亡。三九已至傍衡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間数苫,已是汗流浹背权纤。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來泰國(guó)打工祸憋, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人狞膘。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓滔迈,卻偏偏與公主長(zhǎng)得像止吁,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子燎悍,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容