mirror of
https://github.com/openjdk/jdk.git
synced 2026-06-06 10:42:45 +00:00
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:
parent
d3483a98d4
commit
85db081db4
@ -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));
|
||||
|
||||
131
src/hotspot/share/gc/g1/g1ConcurrentCycleTracker.cpp
Normal file
131
src/hotspot/share/gc/g1/g1ConcurrentCycleTracker.cpp
Normal 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;
|
||||
}
|
||||
111
src/hotspot/share/gc/g1/g1ConcurrentCycleTracker.hpp
Normal file
111
src/hotspot/share/gc/g1/g1ConcurrentCycleTracker.hpp
Normal 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
|
||||
@ -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
|
||||
@ -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());
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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:
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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);
|
||||
};
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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" />
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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");
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user