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)整策略栅哀。
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 }
-
start
和end
分別表示該次 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é)一下,可以分三類:
- 初始化
嚴(yán)格說這不算調(diào)整眉孩,只是計(jì)算個(gè)初始值 - GC 結(jié)束后
a. 處于in_young_only_phase
(也就說 Pause Young (Prepare Mixed) 結(jié)束后不調(diào)整)
b. FullGC 結(jié)束后 - 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()
方法中是通過 G1YoungRemSetSamplingClosure
的 cl.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)閉:
- 設(shè)置了 NewRatio(但是未設(shè)置 NewSize 和 MaxNewSize)
- 設(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ù):
-
rs_lengths
上面已經(jīng)解釋鲤屡。 -
base_min_length
當(dāng)前 survivor 的大小,設(shè)置的新生代大小不能小于本次 GC 后的 survivor福侈。 -
desired_min_length
這個(gè)下限值的計(jì)算用到了上面介紹的 MMU酒来,下面再詳細(xì)看。absolute_min_length
保證至少有一個(gè)可分配的 eden region癌刽。 -
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í)包含兩部分:
- 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í)間 - 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é)一下:
-
min_young_length
是滿足 MMU 目標(biāo)需要分配的最少內(nèi)存 -
max_young_length
是設(shè)置的最大新生代限制 - 預(yù)測(cè)下一次 GC 的基本耗時(shí)
base_time_ms
- 當(dāng)前可用的空閑空間
base_free_regions
- 接下來就是綜合以上信息從 [min, max] 中找到一個(gè)滿足 MaxGCPauseMillis 的最大值
判斷是否滿足條件的邏輯封裝在 G1YoungLengthPredictor
的 will_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è)條件:
- 超過了當(dāng)前最大可用空間
- 如果預(yù)估的停頓時(shí)間大于 MaxGCPauseMillis。此處預(yù)估
pause_time_ms
在基本耗時(shí)的基礎(chǔ)上加上了下次 GC 時(shí) eden 區(qū)的處理時(shí)間剂娄,給定的新生代越大蠢涝,預(yù)估耗時(shí)也會(huì)越大。 - 為了避免 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ù)情況下只有最小值合適养晋。