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
This commit is contained in:
Ivan Walulya 2026-06-03 14:04:11 +00:00
parent d3483a98d4
commit 85db081db4
17 changed files with 1301 additions and 551 deletions

View File

@ -729,7 +729,7 @@ HeapWord* G1CollectedHeap::attempt_allocation_humongous(size_t word_size) {
result = humongous_obj_allocate(word_size);
if (result != nullptr) {
policy()->old_gen_alloc_tracker()->
add_allocated_humongous_bytes_since_last_gc(humongous_byte_size);
add_allocated_humongous_bytes(humongous_byte_size);
return result;
}
@ -2861,7 +2861,7 @@ void G1CollectedHeap::record_obj_copy_mem_stats() {
G1ReservePercent);
policy()->old_gen_alloc_tracker()->
add_allocated_bytes_since_last_gc(total_old_allocated * HeapWordSize);
add_allocated_non_humongous_bytes(total_old_allocated * HeapWordSize);
_gc_tracer_stw->report_evacuation_statistics(create_g1_evac_summary(&_survivor_evac_stats),
create_g1_evac_summary(&_old_evac_stats));

View File

@ -0,0 +1,131 @@
/*
* Copyright (c) 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/g1ConcurrentCycleTracker.hpp"
#include "gc/g1/g1OldGenAllocationTracker.hpp"
#include "utilities/checkedCast.hpp"
#include "utilities/debug.hpp"
void G1ConcurrentCycleTracker::reset() {
_state = CycleState::Inactive;
_total_pause_time_s = 0.0;
_cycle_start_time_s = 0.0;
_cycle_end_time_s = 0.0;
_humongous_bytes_at_start = 0;
_non_humongous_allocated_bytes = 0;
_peak_extra_humongous_occupancy_bytes = 0;
}
void G1ConcurrentCycleTracker::update_allocation_stats(G1AllocationIntervalStats interval_stats) {
if (!is_active()) {
return;
}
_non_humongous_allocated_bytes += interval_stats._non_humongous_allocated_bytes;
intptr_t delta_before = checked_cast<intptr_t>(interval_stats._total_humongous_before_bytes) -
checked_cast<intptr_t>(_humongous_bytes_at_start);
intptr_t delta_after = delta_before +
checked_cast<intptr_t>(interval_stats._humongous_allocated_bytes);
if (delta_after > 0) {
_peak_extra_humongous_occupancy_bytes = MAX2(_peak_extra_humongous_occupancy_bytes, checked_cast<size_t>(delta_after));
}
}
G1ConcurrentCycleTracker::G1ConcurrentCycleTracker()
: _state(CycleState::Inactive),
_cycle_start_time_s(0.0),
_cycle_end_time_s(0.0),
_total_pause_time_s(0.0),
_humongous_bytes_at_start(0),
_non_humongous_allocated_bytes(0),
_peak_extra_humongous_occupancy_bytes(0)
{ }
void G1ConcurrentCycleTracker::record_cycle_start(double cycle_start_time_s, size_t humongous_bytes_after_pause) {
assert(_state == CycleState::Inactive, "Concurrent start out of order.");
_cycle_start_time_s = cycle_start_time_s;
_humongous_bytes_at_start = humongous_bytes_after_pause;
_state = CycleState::Active;
}
void G1ConcurrentCycleTracker::record_allocation_interval(Pause pause_type,
bool is_periodic_gc,
double pause_start_time_s,
double pause_end_time_s,
G1AllocationIntervalStats interval_stats) {
if (is_periodic_gc || pause_type == Pause::Full || pause_type == Pause::ConcurrentStartUndo) {
reset();
return;
}
if (pause_type == Pause::ConcurrentStartFull) {
record_cycle_start(pause_end_time_s, interval_stats._total_humongous_after_bytes);
return;
}
if (!is_active()) {
return;
}
update_allocation_stats(interval_stats);
if (pause_type == Pause::Mixed) {
complete_cycle(pause_start_time_s);
return;
}
assert(pause_type == Pause::Normal ||
pause_type == Pause::PrepareMixed ||
pause_type == Pause::Remark ||
pause_type == Pause::Cleanup,
"Unhandled pause type");
add_pause(pause_end_time_s - pause_start_time_s);
}
void G1ConcurrentCycleTracker::complete_cycle(double cycle_end_time_s) {
precond(is_active());
_cycle_end_time_s = cycle_end_time_s;
_state = CycleState::Complete;
}
G1ConcurrentCycleStats G1ConcurrentCycleTracker::get_and_reset_cycle_stats() {
precond(has_completed_cycle());
double cycle_duration = (_cycle_end_time_s - _cycle_start_time_s - _total_pause_time_s);
G1ConcurrentCycleStats stats{
cycle_duration,
_non_humongous_allocated_bytes,
_peak_extra_humongous_occupancy_bytes
};
reset();
return stats;
}

View File

@ -0,0 +1,111 @@
/*
* Copyright (c) 2001, 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.
*
*/
#ifndef SHARE_GC_G1_G1CONCURRENTCYCLETRACKER_HPP
#define SHARE_GC_G1_G1CONCURRENTCYCLETRACKER_HPP
#include "gc/g1/g1CollectorState.hpp"
#include "utilities/globalDefinitions.hpp"
struct G1AllocationIntervalStats;
// The sampling interval for G1ConcurrentCycleTracker covers the concurrent cycle
// from the end of the Concurrent Start GC to start of the first Mixed GC.
struct G1ConcurrentCycleStats {
double _cycle_duration_s;
size_t _non_humongous_allocated_bytes;
size_t _peak_extra_humongous_occupancy_bytes;
G1ConcurrentCycleStats(double cycle_duration_s,
size_t non_humongous_allocated_bytes,
size_t peak_extra_humongous_occupancy_bytes)
: _cycle_duration_s(cycle_duration_s),
_non_humongous_allocated_bytes(non_humongous_allocated_bytes),
_peak_extra_humongous_occupancy_bytes(peak_extra_humongous_occupancy_bytes)
{ }
};
class G1ConcurrentCycleTracker {
using Pause = G1CollectorState::Pause;
enum class CycleState {
Inactive,
Active,
Complete,
};
CycleState _state;
double _cycle_start_time_s;
double _cycle_end_time_s;
double _total_pause_time_s;
// allocation accounting
size_t _humongous_bytes_at_start;
size_t _non_humongous_allocated_bytes;
size_t _peak_extra_humongous_occupancy_bytes;
void reset();
bool is_active() const {
return _state == CycleState::Active;
}
void update_allocation_stats(G1AllocationIntervalStats interval_stats);
void add_pause(double pause_duration_s) {
_total_pause_time_s += pause_duration_s;
}
void record_cycle_start(double cycle_start_time_s, size_t humongous_bytes_after_pause);
void complete_cycle(double cycle_end_time_s);
public:
G1ConcurrentCycleTracker();
void record_allocation_interval(Pause pause_type,
bool is_periodic_gc,
double pause_start_time_s,
double pause_end_time_s,
G1AllocationIntervalStats interval_stats);
void abort_cycle() {
reset();
}
bool has_completed_cycle() const {
return _state == CycleState::Complete;
}
size_t non_humongous_allocated_bytes() const {
return _non_humongous_allocated_bytes;
}
size_t peak_extra_humongous_occupancy_bytes() const {
return _peak_extra_humongous_occupancy_bytes;
}
G1ConcurrentCycleStats get_and_reset_cycle_stats();
};
#endif // SHARE_GC_G1_G1CONCURRENTCYCLETRACKER_HPP

View File

@ -1,89 +0,0 @@
/*
* Copyright (c) 2001, 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.
*
*/
#ifndef SHARE_GC_G1_G1CONCURRENTSTARTTOMIXEDTIMETRACKER_HPP
#define SHARE_GC_G1_G1CONCURRENTSTARTTOMIXEDTIMETRACKER_HPP
#include "utilities/debug.hpp"
#include "utilities/globalDefinitions.hpp"
// Used to track time from the end of concurrent start to the first mixed GC.
// After calling the concurrent start/mixed gc notifications, the result can be
// obtained in get_and_reset_last_marking_time() once, after which the tracking resets.
// Any pauses recorded by add_pause() will be subtracted from that results.
class G1ConcurrentStartToMixedTimeTracker {
private:
bool _active;
double _concurrent_start_end_time;
double _mixed_start_time;
double _total_pause_time;
double wall_time() const {
return _mixed_start_time - _concurrent_start_end_time;
}
public:
G1ConcurrentStartToMixedTimeTracker() { reset(); }
// Record concurrent start pause end, starting the time tracking.
void record_concurrent_start_end(double end_time) {
assert(!_active, "Concurrent start out of order.");
_concurrent_start_end_time = end_time;
_active = true;
}
// Record the first mixed gc pause start, ending the time tracking.
void record_mixed_gc_start(double start_time) {
if (_active) {
_mixed_start_time = start_time;
_active = false;
}
}
double get_and_reset_last_marking_time() {
assert(has_result(), "Do not have all measurements yet.");
double result = (_mixed_start_time - _concurrent_start_end_time) - _total_pause_time;
reset();
return result;
}
void reset() {
_active = false;
_total_pause_time = 0.0;
_concurrent_start_end_time = -1.0;
_mixed_start_time = -1.0;
}
void add_pause(double time) {
if (_active) {
_total_pause_time += time;
}
}
bool is_active() const { return _active; }
// Returns whether we have a result that can be retrieved.
bool has_result() const { return _mixed_start_time > 0.0 && _concurrent_start_end_time > 0.0; }
};
#endif // SHARE_GC_G1_G1CONCURRENTSTARTTOMIXEDTIMETRACKER_HPP

View File

@ -31,19 +31,14 @@
double G1IHOPControl::predict(const TruncatedSeq* seq) const {
assert(_is_adaptive, "precondition");
assert(_predictor != nullptr, "precondition");
return _predictor->predict_zero_bounded(seq);
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_gen_alloc_rate.num() >= G1AdaptiveIHOPNumInitialSamples);
}
double G1IHOPControl::last_marking_start_to_mixed_time_s() const {
return _marking_start_to_mixed_time_s.last();
((size_t)_old_non_humongous_alloc_rate.num() >= G1AdaptiveIHOPNumInitialSamples);
}
size_t G1IHOPControl::effective_target_occupancy() const {
@ -66,7 +61,6 @@ size_t G1IHOPControl::effective_target_occupancy() const {
}
G1IHOPControl::G1IHOPControl(double ihop_percent,
const G1OldGenAllocationTracker* old_gen_alloc_tracker,
bool adaptive,
const G1Predictions* predictor,
size_t heap_reserve_percent,
@ -76,11 +70,10 @@ G1IHOPControl::G1IHOPControl(double ihop_percent,
_target_occupancy(0),
_heap_reserve_percent(heap_reserve_percent),
_heap_waste_percent(heap_waste_percent),
_last_allocation_time_s(0.0),
_old_gen_alloc_tracker(old_gen_alloc_tracker),
_predictor(predictor),
_marking_start_to_mixed_time_s(10, 0.05),
_old_gen_alloc_rate(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);
@ -93,22 +86,28 @@ void G1IHOPControl::update_target_occupancy(size_t new_target_occupancy) {
_target_occupancy = new_target_occupancy;
}
void G1IHOPControl::report_statistics(G1NewTracer* new_tracer, size_t non_young_occupancy) {
print_log(non_young_occupancy);
send_trace_event(new_tracer, non_young_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::update_allocation_info(double allocation_time_s, size_t expected_young_gen_size) {
assert(allocation_time_s > 0, "Invalid allocation time: %.3f", allocation_time_s);
_last_allocation_time_s = allocation_time_s;
double alloc_rate = _old_gen_alloc_tracker->last_period_old_gen_growth() / allocation_time_s;
_old_gen_alloc_rate.add(alloc_rate);
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::add_marking_start_to_mixed_length(double length_s) {
assert(length_s >= 0.0, "Invalid marking length: %.3f", length_s);
_marking_start_to_mixed_time_s.add(length_s);
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
@ -121,80 +120,93 @@ size_t G1IHOPControl::old_gen_threshold_for_conc_mark_start() const {
return (size_t)(_initial_ihop_percent * _target_occupancy / 100.0);
}
// During the time between marking start and the first Mixed GC,
// additional memory will be consumed:
// - Old gen grows due to allocations:
// old_gen_alloc_bytes = old_gen_alloc_rate * marking_start_to_mixed_time
// - Young gen will occupy a certain size at the first Mixed GC:
// expected_young_gen_at_first_mixed_gc
double marking_start_to_mixed_time = predict(&_marking_start_to_mixed_time_s);
double old_gen_alloc_rate = predict(&_old_gen_alloc_rate);
size_t old_gen_alloc_bytes = (size_t)(marking_start_to_mixed_time * old_gen_alloc_rate);
// Therefore, the total heap occupancy at the first Mixed GC is:
// current_old_gen + old_gen_growth + expected_young_gen_at_first_mixed_gc
// 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.
//
// To ensure this does not exceed the target_heap_occupancy, we work
// backwards to compute the old gen occupancy at which marking must start:
// mark_start_threshold = target_heap_occupancy -
// (old_gen_growth + expected_young_gen_at_first_mixed_gc)
// 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_needed = old_gen_alloc_bytes + _expected_young_gen_at_first_mixed_gc;
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();
return predicted_needed < target_heap_occupancy
? target_heap_occupancy - predicted_needed
: 0;
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) {
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%%), "
"recent old-gen allocation size: %zuB, recent allocation duration: %1.2fms, recent old-gen allocation rate: %1.2fB/s, recent marking phase length: %1.2fms",
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),
_old_gen_alloc_tracker->last_period_old_gen_bytes(),
_last_allocation_time_s * 1000.0,
_last_allocation_time_s > 0.0 ? _old_gen_alloc_tracker->last_period_old_gen_bytes() / _last_allocation_time_s : 0.0,
last_marking_start_to_mixed_time_s() * 1000.0);
percent_of(non_young_occupancy, _target_occupancy));
if (!_is_adaptive) {
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), prediction active: %s, old-gen threshold: %zuB (%1.2f%%), internal target occupancy: %zuB, "
"old-gen occupancy: %zuB, additional buffer size: %zuB, predicted old-gen allocation rate: %1.2fB/s, "
"predicted marking phase length: %1.2fms",
BOOL_TO_STR(have_enough_data_for_prediction()),
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,
predict(&_old_gen_alloc_rate),
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) {
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,
_old_gen_alloc_tracker->last_period_old_gen_bytes(),
_last_allocation_time_s,
last_marking_start_to_mixed_time_s());
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,
predict(&_old_gen_alloc_rate),
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());
}

View File

@ -25,7 +25,6 @@
#ifndef SHARE_GC_G1_G1IHOPCONTROL_HPP
#define SHARE_GC_G1_G1IHOPCONTROL_HPP
#include "gc/g1/g1OldGenAllocationTracker.hpp"
#include "memory/allocation.hpp"
#include "utilities/numberSeq.hpp"
@ -53,16 +52,15 @@ class G1IHOPControl : public CHeapObj<mtGC> {
// Percentage of free heap that should be considered as waste.
const size_t _heap_waste_percent;
// Most recent complete mutator allocation period in seconds.
double _last_allocation_time_s;
const G1OldGenAllocationTracker* _old_gen_alloc_tracker;
const G1Predictions* _predictor;
// Wall-clock time in seconds from marking start to the first mixed GC,
// excluding GC Pause time.
TruncatedSeq _marking_start_to_mixed_time_s;
// Old generation allocation rate in bytes per second.
TruncatedSeq _old_gen_alloc_rate;
// Track old-generation allocations during a concurrent cycle: end of the
// Concurrent Start to the first Mixed GC.
// These values are used only when G1UseAdaptiveIHOP is enabled.
TruncatedSeq _old_non_humongous_alloc_rate;
TruncatedSeq _peak_extra_humongous_occupancy_in_mark_cycle;
// The most recent unrestrained size of the young gen. This is used as an additional
// factor in the calculation of the threshold, as the threshold is based on
@ -77,19 +75,23 @@ class G1IHOPControl : public CHeapObj<mtGC> {
double predict(const TruncatedSeq* seq) const;
bool have_enough_data_for_prediction() const;
double last_marking_start_to_mixed_time_s() const;
// The "effective" target occupancy the algorithm wants to keep until the start
// of Mixed GCs. This is typically lower than the target occupancy, as the
// algorithm needs to consider restrictions by the environment.
size_t effective_target_occupancy() const;
void print_log(size_t non_young_occupancy);
void send_trace_event(G1NewTracer* tracer, size_t non_young_occupancy);
void print_log(size_t non_young_occupancy,
size_t non_humongous_allocation,
size_t peak_extra_humongous_occupancy);
void send_trace_event(G1NewTracer* tracer,
size_t non_young_occupancy,
size_t non_humongous_allocation,
size_t peak_extra_humongous_occupancy);
public:
G1IHOPControl(double ihop_percent,
const G1OldGenAllocationTracker* old_gen_alloc_tracker,
bool adaptive,
const G1Predictions* predictor,
size_t heap_reserve_percent,
@ -98,26 +100,23 @@ class G1IHOPControl : public CHeapObj<mtGC> {
// Adjust target occupancy.
void update_target_occupancy(size_t new_target_occupancy);
void update_target_after_marking_phase();
// Update allocation rate information and current expected young gen size for the
// first mixed gc needed for the predictor. Allocation rate is given as the
// separately passed in allocation increment and the time passed (mutator time)
// for the latest allocation increment here. Allocation size is the memory needed
// during the mutator before and the first mixed gc pause itself.
// Updates expected young gen size for the first mixed gc needed for the predictor.
// Contents include young gen at that point, and the memory required for evacuating
// the collection set in that first mixed gc (including waste caused by PLAB
// allocation etc.).
void update_allocation_info(double allocation_time_s, size_t expected_young_gen_size);
void record_expected_young_gen_size(size_t expected_young_gen_size);
// Update the time spent in the mutator beginning from the end of concurrent start to
// the first mixed gc.
void add_marking_start_to_mixed_length(double length_s);
void record_concurrent_cycle(double marking_start_to_mixed_time_s,
size_t non_humongous_bytes,
size_t peak_extra_humongous_occupancy);
// Get the current non-young occupancy at which concurrent marking should start.
size_t old_gen_threshold_for_conc_mark_start() const;
void report_statistics(G1NewTracer* tracer, size_t non_young_occupancy);
void report_statistics(G1NewTracer* tracer,
size_t non_young_occupancy,
size_t non_humongous_allocation,
size_t peak_extra_humongous_occupancy);
};
#endif // SHARE_GC_G1_G1IHOPCONTROL_HPP

View File

@ -26,36 +26,43 @@
#include "logging/log.hpp"
G1OldGenAllocationTracker::G1OldGenAllocationTracker() :
_last_period_old_gen_bytes(0),
_last_period_old_gen_growth(0),
_humongous_bytes_after_last_gc(0),
_allocated_bytes_since_last_gc(0),
_allocated_humongous_bytes_since_last_gc(0) {
_humongous_bytes_after_last_pause(0),
_allocated_non_humongous_bytes_since_last_pause(0),
_allocated_humongous_bytes_since_last_pause(0) {
}
void G1OldGenAllocationTracker::reset_after_gc(size_t humongous_bytes_after_gc) {
G1AllocationIntervalStats G1OldGenAllocationTracker::end_allocation_interval(size_t humongous_bytes_after_pause) {
// Calculate actual increase in old, taking eager reclaim into consideration.
size_t last_period_humongous_increase = 0;
if (humongous_bytes_after_gc > _humongous_bytes_after_last_gc) {
last_period_humongous_increase = humongous_bytes_after_gc - _humongous_bytes_after_last_gc;
assert(last_period_humongous_increase <= _allocated_humongous_bytes_since_last_gc,
size_t last_interval_humongous_increase = 0;
if (humongous_bytes_after_pause > _humongous_bytes_after_last_pause) {
last_interval_humongous_increase = humongous_bytes_after_pause - _humongous_bytes_after_last_pause;
assert(last_interval_humongous_increase <= _allocated_humongous_bytes_since_last_pause,
"Increase larger than allocated %zu <= %zu",
last_period_humongous_increase, _allocated_humongous_bytes_since_last_gc);
last_interval_humongous_increase, _allocated_humongous_bytes_since_last_pause);
}
_last_period_old_gen_growth = _allocated_bytes_since_last_gc + last_period_humongous_increase;
// Calculate and record needed values.
_last_period_old_gen_bytes = _allocated_bytes_since_last_gc + _allocated_humongous_bytes_since_last_gc;
_humongous_bytes_after_last_gc = humongous_bytes_after_gc;
size_t last_interval_old_gen_growth = _allocated_non_humongous_bytes_since_last_pause +
last_interval_humongous_increase;
log_debug(gc, alloc, stats)("Old generation allocation in the last mutator period, "
G1AllocationIntervalStats allocation_interval_stats{
_allocated_non_humongous_bytes_since_last_pause,
_allocated_humongous_bytes_since_last_pause,
_humongous_bytes_after_last_pause,
humongous_bytes_after_pause
};
_humongous_bytes_after_last_pause = humongous_bytes_after_pause;
log_debug(gc, alloc, stats)("Old generation allocation in the last allocation interval, "
"old gen allocated: %zuB, humongous allocated: %zuB, "
"old gen growth: %zuB.",
_allocated_bytes_since_last_gc,
_allocated_humongous_bytes_since_last_gc,
_last_period_old_gen_growth);
_allocated_non_humongous_bytes_since_last_pause,
_allocated_humongous_bytes_since_last_pause,
last_interval_old_gen_growth);
// Reset for next mutator period.
_allocated_bytes_since_last_gc = 0;
_allocated_humongous_bytes_since_last_gc = 0;
// Reset for the next interval.
_allocated_non_humongous_bytes_since_last_pause = 0;
_allocated_humongous_bytes_since_last_pause = 0;
return allocation_interval_stats;
}

View File

@ -22,46 +22,71 @@
*
*/
#ifndef SHARE_VM_GC_G1_G1OLDGENALLOCATIONTRACKER_HPP
#define SHARE_VM_GC_G1_G1OLDGENALLOCATIONTRACKER_HPP
#ifndef SHARE_GC_G1_G1OLDGENALLOCATIONTRACKER_HPP
#define SHARE_GC_G1_G1OLDGENALLOCATIONTRACKER_HPP
#include "gc/g1/g1HeapRegion.hpp"
#include "memory/allocation.hpp"
// Allocation statistics for an allocation interval, i.e. the interval between two
// consecutive STW pauses.
//
// The allocation counters record allocations made during that interval.
//
// _total_humongous_before_bytes is the humongous occupancy after the previous
// pause (at the start of the allocation interval).
//
// _total_humongous_after_bytes is the humongous occupancy after the current
// pause (at the end of the allocation interval).
struct G1AllocationIntervalStats {
size_t _non_humongous_allocated_bytes;
size_t _humongous_allocated_bytes;
size_t _total_humongous_before_bytes;
size_t _total_humongous_after_bytes;
G1AllocationIntervalStats(size_t non_humongous_allocated_bytes,
size_t humongous_allocated_bytes,
size_t total_humongous_before_bytes,
size_t total_humongous_after_bytes)
: _non_humongous_allocated_bytes(non_humongous_allocated_bytes),
_humongous_allocated_bytes(humongous_allocated_bytes),
_total_humongous_before_bytes(total_humongous_before_bytes),
_total_humongous_after_bytes(total_humongous_after_bytes)
{ }
void record_humongous_allocation(size_t humongous_allocation_bytes) {
_humongous_allocated_bytes += humongous_allocation_bytes;
_total_humongous_after_bytes += humongous_allocation_bytes;
}
};
// Track allocation details in the old generation.
class G1OldGenAllocationTracker : public CHeapObj<mtGC> {
// Total number of bytes allocated in the old generation at the end
// of the last gc.
size_t _last_period_old_gen_bytes;
// Total growth of the old geneneration since the last gc,
// taking eager-reclaim into consideration.
size_t _last_period_old_gen_growth;
// Total size of humongous objects after the last STW pause.
size_t _humongous_bytes_after_last_pause;
// Total size of humongous objects for last gc.
size_t _humongous_bytes_after_last_gc;
// Non-humongous old generation allocations since the last gc.
size_t _allocated_bytes_since_last_gc;
// Humongous allocations during last mutator period.
size_t _allocated_humongous_bytes_since_last_gc;
// Non-humongous old generation allocations since the last STW pause.
size_t _allocated_non_humongous_bytes_since_last_pause;
// Humongous allocations during last allocation interval.
size_t _allocated_humongous_bytes_since_last_pause;
public:
G1OldGenAllocationTracker();
void add_allocated_bytes_since_last_gc(size_t bytes) { _allocated_bytes_since_last_gc += bytes; }
void add_allocated_humongous_bytes_since_last_gc(size_t bytes) { _allocated_humongous_bytes_since_last_gc += bytes; }
// Record a humongous allocation in a collection pause. This allocation
// is accounted to the previous mutator period.
void record_collection_pause_humongous_allocation(size_t bytes) {
_humongous_bytes_after_last_gc += bytes;
void add_allocated_non_humongous_bytes(size_t bytes) {
_allocated_non_humongous_bytes_since_last_pause += bytes;
}
void add_allocated_humongous_bytes(size_t bytes) {
_allocated_humongous_bytes_since_last_pause += bytes;
}
size_t last_period_old_gen_bytes() const { return _last_period_old_gen_bytes; }
size_t last_period_old_gen_growth() const { return _last_period_old_gen_growth; };
// Record a humongous allocation in a collection pause. This allocation
// is accounted to the previous allocation interval.
void record_collection_pause_humongous_allocation(size_t bytes) {
_humongous_bytes_after_last_pause += bytes;
}
// Calculates and resets stats after a collection.
void reset_after_gc(size_t humongous_bytes_after_gc);
// Calculate and reset allocation statistics after a pause.
G1AllocationIntervalStats end_allocation_interval(size_t humongous_bytes_after_pause);
};
#endif // SHARE_VM_GC_G1_G1OLDGENALLOCATIONTRACKER_HPP
#endif // SHARE_GC_G1_G1OLDGENALLOCATIONTRACKER_HPP

View File

@ -55,8 +55,9 @@ G1Policy::G1Policy(STWGCTimer* gc_timer) :
_analytics(new G1Analytics(&_predictor)),
_remset_tracker(),
_mmu_tracker(new G1MMUTracker(GCPauseIntervalMillis / 1000.0, MaxGCPauseMillis / 1000.0)),
_concurrent_cycle_tracker(),
_old_gen_alloc_tracker(),
_ihop_control(create_ihop_control(&_old_gen_alloc_tracker, &_predictor)),
_ihop_control(create_ihop_control(&_predictor)),
_policy_counters(new GCPolicyCounters("GarbageFirst", 1, 2)),
_cur_pause_start_sec(0.0),
_young_list_desired_length(0),
@ -68,7 +69,6 @@ G1Policy::G1Policy(STWGCTimer* gc_timer) :
_young_gen_sizer(),
_free_regions_at_end_of_collection(0),
_pending_cards_from_gc(0),
_concurrent_start_to_mixed(),
_collection_set(nullptr),
_g1h(nullptr),
_phase_times_timer(gc_timer),
@ -589,6 +589,7 @@ void G1Policy::record_full_collection_end(size_t allocation_word_size) {
// Consider this like a collection pause for the purposes of allocation
// since last pause.
double end_sec = os::elapsedTime();
double start_time_sec = cur_pause_start_sec();
// "Nuke" the heuristics that control the young/mixed GC
// transitions and make sure we start with young GCs after the Full GC.
@ -602,9 +603,6 @@ void G1Policy::record_full_collection_end(size_t allocation_word_size) {
_survivor_surv_rate_group->reset();
update_young_length_bounds();
_old_gen_alloc_tracker.reset_after_gc(_g1h->humongous_regions_count() * G1HeapRegion::GrainBytes);
double start_time_sec = cur_pause_start_sec();
record_pause(Pause::Full, start_time_sec, end_sec);
}
@ -954,8 +952,6 @@ G1CollectorState G1Policy::record_young_collection_end(bool concurrent_operation
phase_times()->sum_thread_work_items(G1GCPhaseTimes::MergePSS, G1GCPhaseTimes::MergePSSToYoungGenCards));
}
record_pause(collector_state()->gc_pause_type(concurrent_operation_is_full_mark), start_time_sec, end_time_sec);
if (collector_state()->is_in_prepare_mixed_gc()) {
assert(!collector_state()->is_in_concurrent_start_gc(),
"The young GC before mixed is not allowed to be concurrent start GC");
@ -988,23 +984,27 @@ G1CollectorState G1Policy::record_young_collection_end(bool concurrent_operation
_free_regions_at_end_of_collection = _g1h->num_free_regions();
_old_gen_alloc_tracker.reset_after_gc(_g1h->humongous_regions_count() * G1HeapRegion::GrainBytes);
Pause this_pause = collector_state()->gc_pause_type(concurrent_operation_is_full_mark);
size_t humongous_allocation_bytes = G1CollectedHeap::is_humongous(allocation_word_size) ?
G1CollectedHeap::allocation_used_bytes(allocation_word_size) : 0;
record_pause(this_pause, start_time_sec, end_time_sec, humongous_allocation_bytes);
// Do not update dynamic IHOP due to G1 periodic collection as it is highly likely
// that in this case we are not running in a "normal" operating mode.
if (_g1h->gc_cause() != GCCause::_g1_periodic_collection) {
update_young_length_bounds();
// Take snapshots of these values here as update_ihop_prediction
// may complete the concurrent cycle and reset the values.
size_t non_humongous_allocation = _concurrent_cycle_tracker.non_humongous_allocated_bytes();
size_t peak_extra_humongous_occupancy = _concurrent_cycle_tracker.peak_extra_humongous_occupancy_bytes();
if (update_ihop_prediction(app_time_ms / 1000.0, is_young_only_pause)) {
_ihop_control->report_statistics(_g1h->gc_tracer_stw(), _g1h->non_young_occupancy_after_allocation(allocation_word_size));
_ihop_control->report_statistics(_g1h->gc_tracer_stw(),
_g1h->non_young_occupancy_after_allocation(allocation_word_size),
non_humongous_allocation,
peak_extra_humongous_occupancy);
}
} else {
// Any garbage collection triggered as periodic collection resets the time-to-mixed
// measurement. Periodic collection typically means that the application is "inactive", i.e.
// the marking threads may have received an uncharacteristic amount of cpu time
// for completing the marking, i.e. are faster than expected.
// This skews the predicted marking length towards smaller values which might cause
// the mark start being too late.
abort_time_to_mixed_tracking();
}
// Note that _mmu_tracker->max_gc_time() returns the time in seconds.
@ -1032,10 +1032,8 @@ G1CollectorState G1Policy::record_young_collection_end(bool concurrent_operation
return next_state;
}
G1IHOPControl* G1Policy::create_ihop_control(const G1OldGenAllocationTracker* old_gen_alloc_tracker,
const G1Predictions* predictor) {
G1IHOPControl* G1Policy::create_ihop_control(const G1Predictions* predictor) {
return new G1IHOPControl(G1IHOP,
old_gen_alloc_tracker,
G1UseAdaptiveIHOP,
predictor,
G1ReservePercent,
@ -1052,29 +1050,31 @@ bool G1Policy::update_ihop_prediction(double mutator_time_s,
double const min_valid_time = 1e-6;
bool report = false;
if (!this_gc_was_young_only && _concurrent_cycle_tracker.has_completed_cycle()) {
G1ConcurrentCycleStats cycle_stats = _concurrent_cycle_tracker.get_and_reset_cycle_stats();
if (!this_gc_was_young_only && _concurrent_start_to_mixed.has_result()) {
double marking_to_mixed_time = _concurrent_start_to_mixed.get_and_reset_last_marking_time();
assert(marking_to_mixed_time > 0.0,
"Concurrent start to mixed time must be larger than zero but is %.3f",
marking_to_mixed_time);
if (marking_to_mixed_time > min_valid_time) {
_ihop_control->add_marking_start_to_mixed_length(marking_to_mixed_time);
double concurrent_cycle_duration_s = cycle_stats._cycle_duration_s;
assert(concurrent_cycle_duration_s > 0.0,
"Time for Concurrent Start GC to the first Mixed GC must be larger than zero but is %.3f",
concurrent_cycle_duration_s);
if (concurrent_cycle_duration_s > min_valid_time) {
_ihop_control->record_concurrent_cycle(concurrent_cycle_duration_s,
cycle_stats._non_humongous_allocated_bytes,
cycle_stats._peak_extra_humongous_occupancy_bytes);
report = true;
}
}
// As an approximation for the young gc promotion rates during marking we use
// all of them. In many applications there are only a few if any young gcs during
// marking, which makes any prediction useless. This increases the accuracy of the
// prediction.
// The second clause prevents skewing the IHOP prediction with (typically) degenerate
// back-to-back young-gen-size samples.
if (this_gc_was_young_only && mutator_time_s > min_valid_time) {
// IHOP control wants to know the expected young gen length if it were not
// restrained by the heap reserve. Using the actual length would make the
// prediction too small and the limit the young gen every time we get to the
// predicted target occupancy.
size_t young_gen_size = young_list_desired_length() * G1HeapRegion::GrainBytes;
_ihop_control->update_allocation_info(mutator_time_s, young_gen_size);
_ihop_control->record_expected_young_gen_size(young_gen_size);
report = true;
}
@ -1311,11 +1311,11 @@ void G1Policy::decide_on_concurrent_start_pause() {
// Force concurrent start.
collector_state()->set_in_concurrent_start_gc();
// We might have ended up coming here about to start a mixed phase with a collection set
// active. The following remark might change the change the "evacuation efficiency" of
// active. The following remark might change the "evacuation efficiency" of
// the regions in this set, leading to failing asserts later.
// Since the concurrent cycle will recreate the collection set anyway, simply drop it here.
abandon_collection_set_candidates();
abort_time_to_mixed_tracking();
abort_concurrent_cycle_tracking();
log_debug(gc, ergo)("Initiate concurrent cycle (%s requested concurrent cycle)",
requester_for_mixed_abort(cause));
} else {
@ -1355,7 +1355,7 @@ void G1Policy::record_concurrent_mark_cleanup_end(bool has_rebuilt_remembered_se
}
if (!mixed_gc_pending) {
abort_time_to_mixed_tracking();
abort_concurrent_cycle_tracking();
log_debug(gc, ergo)("request young-only gcs (candidate old regions not available)");
}
if (mixed_gc_pending) {
@ -1390,7 +1390,8 @@ void G1Policy::update_gc_pause_time_ratios(Pause gc_type, double start_time_sec,
void G1Policy::record_pause(Pause gc_type,
double start,
double end) {
double end,
size_t humongous_allocation_bytes) {
// Manage the MMU tracker. For some reason it ignores Full GCs.
if (gc_type != Pause::Full) {
_mmu_tracker->add_pause(start, end);
@ -1398,49 +1399,26 @@ void G1Policy::record_pause(Pause gc_type,
update_gc_pause_time_ratios(gc_type, start, end);
update_time_to_mixed_tracking(gc_type, start, end);
size_t humongous_bytes = _g1h->humongous_regions_count() * G1HeapRegion::GrainBytes;
G1AllocationIntervalStats alloc_interval_stats = _old_gen_alloc_tracker.end_allocation_interval(humongous_bytes);
bool is_periodic_gc = _g1h->gc_cause() == GCCause::_g1_periodic_collection;
if (humongous_allocation_bytes > 0) {
// Record the humongous allocation that triggered the GC and attribute it to
// the ending allocation interval. We do this eagerly, before we know whether
// the post GC allocation succeeds, to keep the common case simple. In the
// rare case where this allocation fails, we over-account; this only
// affects the stored IHOP sample if the current GC is the first Mixed GC.
alloc_interval_stats.record_humongous_allocation(humongous_allocation_bytes);
}
_concurrent_cycle_tracker.record_allocation_interval(gc_type, is_periodic_gc, start, end, alloc_interval_stats);
double elapsed_gc_cpu_time = _analytics->gc_cpu_time_ms();
_analytics->set_gc_cpu_time_at_pause_end_ms(elapsed_gc_cpu_time);
}
void G1Policy::update_time_to_mixed_tracking(Pause gc_type,
double start,
double end) {
// Manage the mutator time tracking from concurrent start to first mixed gc.
switch (gc_type) {
case Pause::Full:
abort_time_to_mixed_tracking();
break;
case Pause::Cleanup:
case Pause::Remark:
case Pause::Normal:
case Pause::PrepareMixed:
_concurrent_start_to_mixed.add_pause(end - start);
break;
case Pause::ConcurrentStartFull:
// Do not track time-to-mixed time for periodic collections as they are likely
// to be not representative to regular operation as the mutators are idle at
// that time. Also only track full concurrent mark cycles.
if (_g1h->gc_cause() != GCCause::_g1_periodic_collection) {
_concurrent_start_to_mixed.record_concurrent_start_end(end);
}
break;
case Pause::ConcurrentStartUndo:
assert(_g1h->gc_cause() == GCCause::_g1_humongous_allocation,
"GC cause must be humongous allocation but is %d",
_g1h->gc_cause());
break;
case Pause::Mixed:
_concurrent_start_to_mixed.record_mixed_gc_start(start);
break;
default:
ShouldNotReachHere();
}
}
void G1Policy::abort_time_to_mixed_tracking() {
_concurrent_start_to_mixed.reset();
void G1Policy::abort_concurrent_cycle_tracking() {
_concurrent_cycle_tracker.abort_cycle();
}
bool G1Policy::next_gc_should_be_mixed() const {

View File

@ -26,7 +26,7 @@
#define SHARE_GC_G1_G1POLICY_HPP
#include "gc/g1/g1CollectorState.hpp"
#include "gc/g1/g1ConcurrentStartToMixedTimeTracker.hpp"
#include "gc/g1/g1ConcurrentCycleTracker.hpp"
#include "gc/g1/g1GCPhaseTimes.hpp"
#include "gc/g1/g1HeapRegionAttr.hpp"
#include "gc/g1/g1MMUTracker.hpp"
@ -58,8 +58,7 @@ class STWGCTimer;
class G1Policy: public CHeapObj<mtGC> {
using Pause = G1CollectorState::Pause;
static G1IHOPControl* create_ihop_control(const G1OldGenAllocationTracker* old_gen_alloc_tracker,
const G1Predictions* predictor);
static G1IHOPControl* create_ihop_control(const G1Predictions* predictor);
// Update the IHOP control with the necessary statistics. Returns true if there
// has been a significant update to the prediction.
bool update_ihop_prediction(double mutator_time_s,
@ -70,8 +69,9 @@ class G1Policy: public CHeapObj<mtGC> {
G1RemSetTrackingPolicy _remset_tracker;
G1MMUTracker* _mmu_tracker;
G1ConcurrentCycleTracker _concurrent_cycle_tracker;
// Tracking the allocation in the old generation between
// two GCs.
// two pauses.
G1OldGenAllocationTracker _old_gen_alloc_tracker;
G1IHOPControl* _ihop_control;
@ -114,8 +114,6 @@ class G1Policy: public CHeapObj<mtGC> {
// garbage collection or the most recent refinement sweep.
size_t _to_collection_set_cards;
G1ConcurrentStartToMixedTimeTracker _concurrent_start_to_mixed;
bool should_update_surv_rate_group_predictors();
double pending_cards_processing_time() const;
@ -264,17 +262,16 @@ public:
private:
void abandon_collection_set_candidates();
// Manage time-to-mixed tracking.
void update_time_to_mixed_tracking(Pause gc_type, double start, double end);
// Record the given STW pause with the given start and end times (in s).
void record_pause(Pause gc_type,
double start,
double end);
double end,
size_t humongous_allocation_bytes = 0);
void update_gc_pause_time_ratios(Pause gc_type, double start_sec, double end_sec);
// Indicate that we aborted marking before doing any mixed GCs.
void abort_time_to_mixed_tracking();
void abort_concurrent_cycle_tracking();
public:

View File

@ -97,31 +97,31 @@ void G1NewTracer::report_evacuation_statistics(const G1EvacSummary& young_summar
}
void G1NewTracer::report_basic_ihop_statistics(size_t threshold,
size_t target_ccupancy,
size_t non_young_occupancy,
size_t last_allocation_size,
double last_allocation_duration,
double last_marking_length) {
size_t target_occupancy,
size_t non_young_occupancy) {
send_basic_ihop_statistics(threshold,
target_ccupancy,
non_young_occupancy,
last_allocation_size,
last_allocation_duration,
last_marking_length);
target_occupancy,
non_young_occupancy);
}
void G1NewTracer::report_adaptive_ihop_statistics(size_t threshold,
size_t internal_target_occupancy,
size_t current_occupancy,
size_t additional_buffer_size,
double predicted_allocation_rate,
size_t non_humongous_allocation,
size_t peak_extra_humongous_occupancy,
double predicted_old_non_hum_alloc_rate,
size_t predicted_peak_extra_humongous_occupancy,
double predicted_marking_length,
bool prediction_active) {
send_adaptive_ihop_statistics(threshold,
internal_target_occupancy,
current_occupancy,
additional_buffer_size,
predicted_allocation_rate,
non_humongous_allocation,
peak_extra_humongous_occupancy,
predicted_old_non_hum_alloc_rate,
predicted_peak_extra_humongous_occupancy,
predicted_marking_length,
prediction_active);
}
@ -206,10 +206,7 @@ void G1NewTracer::send_old_evacuation_statistics(const G1EvacSummary& summary) c
void G1NewTracer::send_basic_ihop_statistics(size_t threshold,
size_t target_occupancy,
size_t non_young_occupancy,
size_t last_allocation_size,
double last_allocation_duration,
double last_marking_length) {
size_t non_young_occupancy) {
EventG1BasicIHOP evt;
if (evt.should_commit()) {
evt.set_gcId(GCId::current());
@ -217,10 +214,6 @@ void G1NewTracer::send_basic_ihop_statistics(size_t threshold,
evt.set_targetOccupancy(target_occupancy);
evt.set_thresholdPercentage(target_occupancy > 0 ? ((double)threshold / target_occupancy) : 0.0);
evt.set_currentOccupancy(non_young_occupancy);
evt.set_recentMutatorAllocationSize(last_allocation_size);
evt.set_recentMutatorDuration(last_allocation_duration * MILLIUNITS);
evt.set_recentAllocationRate(last_allocation_duration != 0.0 ? last_allocation_size / last_allocation_duration : 0.0);
evt.set_lastMarkingDuration(last_marking_length * MILLIUNITS);
evt.commit();
}
}
@ -229,7 +222,10 @@ void G1NewTracer::send_adaptive_ihop_statistics(size_t threshold,
size_t internal_target_occupancy,
size_t current_occupancy,
size_t additional_buffer_size,
double predicted_allocation_rate,
size_t non_humongous_allocation,
size_t peak_extra_humongous_occupancy,
double predicted_old_non_hum_alloc_rate,
size_t predicted_peak_extra_humongous_occupancy,
double predicted_marking_length,
bool prediction_active) {
EventG1AdaptiveIHOP evt;
@ -240,7 +236,10 @@ void G1NewTracer::send_adaptive_ihop_statistics(size_t threshold,
evt.set_ihopTargetOccupancy(internal_target_occupancy);
evt.set_currentOccupancy(current_occupancy);
evt.set_additionalBufferSize(additional_buffer_size);
evt.set_predictedAllocationRate(predicted_allocation_rate);
evt.set_nonHumongousAllocation(non_humongous_allocation);
evt.set_peakExtraHumongousOccupancy(peak_extra_humongous_occupancy);
evt.set_predictedNonHumongousAllocation(predicted_old_non_hum_alloc_rate);
evt.set_predictedPeakExtraHumongousOccupancy(predicted_peak_extra_humongous_occupancy);
evt.set_predictedMarkingDuration(predicted_marking_length * MILLIUNITS);
evt.set_predictionActive(prediction_active);
evt.commit();

View File

@ -52,15 +52,15 @@ public:
void report_basic_ihop_statistics(size_t threshold,
size_t target_occupancy,
size_t current_occupancy,
size_t last_allocation_size,
double last_allocation_duration,
double last_marking_length);
size_t current_occupancy);
void report_adaptive_ihop_statistics(size_t threshold,
size_t internal_target_occupancy,
size_t current_occupancy,
size_t additional_buffer_size,
double predicted_allocation_rate,
size_t non_humongous_allocation,
size_t peak_extra_humongous_occupancy,
double predicted_old_gen_non_humongous_allocation_rate,
size_t predicted_peak_extra_humongous_occupancy,
double predicted_marking_length,
bool prediction_active);
private:
@ -73,15 +73,16 @@ private:
void send_basic_ihop_statistics(size_t threshold,
size_t target_occupancy,
size_t non_young_occupancy,
size_t last_allocation_size,
double last_allocation_duration,
double last_marking_length);
size_t non_young_occupancy);
void send_adaptive_ihop_statistics(size_t threshold,
size_t internal_target_occupancy,
size_t non_young_occupancy,
size_t additional_buffer_size,
double predicted_allocation_rate,
size_t non_humongous_allocation,
size_t peak_extra_humongous_occupancy,
double predicted_old_gen_non_humongous_allocation_rate,
size_t predicted_peak_extra_humongous_occupancy,
double predicted_marking_length,
bool prediction_active);
};

View File

@ -542,7 +542,7 @@ public:
class FreeCSetStats {
size_t _before_used_bytes; // Usage in regions successfully evacuate
size_t _after_used_bytes; // Usage in regions failing evacuation
size_t _bytes_allocated_in_old_since_last_gc; // Size of young regions turned into old
size_t _bytes_allocated_in_old_since_last_pause; // Size of young regions turned into old
size_t _failure_used_words; // Live size in failed regions
size_t _failure_waste_words; // Wasted size in failed regions
uint _regions_freed; // Number of regions freed
@ -551,7 +551,7 @@ public:
FreeCSetStats() :
_before_used_bytes(0),
_after_used_bytes(0),
_bytes_allocated_in_old_since_last_gc(0),
_bytes_allocated_in_old_since_last_pause(0),
_failure_used_words(0),
_failure_waste_words(0),
_regions_freed(0) { }
@ -560,7 +560,7 @@ public:
assert(other != nullptr, "invariant");
_before_used_bytes += other->_before_used_bytes;
_after_used_bytes += other->_after_used_bytes;
_bytes_allocated_in_old_since_last_gc += other->_bytes_allocated_in_old_since_last_gc;
_bytes_allocated_in_old_since_last_pause += other->_bytes_allocated_in_old_since_last_pause;
_failure_used_words += other->_failure_used_words;
_failure_waste_words += other->_failure_waste_words;
_regions_freed += other->_regions_freed;
@ -575,7 +575,7 @@ public:
g1h->alloc_buffer_stats(G1HeapRegionAttr::Old)->add_failure_used_and_waste(_failure_used_words, _failure_waste_words);
G1Policy *policy = g1h->policy();
policy->old_gen_alloc_tracker()->add_allocated_bytes_since_last_gc(_bytes_allocated_in_old_since_last_gc);
policy->old_gen_alloc_tracker()->add_allocated_non_humongous_bytes(_bytes_allocated_in_old_since_last_pause);
policy->cset_regions_freed();
}
@ -592,7 +592,7 @@ public:
// additional allocation: both the objects still in the region and the
// ones already moved are accounted for elsewhere.
if (r->is_young()) {
_bytes_allocated_in_old_since_last_gc += G1HeapRegion::GrainBytes;
_bytes_allocated_in_old_since_last_pause += G1HeapRegion::GrainBytes;
}
}

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved.
Copyright (c) 2012, 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
@ -500,11 +500,6 @@
<Field type="float" contentType="percentage" name="thresholdPercentage" label="Current IHOP Threshold" description="Current IHOP threshold in percent of old generation" />
<Field type="ulong" contentType="bytes" name="targetOccupancy" label="Target Occupancy" description="Target old generation occupancy to reach at the start of mixed GC" />
<Field type="ulong" contentType="bytes" name="currentOccupancy" label="Current Occupancy" description="Current old generation occupancy" />
<Field type="ulong" contentType="bytes" name="recentMutatorAllocationSize" label="Recent Mutator Allocation Size"
description="Mutator allocation during mutator operation in the most recent interval" />
<Field type="long" contentType="millis" name="recentMutatorDuration" label="Recent Mutator Duration" description="Time the mutator ran in the most recent interval" />
<Field type="double" contentType="bytes-per-second" name="recentAllocationRate" label="Recent Allocation Rate" description="Allocation rate of the mutator in the most recent interval in bytes/second" />
<Field type="long" contentType="millis" name="lastMarkingDuration" label="Last Marking Duration" description="Last time from the end of the last concurrent start to the first mixed GC" />
</Event>
<Event name="G1AdaptiveIHOP" category="Java Virtual Machine, GC, Detailed" label="G1 Adaptive IHOP Statistics" startTime="false"
@ -514,8 +509,15 @@
<Field type="float" contentType="percentage" name="thresholdPercentage" label="Threshold" description="Current IHOP threshold in percent of the internal target occupancy" />
<Field type="ulong" contentType="bytes" name="ihopTargetOccupancy" label="IHOP Target Occupancy" description="Internal target old generation occupancy to reach at the start of mixed GC" />
<Field type="ulong" contentType="bytes" name="currentOccupancy" label="Current Occupancy" description="Current old generation occupancy" />
<Field type="ulong" contentType="bytes" name="nonHumongousAllocation" label="Current Non-Humongous Allocation"
description="Current non-humongous allocation accumulated by the concurrent cycle tracker" />
<Field type="ulong" contentType="bytes" name="peakExtraHumongousOccupancy" label="Current Peak Extra Humongous Occupancy"
description="Current peak increase in humongous occupancy relative to the occupancy at concurrent cycle start" experimental="true" />
<Field type="ulong" contentType="bytes" name="additionalBufferSize" label="Additional Buffer" description="Additional buffer size" experimental="true" />
<Field type="double" contentType="bytes-per-second" name="predictedAllocationRate" label="Predicted Allocation Rate" description="Current predicted allocation rate for the mutator in bytes/second" />
<Field type="double" contentType="bytes-per-second" name="predictedNonHumongousAllocation" label="Predicted Non-Humongous Allocation Rate"
description="Predicted allocation rate of non-humongous objects into the old generation, in bytes per second" />
<Field type="ulong" contentType="bytes" name="predictedPeakExtraHumongousOccupancy" label="Predicted Peak Extra Humongous Occupancy"
description="Predicted peak increase in humongous occupancy during a concurrent cycle relative to the occupancy at concurrent cycle start" experimental="true" />
<Field type="long" contentType="millis" name="predictedMarkingDuration" label="Predicted Marking Duration"
description="Current predicted time from the end of the last concurrent start to the first mixed GC" />
<Field type="boolean" name="predictionActive" label="Prediction Active" description="Indicates whether the adaptive IHOP prediction is active" />

View File

@ -21,227 +21,804 @@
* questions.
*/
#include "gc/g1/g1CollectedHeap.inline.hpp"
#include "gc/g1/g1CollectedHeap.inline.hpp"
#include "gc/g1/g1ConcurrentCycleTracker.hpp"
#include "gc/g1/g1IHOPControl.hpp"
#include "gc/g1/g1OldGenAllocationTracker.hpp"
#include "gc/g1/g1Predictions.hpp"
#include "unittest.hpp"
static void test_update_allocation_tracker(G1OldGenAllocationTracker* alloc_tracker,
size_t alloc_amount) {
alloc_tracker->add_allocated_bytes_since_last_gc(alloc_amount);
alloc_tracker->reset_after_gc((size_t)0);
}
struct GCPauseData {
double _gc_start_time_s = 0;
double _gc_pause_duration_s = 0;
size_t _desired_young_bytes = 0;
size_t _non_hum_alloc_bytes = 0;
size_t _hum_alloc_bytes = 0;
size_t _total_hum_after_gc_bytes = 0;
size_t _gc_trigger_hum_bytes = 0;
static void test_update(G1IHOPControl* ctrl,
G1OldGenAllocationTracker* alloc_tracker,
double alloc_time, size_t alloc_amount,
size_t young_size, double mark_time) {
test_update_allocation_tracker(alloc_tracker, alloc_amount);
for (int i = 0; i < 100; i++) {
ctrl->update_allocation_info(alloc_time, young_size);
ctrl->add_marking_start_to_mixed_length(mark_time);
GCPauseData(double gc_start_time_s,
double gc_pause_duration_s,
size_t desired_young_bytes,
size_t non_hum_alloc_bytes,
size_t hum_alloc_bytes,
size_t total_hum_after_gc_bytes,
size_t gc_trigger_hum_bytes = 0)
: _gc_start_time_s(gc_start_time_s),
_gc_pause_duration_s(gc_pause_duration_s),
_desired_young_bytes(desired_young_bytes),
_non_hum_alloc_bytes(non_hum_alloc_bytes),
_hum_alloc_bytes(hum_alloc_bytes),
_total_hum_after_gc_bytes(total_hum_after_gc_bytes),
_gc_trigger_hum_bytes(gc_trigger_hum_bytes) {}
};
class G1IHOPTestController {
G1ConcurrentCycleTracker _conc_cycle_tracker;
G1OldGenAllocationTracker _alloc_tracker;
G1Predictions _pred;
G1IHOPControl _ihop_control;
public:
G1IHOPTestController(bool adaptive, size_t ihop, size_t target_occupancy)
: _conc_cycle_tracker(),
_alloc_tracker(),
_pred(0.50),
_ihop_control(ihop, adaptive, &_pred, 0 /* heap_reserve_percent */, 0 /* heap_waste_percent */)
{
_ihop_control.update_target_occupancy(target_occupancy);
}
void end_mutator_phase(GCPauseData pause_data, G1CollectorState::Pause pause_type) {
_alloc_tracker.add_allocated_non_humongous_bytes(pause_data._non_hum_alloc_bytes);
_alloc_tracker.add_allocated_humongous_bytes(pause_data._hum_alloc_bytes);
G1AllocationIntervalStats interval_stats =
_alloc_tracker.end_allocation_interval(pause_data._total_hum_after_gc_bytes);
if (pause_data._gc_trigger_hum_bytes > 0) {
// Mirror G1Policy::record_pause() and the successful allocation tracking
interval_stats.record_humongous_allocation(pause_data._gc_trigger_hum_bytes);
_alloc_tracker.record_collection_pause_humongous_allocation(pause_data._gc_trigger_hum_bytes);
}
_conc_cycle_tracker.record_allocation_interval(pause_type,
false /* is_periodic_gc */,
pause_data._gc_start_time_s,
pause_data._gc_start_time_s + pause_data._gc_pause_duration_s,
interval_stats);
if (pause_type != G1CollectorState::Pause::Mixed &&
pause_type != G1CollectorState::Pause::Full) {
_ihop_control.record_expected_young_gen_size(pause_data._desired_young_bytes);
}
}
void mutator_phase_end_with_conc_start(GCPauseData pause_data) {
end_mutator_phase(pause_data, G1CollectorState::Pause::ConcurrentStartFull);
}
void mutator_phase_end_with_normal_gc(GCPauseData pause_data) {
end_mutator_phase(pause_data, G1CollectorState::Pause::Normal);
}
void mutator_phase_end_with_mixed_gc(GCPauseData pause_data) {
end_mutator_phase(pause_data, G1CollectorState::Pause::Mixed);
G1ConcurrentCycleStats cycle_stats = _conc_cycle_tracker.get_and_reset_cycle_stats();
_ihop_control.record_concurrent_cycle(cycle_stats._cycle_duration_s,
cycle_stats._non_humongous_allocated_bytes,
cycle_stats._peak_extra_humongous_occupancy_bytes);
}
size_t old_gen_threshold_for_conc_mark_start() {
return _ihop_control.old_gen_threshold_for_conc_mark_start();
}
size_t non_humongous_allocated_bytes() {
return _conc_cycle_tracker.non_humongous_allocated_bytes();
}
size_t peak_extra_humongous_occupancy_bytes() {
return _conc_cycle_tracker.peak_extra_humongous_occupancy_bytes();
}
};
static void add_multiple_samples(G1IHOPTestController* ctrl,
double mutator_duration_s,
double gc_pause_duration_s,
size_t young_reserve_bytes,
size_t non_hum_bytes,
size_t hum_alloc_bytes,
size_t num_samples) {
double gc_start_time_s = mutator_duration_s;
for (size_t i = 0; i < num_samples; i++) {
ctrl->mutator_phase_end_with_conc_start({
gc_start_time_s,
gc_pause_duration_s,
young_reserve_bytes,
non_hum_bytes,
hum_alloc_bytes,
hum_alloc_bytes /* _total_hum_after_gc_bytes */
});
gc_start_time_s += (gc_pause_duration_s + mutator_duration_s);
ctrl->mutator_phase_end_with_normal_gc({
gc_start_time_s,
gc_pause_duration_s,
young_reserve_bytes,
non_hum_bytes,
hum_alloc_bytes,
hum_alloc_bytes /* _total_hum_after_gc_bytes */
});
gc_start_time_s += (gc_pause_duration_s + mutator_duration_s);
ctrl->mutator_phase_end_with_mixed_gc({
gc_start_time_s,
gc_pause_duration_s,
young_reserve_bytes,
non_hum_bytes,
hum_alloc_bytes,
hum_alloc_bytes /* _total_hum_after_gc_bytes */
});
gc_start_time_s += (gc_pause_duration_s + mutator_duration_s);
}
}
static void test_update_humongous(G1IHOPControl* ctrl,
G1OldGenAllocationTracker* alloc_tracker,
double alloc_time,
size_t alloc_amount_non_hum,
size_t alloc_amount_hum,
size_t humongous_bytes_after_last_gc,
size_t young_size,
double mark_time) {
alloc_tracker->add_allocated_bytes_since_last_gc(alloc_amount_non_hum);
alloc_tracker->add_allocated_humongous_bytes_since_last_gc(alloc_amount_hum);
alloc_tracker->reset_after_gc(humongous_bytes_after_last_gc);
for (int i = 0; i < 100; i++) {
ctrl->update_allocation_info(alloc_time, young_size);
ctrl->add_marking_start_to_mixed_length(mark_time);
}
static size_t old_gen_threshold(size_t target_occupancy_bytes,
size_t young_reserve_bytes,
size_t non_hum_alloc_rate,
size_t peak_extra_hum,
double cycle_duration_s) {
size_t needed_during_cycle = young_reserve_bytes + non_hum_alloc_rate * cycle_duration_s + peak_extra_hum;
return needed_during_cycle < target_occupancy_bytes ?
target_occupancy_bytes - needed_during_cycle : 0;
}
// @requires UseG1GC
TEST_VM(G1IHOPControl, static_simple) {
// Test requires G1
if (!UseG1GC) {
return;
}
const bool is_adaptive = false;
const size_t initial_ihop = 45;
G1OldGenAllocationTracker alloc_tracker;
G1IHOPControl ctrl(initial_ihop, &alloc_tracker, is_adaptive, nullptr, 0, 0);
ctrl.update_target_occupancy(100);
size_t threshold = ctrl.old_gen_threshold_for_conc_mark_start();
EXPECT_EQ(initial_ihop, threshold);
test_update_allocation_tracker(&alloc_tracker, 100);
ctrl.update_allocation_info(100.0, 100);
threshold = ctrl.old_gen_threshold_for_conc_mark_start();
EXPECT_EQ(initial_ihop, threshold);
ctrl.add_marking_start_to_mixed_length(1000.0);
threshold = ctrl.old_gen_threshold_for_conc_mark_start();
EXPECT_EQ(initial_ihop, threshold);
// Whatever we pass, the IHOP value must stay the same.
test_update(&ctrl, &alloc_tracker, 2, 10, 10, 3);
threshold = ctrl.old_gen_threshold_for_conc_mark_start();
EXPECT_EQ(initial_ihop, threshold);
test_update(&ctrl, &alloc_tracker, 12, 10, 10, 3);
threshold = ctrl.old_gen_threshold_for_conc_mark_start();
EXPECT_EQ(initial_ihop, threshold);
}
// @requires UseG1GC
TEST_VM(G1IHOPControl, adaptive_simple) {
TEST_VM(G1IHOPControl, allocation_tracker_incr) {
// Test requires G1
if (!UseG1GC) {
return;
}
const bool is_adaptive = true;
const size_t initial_threshold = 45;
const size_t young_size = 10;
const size_t target_size = 100;
size_t initial_ihop = InitiatingHeapOccupancyPercent;
G1IHOPTestController ctrl(false /* adaptive */, initial_ihop, 100 /* target_occupancy */);
double const time_step_s = 1.0;
double gc_start_time_s = time_step_s;
double gc_pause_duration_s = 1.0;
ctrl.mutator_phase_end_with_conc_start({
gc_start_time_s,
gc_pause_duration_s,
10 /* _desired_young_bytes */,
0 /* _non_hum_alloc_bytes */,
0 /* _hum_alloc_bytes */,
0 /* _total_hum_after_gc_bytes */
});
// The final IHOP value is always
// target_size - (young_size + alloc_amount/alloc_time * marking_time)
gc_start_time_s += (gc_pause_duration_s + time_step_s);
G1OldGenAllocationTracker alloc_tracker;
G1Predictions pred(0.95);
G1IHOPControl ctrl(initial_threshold, &alloc_tracker, is_adaptive, &pred, 0, 0);
ctrl.update_target_occupancy(target_size);
ctrl.mutator_phase_end_with_normal_gc({
gc_start_time_s,
gc_pause_duration_s,
10 /* _desired_young_bytes */,
20 /* _non_hum_alloc_bytes */,
30 /* _hum_alloc_bytes */,
25 /* _total_hum_after_gc_bytes */
});
// First "load".
const size_t alloc_time1 = 2;
const size_t alloc_amount1 = 10;
const size_t marking_time1 = 2;
const size_t settled_ihop1 = target_size
- (young_size + alloc_amount1 / alloc_time1 * marking_time1);
EXPECT_EQ(20u, ctrl.non_humongous_allocated_bytes());
EXPECT_EQ(30u, ctrl.peak_extra_humongous_occupancy_bytes());
size_t threshold;
threshold = ctrl.old_gen_threshold_for_conc_mark_start();
gc_start_time_s += (gc_pause_duration_s + time_step_s);
ctrl.mutator_phase_end_with_normal_gc({
gc_start_time_s,
gc_pause_duration_s,
10 /* _desired_young_bytes */,
5 /* _non_hum_alloc_bytes */,
10 /* _hum_alloc_bytes */,
10 /* _total_hum_after_gc_bytes */
});
EXPECT_EQ(initial_threshold, threshold);
for (size_t i = 0; i < G1AdaptiveIHOPNumInitialSamples - 1; i++) {
test_update_allocation_tracker(&alloc_tracker, alloc_amount1);
ctrl.update_allocation_info(alloc_time1, young_size);
ctrl.add_marking_start_to_mixed_length(marking_time1);
// Not enough data yet.
threshold = ctrl.old_gen_threshold_for_conc_mark_start();
ASSERT_EQ(initial_threshold, threshold) << "on step " << i;
}
test_update(&ctrl, &alloc_tracker, alloc_time1, alloc_amount1, young_size, marking_time1);
threshold = ctrl.old_gen_threshold_for_conc_mark_start();
EXPECT_EQ(settled_ihop1, threshold);
// Second "load". A bit higher allocation rate.
const size_t alloc_time2 = 2;
const size_t alloc_amount2 = 30;
const size_t marking_time2 = 2;
const size_t settled_ihop2 = target_size
- (young_size + alloc_amount2 / alloc_time2 * marking_time2);
test_update(&ctrl, &alloc_tracker, alloc_time2, alloc_amount2, young_size, marking_time2);
threshold = ctrl.old_gen_threshold_for_conc_mark_start();
EXPECT_LT(threshold, settled_ihop1);
// Third "load". Very high (impossible) allocation rate.
const size_t alloc_time3 = 1;
const size_t alloc_amount3 = 50;
const size_t marking_time3 = 2;
const size_t settled_ihop3 = 0;
test_update(&ctrl, &alloc_tracker, alloc_time3, alloc_amount3, young_size, marking_time3);
threshold = ctrl.old_gen_threshold_for_conc_mark_start();
EXPECT_EQ(settled_ihop3, threshold);
// And back to some arbitrary value.
test_update(&ctrl, &alloc_tracker, alloc_time2, alloc_amount2, young_size, marking_time2);
threshold = ctrl.old_gen_threshold_for_conc_mark_start();
EXPECT_GT(threshold, settled_ihop3);
// Peak Humongous should be:
// hum_after_gc (from previous gc) + _hum_alloc_bytes
EXPECT_EQ(25u, ctrl.non_humongous_allocated_bytes());
EXPECT_EQ(35u, ctrl.peak_extra_humongous_occupancy_bytes());
}
TEST_VM(G1IHOPControl, adaptive_humongous) {
TEST_VM(G1IHOPControl, non_adaptive_ihop) {
// Test requires G1
if (!UseG1GC) {
return;
}
const bool is_adaptive = true;
const size_t initial_threshold = 45;
const size_t young_size = 10;
const size_t target_size = 100;
const double duration = 10.0;
const size_t marking_time = 2;
size_t initial_ihop = InitiatingHeapOccupancyPercent;
G1OldGenAllocationTracker alloc_tracker;
G1Predictions pred(0.95);
G1IHOPControl ctrl(initial_threshold, &alloc_tracker, is_adaptive, &pred, 0, 0);
ctrl.update_target_occupancy(target_size);
G1IHOPTestController ctrl(false /* adaptive */, initial_ihop, 100 /* target_occupancy */);
size_t old_bytes = 100;
size_t humongous_bytes = 200;
size_t humongous_bytes_after_gc = 150;
size_t humongous_bytes_after_last_gc = 50;
// Load 1
test_update_humongous(&ctrl, &alloc_tracker, duration, 0, humongous_bytes,
humongous_bytes_after_last_gc, young_size, marking_time);
// Test threshold
size_t threshold;
threshold = ctrl.old_gen_threshold_for_conc_mark_start();
// Adjusted allocated bytes:
// Total bytes: humongous_bytes
// Freed hum bytes: humongous_bytes - humongous_bytes_after_last_gc
double alloc_rate = humongous_bytes_after_last_gc / duration;
size_t target_threshold = target_size - (size_t)(young_size + alloc_rate * marking_time);
add_multiple_samples(&ctrl,
1.0 /* mutator_duration_s */,
1.0 /* gc_pause_duration_s */,
10 /* young_reserve */,
20 /* non_hum_bytes */,
0 /* hum_alloc_bytes */,
100 /* num_samples */);
EXPECT_EQ(threshold, target_threshold);
// Load 2
G1IHOPControl ctrl2(initial_threshold, &alloc_tracker, is_adaptive, &pred, 0, 0);
ctrl2.update_target_occupancy(target_size);
test_update_humongous(&ctrl2, &alloc_tracker, duration, old_bytes, humongous_bytes,
humongous_bytes_after_gc, young_size, marking_time);
threshold = ctrl2.old_gen_threshold_for_conc_mark_start();
// Adjusted allocated bytes:
// Total bytes: old_bytes + humongous_bytes
// Freed hum bytes: humongous_bytes - (humongous_bytes_after_gc - humongous_bytes_after_last_gc)
alloc_rate = (old_bytes + (humongous_bytes_after_gc - humongous_bytes_after_last_gc)) / duration;
target_threshold = target_size - (size_t)(young_size + alloc_rate * marking_time);
EXPECT_EQ(threshold, target_threshold);
// Load 3
humongous_bytes_after_last_gc = humongous_bytes_after_gc;
humongous_bytes_after_gc = 50;
G1IHOPControl ctrl3(initial_threshold, &alloc_tracker, is_adaptive, &pred, 0, 0);
ctrl3.update_target_occupancy(target_size);
test_update_humongous(&ctrl3, &alloc_tracker, duration, old_bytes, humongous_bytes,
humongous_bytes_after_gc, young_size, marking_time);
threshold = ctrl3.old_gen_threshold_for_conc_mark_start();
// Adjusted allocated bytes:
// All humongous are cleaned up since humongous_bytes_after_gc < humongous_bytes_after_last_gc
// Total bytes: old_bytes + humongous_bytes
// Freed hum bytes: humongous_bytes
alloc_rate = old_bytes / duration;
target_threshold = target_size - (size_t)(young_size + alloc_rate * marking_time);
EXPECT_EQ(threshold, target_threshold);
EXPECT_EQ(initial_ihop, ctrl.old_gen_threshold_for_conc_mark_start());
}
TEST_VM(G1IHOPControl, adaptive_ihop_not_enough_samples) {
// Test requires G1
if (!UseG1GC) {
return;
}
size_t initial_ihop = InitiatingHeapOccupancyPercent;
G1IHOPTestController ctrl(true /* adaptive */, initial_ihop, 100 /* target_occupancy */);
add_multiple_samples(&ctrl,
1.0 /* mutator_duration_s */,
1.0 /* gc_pause_duration_s */,
10 /* young_reserve */,
20 /* non_hum_bytes */,
0 /* hum_alloc_bytes */,
G1AdaptiveIHOPNumInitialSamples - 1 /* num_samples */);
EXPECT_EQ(initial_ihop, ctrl.old_gen_threshold_for_conc_mark_start());
}
TEST_VM(G1IHOPControl, adaptive_ihop_non_humongous_only) {
// Test requires G1
if (!UseG1GC) {
return;
}
size_t initial_ihop = InitiatingHeapOccupancyPercent;
// G1Predictions require 5 or more samples to skip special considerations for
// small samples.
size_t num_samples = 5;
G1IHOPTestController ctrl(true /* adaptive */, initial_ihop, 100 /* target_occupancy */);
// We run 2 allocation intervals for each concurrent cycle
double total_cycle_duration_s = 2;
add_multiple_samples(&ctrl,
1.0 /* mutator_duration_s */,
1.0 /* gc_pause_duration_s */,
10 /* young_reserve */,
20 /* non_hum_bytes */,
0 /* hum_alloc_bytes */,
num_samples);
size_t expected_threshold = old_gen_threshold(100 /* target_occupancy */,
10 /* young_reserve */,
20 /* non_hum_alloc_rate */,
0 /* peak_extra_hum */,
total_cycle_duration_s);
EXPECT_EQ(expected_threshold, ctrl.old_gen_threshold_for_conc_mark_start());
}
TEST_VM(G1IHOPControl, adaptive_ihop_peak_humongous_only) {
// Test requires G1
if (!UseG1GC) {
return;
}
size_t initial_ihop = InitiatingHeapOccupancyPercent;
// G1Predictions require 5 or more samples to skip special considerations for
// small samples.
size_t num_samples = 5;
G1IHOPTestController ctrl(true /* adaptive */, initial_ihop, 100 /* target_occupancy */);
add_multiple_samples(&ctrl,
1.0 /* mutator_duration_s */,
1.0 /* gc_pause_duration_s */,
10 /* young_reserve */,
0 /* non_hum_bytes */,
30 /* hum_alloc_bytes */,
num_samples);
double total_cycle_duration_s = 2;
size_t expected_threshold = old_gen_threshold(100 /* target_occupancy_bytes */,
10 /* young_reserve_bytes */,
0 /* non_hum_alloc_rate */,
30 /* peak_extra_hum */,
total_cycle_duration_s);
EXPECT_EQ(expected_threshold, ctrl.old_gen_threshold_for_conc_mark_start());
}
TEST_VM(G1IHOPControl, adaptive_ihop_combined) {
// Test requires G1
if (!UseG1GC) {
return;
}
size_t initial_ihop = InitiatingHeapOccupancyPercent;
// G1Predictions require 5 or more samples to skip special considerations for
// small samples.
size_t num_samples = 5;
G1IHOPTestController ctrl(true /* adaptive */, initial_ihop, 100 /* target_occupancy */);
add_multiple_samples(&ctrl,
1.0 /* mutator_duration_s */,
1.0 /* gc_pause_duration_s */,
10 /* young_reserve */,
20 /* non_hum_bytes */,
30 /* hum_alloc_bytes */,
num_samples);
double total_cycle_duration_s = 2;
size_t expected_threshold = old_gen_threshold(100 /* target_occupancy */,
10 /* young_reserve */,
20 /* non_hum_alloc_rate */,
30 /* peak_extra_hum */,
total_cycle_duration_s);
EXPECT_EQ(expected_threshold, ctrl.old_gen_threshold_for_conc_mark_start());
}
TEST_VM(G1IHOPControl, adaptive_ihop_high_alloc_pressure) {
// Test requires G1
if (!UseG1GC) {
return;
}
size_t initial_ihop = InitiatingHeapOccupancyPercent;
// G1Predictions require 5 or more samples to skip special considerations for
// small samples.
size_t num_samples = 5;
G1IHOPTestController ctrl(true /* adaptive */, initial_ihop, 100 /* target_occupancy */);
add_multiple_samples(&ctrl,
1.0 /* mutator_duration_s */,
1.0 /* gc_pause_duration_s */,
10 /* young_reserve */,
70 /* non_hum_bytes */,
35 /* hum_alloc_bytes */,
num_samples);
size_t expected_threshold = 0;
EXPECT_EQ(expected_threshold, ctrl.old_gen_threshold_for_conc_mark_start());
}
TEST_VM(G1IHOPControl, adaptive_ihop_young_reserve) {
// Test requires G1
if (!UseG1GC) {
return;
}
size_t initial_ihop = InitiatingHeapOccupancyPercent;
// G1Predictions require 5 or more samples to skip special considerations for
// small samples.
size_t num_samples = 5;
G1IHOPTestController ctrl_small(true /* adaptive */, initial_ihop, 100 /* target_occupancy */);
add_multiple_samples(&ctrl_small,
1.0 /* mutator_duration_s */,
1.0 /* gc_pause_duration_s */,
10 /* young_reserve */,
20 /* non_hum_bytes */,
30 /* hum_alloc_bytes */,
num_samples);
double total_cycle_duration_s = 2;
size_t expected_small_young = old_gen_threshold(100 /* target_occupancy */,
10 /* young_reserve */,
20 /* non_hum_alloc_rate */,
30 /* peak_extra_hum */,
total_cycle_duration_s);
G1IHOPTestController ctrl_large(true /* adaptive */, initial_ihop, 100 /* target_occupancy */);
add_multiple_samples(&ctrl_large,
1.0 /* mutator_duration_s */,
1.0 /* gc_pause_duration_s */,
25 /* young_reserve */,
20 /* non_hum_bytes */,
30 /* hum_alloc_bytes */,
num_samples);
size_t expected_large_young = old_gen_threshold(100 /* target_occupancy */,
25 /* young_reserve */,
20 /* non_hum_alloc_rate */,
30 /* peak_extra_hum */,
total_cycle_duration_s);
EXPECT_EQ(expected_small_young, ctrl_small.old_gen_threshold_for_conc_mark_start());
EXPECT_EQ(expected_large_young, ctrl_large.old_gen_threshold_for_conc_mark_start());
EXPECT_LT(expected_large_young, expected_small_young);
}
TEST_VM(G1IHOPControl, adaptive_ihop_recovers_after_spike) {
// Test requires G1
if (!UseG1GC) {
return;
}
size_t initial_ihop = InitiatingHeapOccupancyPercent;
// G1Predictions require 5 or more samples to skip special considerations for
// small samples.
G1IHOPTestController ctrl(true /* adaptive */, initial_ihop, 100 /* target_occupancy */);
add_multiple_samples(&ctrl,
1.0 /* mutator_duration_s */,
1.0 /* gc_pause_duration_s */,
10 /* young_reserve */,
20 /* non_hum_bytes */,
10 /* hum_alloc_bytes */,
20 /* num_samples */);
size_t before_spike = ctrl.old_gen_threshold_for_conc_mark_start();
add_multiple_samples(&ctrl,
1.0 /* mutator_duration_s */,
1.0 /* gc_pause_duration_s */,
10 /* young_reserve */,
40 /* non_hum_bytes */,
30 /* hum_alloc_bytes */,
5 /* num_samples */);
size_t during_spike = ctrl.old_gen_threshold_for_conc_mark_start();
add_multiple_samples(&ctrl,
1.0 /* mutator_duration_s */,
1.0 /* gc_pause_duration_s */,
10 /* young_reserve */,
20 /* non_hum_bytes */,
5 /* hum_alloc_bytes */,
20 /* num_samples */);
size_t after_recovery = ctrl.old_gen_threshold_for_conc_mark_start();
EXPECT_LT(during_spike, before_spike);
EXPECT_GT(after_recovery, during_spike);
}
TEST_VM(G1IHOPControl, adaptive_ihop_reuse_eagerly_reclaimed) {
// Test requires G1
if (!UseG1GC) {
return;
}
size_t initial_ihop = InitiatingHeapOccupancyPercent;
size_t target_occupancy = 100;
size_t young_reserve = 10;
G1IHOPTestController ctrl(true /* adaptive */, initial_ihop, target_occupancy);
double mutator_duration_s = 1.0;
double gc_pause_duration_s = 1.0;
double gc_start_time_s = mutator_duration_s;
size_t h_t0 = 100;
ctrl.mutator_phase_end_with_conc_start({
gc_start_time_s,
gc_pause_duration_s,
10 /* young_reserve_bytes */,
10 /* non_hum_bytes */,
h_t0 /* hum_alloc_bytes */,
h_t0 /* total_hum_after_gc_bytes */
});
// First mutator phase:
// No new humongous allocations in this phase.
// h_t1 < h_t0 (eager reclaim)
gc_start_time_s += (gc_pause_duration_s + mutator_duration_s);
size_t h_t1 = 60;
ctrl.mutator_phase_end_with_normal_gc({
gc_start_time_s,
gc_pause_duration_s,
10 /* young_reserve_bytes */,
0 /* non_hum_bytes */,
0 /* hum_alloc_bytes */,
h_t1 /* total_hum_after_gc_bytes */
});
EXPECT_EQ(0ul, ctrl.peak_extra_humongous_occupancy_bytes());
// Second mutator phase:
size_t hum_alloc_bytes = 50;
size_t h_t2 = 60;
gc_start_time_s += (gc_pause_duration_s + mutator_duration_s);
ctrl.mutator_phase_end_with_normal_gc({
gc_start_time_s,
gc_pause_duration_s,
10 /* young_reserve_bytes */,
0 /* non_hum_bytes */,
hum_alloc_bytes,
h_t2 /* total_hum_after_gc_bytes */
});
// Expected:
// delta_after_previous_gc = 60 - 100 = -40
// delta_before_this_gc = -40 + 50 = 10
// peak extra humongous occupancy = 10
EXPECT_EQ(10ul, ctrl.peak_extra_humongous_occupancy_bytes());
}
TEST_VM(G1IHOPControl, adaptive_ihop_eager_reclaim_reduces_extra_humongous_occupancy) {
// Test requires G1
if (!UseG1GC) {
return;
}
size_t initial_ihop = InitiatingHeapOccupancyPercent;
// G1Predictions require 5 or more samples to skip special considerations for
// small samples.
size_t num_samples = 5;
size_t target_occupancy = 100;
size_t young_reserve = 10;
double mutator_duration_s = 1.0;
double gc_pause_duration_s = 1.0;
double gc_start_time_s = mutator_duration_s;
G1IHOPTestController ctrl(true /* adaptive */, initial_ihop, target_occupancy);
for (size_t i = 0; i < num_samples; i++) {
size_t h_t0 = 100;
ctrl.mutator_phase_end_with_conc_start({
gc_start_time_s,
gc_pause_duration_s,
young_reserve,
10 /* non_hum_bytes */,
h_t0 /* hum_alloc_bytes */,
h_t0 /* total_hum_after_gc_bytes */
});
// First mutator phase:
// No new humongous allocations in this phase.
// h_t1 < h_t0 (eager reclaim)
gc_start_time_s += (gc_pause_duration_s + mutator_duration_s);
size_t h_t1 = 60;
ctrl.mutator_phase_end_with_normal_gc({
gc_start_time_s,
gc_pause_duration_s,
young_reserve,
20 /* non_hum_bytes */,
0 /* hum_alloc_bytes */,
h_t1 /* total_hum_after_gc_bytes */
});
EXPECT_EQ(0ul, ctrl.peak_extra_humongous_occupancy_bytes());
// Second mutator phase:
size_t hum_alloc_bytes = 50;
size_t h_t2 = 60;
gc_start_time_s += (gc_pause_duration_s + mutator_duration_s);
ctrl.mutator_phase_end_with_mixed_gc({
gc_start_time_s,
gc_pause_duration_s,
10 /* young_reserve_bytes */,
0 /* non_hum_bytes */,
hum_alloc_bytes,
h_t2 /* total_hum_after_gc_bytes */
});
gc_start_time_s += (gc_pause_duration_s + mutator_duration_s);
}
// Expected:
// predicted_needed = young_reserve + non_hum_bytes + peak_extra_humongous_occupancy
// = 10 + 20 + 10
// threshold = target - predicted_needed
// = 100 - 40
EXPECT_EQ(60ul, ctrl.old_gen_threshold_for_conc_mark_start());
}
TEST_VM(G1IHOPControl, adaptive_ihop_cycle_duration) {
// Test requires G1
if (!UseG1GC) {
return;
}
size_t initial_ihop = InitiatingHeapOccupancyPercent;
// G1Predictions require 5 or more samples to skip special considerations for
// small samples.
size_t num_samples = 5;
G1IHOPTestController ctrl_a(true /* adaptive */, initial_ihop, 100 /* target_occupancy */);
G1IHOPTestController ctrl_b(true /* adaptive */, initial_ihop, 100 /* target_occupancy */);
add_multiple_samples(&ctrl_a,
1.0 /* mutator_duration_s */,
1.0 /* gc_pause_duration_s */,
10 /* young_reserve */,
40 /* non_hum_bytes */,
30 /* hum_bytes */,
num_samples);
add_multiple_samples(&ctrl_b,
1.0 /* mutator_duration_s */,
2.0 /* gc_pause_duration_s */,
10 /* young_reserve */,
40 /* non_hum_bytes */,
30 /* hum_bytes */,
num_samples);
EXPECT_EQ(ctrl_a.old_gen_threshold_for_conc_mark_start(),
ctrl_b.old_gen_threshold_for_conc_mark_start());
}
TEST_VM(G1IHOPControl, adaptive_ihop_cycle_duration_scales) {
// Test requires G1
if (!UseG1GC) {
return;
}
size_t initial_ihop = InitiatingHeapOccupancyPercent;
// G1Predictions require 5 or more samples to skip special considerations for
// small samples.
size_t num_samples = 5;
size_t target_occupancy = 200;
G1IHOPTestController ctrl_short(true /* adaptive */, initial_ihop, target_occupancy);
G1IHOPTestController ctrl_long(true /* adaptive */, initial_ihop, target_occupancy);
double mutator_duration_s = 1.0;
double short_cycle_duration_s = mutator_duration_s * 2;
add_multiple_samples(&ctrl_short,
1.0 /* mutator_duration_s */,
1.0 /* gc_pause_duration_s */,
10 /* young_reserve */,
40 /* non_hum_bytes */,
30 /* hum_bytes */,
num_samples);
double long_mutator_duration_s = 4.0;
double long_cycle_duration_s = long_mutator_duration_s * 2;
add_multiple_samples(&ctrl_long,
long_mutator_duration_s,
1.0 /* gc_pause_duration_s */,
10 /* young_reserve */,
80 /* non_hum_bytes */,
30 /* hum_bytes */,
num_samples);
size_t expected_short = old_gen_threshold(200 /* target_occupancy */,
10 /* young_reserve */,
40 /* non_hum_alloc_rate */,
30 /* peak_extra_hum */,
short_cycle_duration_s);
size_t expected_long = old_gen_threshold(200 /* target_occupancy */,
10 /* young_reserve */,
(80 / long_mutator_duration_s) /* non_hum_alloc_rate */,
30 /* peak_extra_hum */,
long_cycle_duration_s);
EXPECT_EQ(ctrl_short.old_gen_threshold_for_conc_mark_start(), expected_short);
EXPECT_EQ(ctrl_long.old_gen_threshold_for_conc_mark_start(), expected_long);
EXPECT_LT(ctrl_long.old_gen_threshold_for_conc_mark_start(),
ctrl_short.old_gen_threshold_for_conc_mark_start());
}
TEST_VM(G1IHOPControl, adaptive_ihop_gc_humongous_allocation) {
// Test requires G1
if (!UseG1GC) {
return;
}
size_t initial_ihop = InitiatingHeapOccupancyPercent;
// G1Predictions require 5 or more samples to skip special considerations for
// small samples.
size_t num_samples = 5;
size_t target_occupancy_bytes = 100;
size_t young_reserve_bytes = 10;
size_t trigger_hum_bytes = 30;
double mutator_duration_s = 1.0;
double gc_pause_duration_s = 1.0;
double gc_start_time_s = mutator_duration_s;
G1IHOPTestController ctrl(true /* adaptive */, initial_ihop, target_occupancy_bytes);
for (size_t i = 0; i < num_samples; i++) {
ctrl.mutator_phase_end_with_conc_start({
gc_start_time_s,
gc_pause_duration_s,
young_reserve_bytes,
10 /* non_hum_bytes */,
0 /* hum_alloc_bytes */,
0 /* total_hum_after_gc_bytes */
});
// First mutator phase:
// No new humongous allocations in this phase.
gc_start_time_s += (gc_pause_duration_s + mutator_duration_s);
ctrl.mutator_phase_end_with_normal_gc({
gc_start_time_s,
gc_pause_duration_s,
young_reserve_bytes,
20 /* non_hum_bytes */,
0 /* hum_alloc_bytes */,
0 /* total_hum_after_gc_bytes */,
trigger_hum_bytes
});
EXPECT_EQ(trigger_hum_bytes, ctrl.peak_extra_humongous_occupancy_bytes());
// Second mutator phase:
gc_start_time_s += (gc_pause_duration_s + mutator_duration_s);
ctrl.mutator_phase_end_with_mixed_gc({
gc_start_time_s,
gc_pause_duration_s,
10 /* young_reserve_bytes */,
10 /* non_hum_bytes */,
0, /* hum_alloc_bytes */
trigger_hum_bytes /* total_hum_after_gc_bytes */
});
gc_start_time_s += (gc_pause_duration_s + mutator_duration_s);
}
// Expected:
// predicted_needed = young_reserve + non_hum_bytes + peak_extra_humongous_occupancy
// = 10 + 30 + 30
// threshold = target - predicted_needed
// = 100 - 70
EXPECT_EQ(30ul, ctrl.old_gen_threshold_for_conc_mark_start());
}
// Models humongous allocation that triggers a concurrent cycle. Make sure that this
// allocation is not counted against the peak extra humongous occupancy because
// conceptually it is considered as already allocated during concurrent cycle start.
TEST_VM(G1IHOPControl, adaptive_ihop_humongous_allocation_causes_conc_start) {
// Test requires G1
if (!UseG1GC) {
return;
}
size_t initial_ihop = InitiatingHeapOccupancyPercent;
size_t target_occupancy = 100;
G1IHOPTestController ctrl(true /* adaptive */, initial_ihop, target_occupancy);
double mutator_duration_s = 1.0;
double gc_pause_duration_s = 1.0;
double gc_start_time_s = mutator_duration_s;
size_t h_t0 = 100;
ctrl.mutator_phase_end_with_conc_start({
gc_start_time_s,
gc_pause_duration_s,
10 /* young_reserve_bytes */,
10 /* non_hum_bytes */,
0 /* hum_alloc_bytes */,
0 /* total_hum_after_gc_bytes */,
h_t0 /* gc_trigger_hum_bytes */
});
// No new humongous allocations in this phase.
// h_t1 == h_t0 (no humongous allocation, no eager reclaim, keep the same)
size_t h_t1 = h_t0;
gc_start_time_s += (gc_pause_duration_s + mutator_duration_s);
ctrl.mutator_phase_end_with_normal_gc({
gc_start_time_s,
gc_pause_duration_s,
10 /* young_reserve_bytes */,
0 /* non_hum_bytes */,
0 /* hum_alloc_bytes */,
h_t1 /* total_hum_after_gc_bytes */
});
EXPECT_EQ(0ul, ctrl.peak_extra_humongous_occupancy_bytes());
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 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
@ -51,7 +51,7 @@ public class IhopUtils {
*/
private final static String CYCLE_INITIATION_MESSAGE = "Request concurrent cycle initiation (occupancy higher than threshold)";
private final static String CYCLE_INITIATION_MESSAGE_FALSE = "Do not request concurrent cycle initiation (still doing mixed collections)";
private final static String ADAPTIVE_IHOP_PREDICTION_ACTIVE_MESSAGE = "prediction active: true";
private final static String ADAPTIVE_IHOP_MESSAGE = "Adaptive IHOP information";
/**
* Finds strings which contains patterns for finding.
@ -139,6 +139,6 @@ public class IhopUtils {
* @throws RuntimeException If IHOP message was not found.
*/
public static void checkAdaptiveIHOPWasActivated(OutputAnalyzer outputAnalyzer) {
outputAnalyzer.shouldContain(ADAPTIVE_IHOP_PREDICTION_ACTIVE_MESSAGE);
outputAnalyzer.shouldContain(ADAPTIVE_IHOP_MESSAGE);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2013, 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
@ -147,7 +147,7 @@ public class TestEventMetadata {
System.out.println("Verifying name: " + name);
Asserts.assertNotEquals(name, null, "Name not allowed to be null");
Asserts.assertTrue(name.length() > 1, "Name must be at least two characters");
Asserts.assertTrue(name.length() < 32, "Name should not exceed 32 characters");
Asserts.assertTrue(name.length() <= 48, "Name should not exceed 48 characters");
Asserts.assertFalse(isReservedKeyword(name),"Name must not be reserved keyword in the Java language (" + name + ")");
char firstChar = name.charAt(0);
Asserts.assertTrue(Character.isAlphabetic(firstChar), "Name must start with a character");