jdk/src/hotspot/share/gc/g1/g1IHOPControl.cpp
Ivan Walulya 85db081db4 8379846: G1: IHOP Allocation rate calculation too sensitive to outliers causing endless concurrent cycles
Co-authored-by: Thomas Schatzl <tschatzl@openjdk.org>
Reviewed-by: tschatzl, aboldtch
2026-06-03 14:04:11 +00:00

214 lines
10 KiB
C++

/*
* Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*
*/
#include "gc/g1/g1CollectedHeap.inline.hpp"
#include "gc/g1/g1IHOPControl.hpp"
#include "gc/g1/g1Predictions.hpp"
#include "gc/g1/g1Trace.hpp"
#include "logging/log.hpp"
double G1IHOPControl::predict(const TruncatedSeq* seq) const {
assert(_is_adaptive, "precondition");
assert(_predictor != nullptr, "precondition");
return _predictor->predict_zero_bounded(seq);
}
bool G1IHOPControl::have_enough_data_for_prediction() const {
assert(_is_adaptive, "precondition");
return ((size_t)_marking_start_to_mixed_time_s.num() >= G1AdaptiveIHOPNumInitialSamples) &&
((size_t)_old_non_humongous_alloc_rate.num() >= G1AdaptiveIHOPNumInitialSamples);
}
size_t G1IHOPControl::effective_target_occupancy() const {
assert(_is_adaptive, "precondition");
// The effective target occupancy takes the heap reserve and the expected waste in
// free space into account.
// _heap_reserve is that part of the total heap capacity that is reserved for
// eventual promotion failure.
// _heap_waste is the amount of space will never be reclaimed in any
// heap, so can not be used for allocation during marking and must always be
// considered.
double safe_total_heap_percentage =
MIN2((double)(_heap_reserve_percent + _heap_waste_percent), 100.0);
return (size_t)MIN2(
G1CollectedHeap::heap()->max_capacity() * (100.0 - safe_total_heap_percentage) / 100.0,
_target_occupancy * (100.0 - _heap_waste_percent) / 100.0
);
}
G1IHOPControl::G1IHOPControl(double ihop_percent,
bool adaptive,
const G1Predictions* predictor,
size_t heap_reserve_percent,
size_t heap_waste_percent)
: _is_adaptive(adaptive),
_initial_ihop_percent(ihop_percent),
_target_occupancy(0),
_heap_reserve_percent(heap_reserve_percent),
_heap_waste_percent(heap_waste_percent),
_predictor(predictor),
_marking_start_to_mixed_time_s(10, 0.05),
_old_non_humongous_alloc_rate(10, 0.05),
_peak_extra_humongous_occupancy_in_mark_cycle(10, 0.05),
_expected_young_gen_at_first_mixed_gc(0) {
assert(_initial_ihop_percent >= 0.0 && _initial_ihop_percent <= 100.0,
"IHOP percent out of range: %.3f", ihop_percent);
assert(!_is_adaptive || _predictor != nullptr, "precondition");
}
void G1IHOPControl::update_target_occupancy(size_t new_target_occupancy) {
log_debug(gc, ihop)("Target occupancy update: old: %zuB, new: %zuB",
_target_occupancy, new_target_occupancy);
_target_occupancy = new_target_occupancy;
}
void G1IHOPControl::report_statistics(G1NewTracer* new_tracer,
size_t non_young_occupancy,
size_t non_humongous_allocation,
size_t peak_extra_humongous_occupancy) {
print_log(non_young_occupancy, non_humongous_allocation, peak_extra_humongous_occupancy);
send_trace_event(new_tracer, non_young_occupancy,
non_humongous_allocation, peak_extra_humongous_occupancy);
}
void G1IHOPControl::record_expected_young_gen_size(size_t expected_young_gen_size) {
_expected_young_gen_at_first_mixed_gc = expected_young_gen_size;
}
void G1IHOPControl::record_concurrent_cycle(double marking_start_to_mixed_time_s,
size_t non_humongous_bytes,
size_t peak_extra_humongous_occupancy_bytes) {
assert(marking_start_to_mixed_time_s > 0.0, "Invalid concurrent cycle duration: %.3f", marking_start_to_mixed_time_s);
double non_humongous_rate = non_humongous_bytes / marking_start_to_mixed_time_s;
_marking_start_to_mixed_time_s.add(marking_start_to_mixed_time_s);
_old_non_humongous_alloc_rate.add(non_humongous_rate);
_peak_extra_humongous_occupancy_in_mark_cycle.add(peak_extra_humongous_occupancy_bytes);
}
// Determine the old generation occupancy threshold at which to start
// concurrent marking such that reclamation (first Mixed GC) begins
// before the heap reaches a critical occupancy level.
size_t G1IHOPControl::old_gen_threshold_for_conc_mark_start() const {
guarantee(_target_occupancy > 0, "Target occupancy must be initialized");
if (!_is_adaptive || !have_enough_data_for_prediction()) {
return (size_t)(_initial_ihop_percent * _target_occupancy / 100.0);
}
// Between Concurrent Start GC and the first Mixed GC (i.e. concurrent cycle),
// we expect extra heap occupancy from three sources:
// - non-humongous allocations into the old-gen
// - peak extra humongous occupancy during the cycle, relative to the humongous occupancy
// at the end of the Concurrent Start GC.
// - we also wish to maintain the current desired young generation until the first Mixed-gc;
// promotions into the old gen should not shrink the young gen and degrade performance.
//
// We therefore start marking early enough such that:
//
// old_gen_at_concurrent_start +
// predicted_non_hum_old_growth +
// predicted_peak_extra_humongous_occupancy +
// expected_young_gen_at_first_mixed_gc
//
// stays below the effective target occupancy.
double marking_start_to_mixed_time = predict(&_marking_start_to_mixed_time_s);
double old_non_humongous_alloc_rate = predict(&_old_non_humongous_alloc_rate);
size_t old_non_humongous_alloc_bytes = (size_t)(marking_start_to_mixed_time * old_non_humongous_alloc_rate);
size_t predicted_peak_extra_humongous_occupancy =
predict(&_peak_extra_humongous_occupancy_in_mark_cycle);
size_t reserve_for_young_regions = _expected_young_gen_at_first_mixed_gc;
size_t target_heap_occupancy = effective_target_occupancy();
size_t needed_for_concurrent_cycle = reserve_for_young_regions +
old_non_humongous_alloc_bytes +
predicted_peak_extra_humongous_occupancy;
size_t threshold = needed_for_concurrent_cycle < target_heap_occupancy ?
target_heap_occupancy - needed_for_concurrent_cycle : 0;
return threshold;
}
void G1IHOPControl::print_log(size_t non_young_occupancy,
size_t non_humongous_allocation,
size_t peak_extra_humongous_occupancy) {
assert(_target_occupancy > 0, "Target occupancy still not updated yet.");
size_t old_gen_mark_start_threshold = old_gen_threshold_for_conc_mark_start();
log_debug(gc, ihop)("Basic information (value update), old-gen threshold: %zuB (%1.2f%%), target occupancy: %zuB, old-gen occupancy: %zuB (%1.2f%%)",
old_gen_mark_start_threshold,
percent_of(old_gen_mark_start_threshold, _target_occupancy),
_target_occupancy,
non_young_occupancy,
percent_of(non_young_occupancy, _target_occupancy));
if (!_is_adaptive || !have_enough_data_for_prediction()) {
return;
}
size_t effective_target = effective_target_occupancy();
log_debug(gc, ihop)("Adaptive IHOP information (value update), old-gen threshold: %zuB (%1.2f%%), internal target occupancy: %zuB, "
"old-gen occupancy: %zuB (%1.2f%%), additional buffer size: %zuB, "
"current non-humongous allocation: %zuB, current peak extra humongous occupancy: %zuB, "
"predicted old-gen non-humongous allocation rate: %1.2fB/s, predicted peak extra humongous occupancy: %1.2fB, "
"predicted concurrent cycle duration: %1.2fms",
old_gen_mark_start_threshold,
percent_of(old_gen_mark_start_threshold, effective_target),
effective_target,
non_young_occupancy,
percent_of(non_young_occupancy, effective_target),
_expected_young_gen_at_first_mixed_gc,
non_humongous_allocation, peak_extra_humongous_occupancy,
predict(&_old_non_humongous_alloc_rate),
predict(&_peak_extra_humongous_occupancy_in_mark_cycle),
predict(&_marking_start_to_mixed_time_s) * 1000.0);
}
void G1IHOPControl::send_trace_event(G1NewTracer* tracer,
size_t non_young_occupancy,
size_t non_humongous_allocation,
size_t peak_extra_humongous_occupancy) {
assert(_target_occupancy > 0, "Target occupancy still not updated yet.");
tracer->report_basic_ihop_statistics(old_gen_threshold_for_conc_mark_start(),
_target_occupancy,
non_young_occupancy);
if (_is_adaptive) {
tracer->report_adaptive_ihop_statistics(old_gen_threshold_for_conc_mark_start(),
effective_target_occupancy(),
non_young_occupancy,
_expected_young_gen_at_first_mixed_gc,
non_humongous_allocation,
peak_extra_humongous_occupancy,
predict(&_old_non_humongous_alloc_rate),
predict(&_peak_extra_humongous_occupancy_in_mark_cycle),
predict(&_marking_start_to_mixed_time_s),
have_enough_data_for_prediction());
}
}