8274178: G1: Occupancy value in IHOP logging and JFR event is inaccurate

8371635: G1: Young gen allocations should never be considered when comparing against IHOP threshold

Reviewed-by: ayang, iwalulya
This commit is contained in:
Thomas Schatzl 2025-11-13 13:55:25 +00:00
parent bbc0f9ef30
commit 7d78818ae6
9 changed files with 91 additions and 78 deletions

View File

@ -353,6 +353,14 @@ size_t G1CollectedHeap::humongous_obj_size_in_regions(size_t word_size) {
return align_up(word_size, G1HeapRegion::GrainWords) / G1HeapRegion::GrainWords;
}
size_t G1CollectedHeap::allocation_used_bytes(size_t allocation_word_size) {
if (is_humongous(allocation_word_size)) {
return humongous_obj_size_in_regions(allocation_word_size) * G1HeapRegion::GrainBytes;
} else {
return allocation_word_size * HeapWordSize;
}
}
// If could fit into free regions w/o expansion, try.
// Otherwise, if can expand, do so.
// Otherwise, if using ex regions might help, try with ex given back.
@ -2955,6 +2963,15 @@ void G1CollectedHeap::abandon_collection_set() {
collection_set()->abandon();
}
size_t G1CollectedHeap::non_young_occupancy_after_allocation(size_t allocation_word_size) {
// For simplicity, just count whole regions.
const size_t cur_occupancy = (old_regions_count() + humongous_regions_count()) * G1HeapRegion::GrainBytes;
// Humongous allocations will always be assigned to non-young heap, so consider
// that allocation in the result as well. Otherwise the allocation will always
// be in young gen, so there is no need to account it here.
return cur_occupancy + (is_humongous(allocation_word_size) ? allocation_used_bytes(allocation_word_size) : 0);
}
bool G1CollectedHeap::is_old_gc_alloc_region(G1HeapRegion* hr) {
return _allocator->is_retained_old_region(hr);
}

View File

@ -1032,9 +1032,10 @@ public:
inline void old_set_add(G1HeapRegion* hr);
inline void old_set_remove(G1HeapRegion* hr);
size_t non_young_capacity_bytes() {
return (old_regions_count() + humongous_regions_count()) * G1HeapRegion::GrainBytes;
}
// Returns how much memory there is assigned to non-young heap that can not be
// allocated into any more without garbage collection after a hypothetical
// allocation of allocation_word_size.
size_t non_young_occupancy_after_allocation(size_t allocation_word_size);
// Determine whether the given region is one that we are using as an
// old GC alloc region.
@ -1226,6 +1227,10 @@ public:
// requires.
static size_t humongous_obj_size_in_regions(size_t word_size);
// Returns how much space in bytes an allocation of word_size will use up in the
// heap.
static size_t allocation_used_bytes(size_t word_size);
// Print the maximum heap capacity.
size_t max_capacity() const override;
size_t min_capacity() const;

View File

@ -366,28 +366,24 @@ static size_t target_heap_capacity(size_t used_bytes, uintx free_ratio) {
}
size_t G1HeapSizingPolicy::full_collection_resize_amount(bool& expand, size_t allocation_word_size) {
// If the full collection was triggered by an allocation failure, we should account
// for the bytes required for this allocation under used_after_gc. This prevents
// unnecessary shrinking that would be followed by an expand call to satisfy the
// allocation.
size_t allocation_bytes = allocation_word_size * HeapWordSize;
if (_g1h->is_humongous(allocation_word_size)) {
// Humongous objects are allocated in entire regions, we must calculate
// required space in terms of full regions, not just the object size.
allocation_bytes = G1HeapRegion::align_up_to_region_byte_size(allocation_bytes);
}
const size_t capacity_after_gc = _g1h->capacity();
// Capacity, free and used after the GC counted as full regions to
// include the waste in the following calculations.
const size_t capacity_after_gc = _g1h->capacity();
const size_t used_after_gc = capacity_after_gc + allocation_bytes -
_g1h->unused_committed_regions_in_bytes() -
// Discount space used by current Eden to establish a
// situation during Remark similar to at the end of full
// GC where eden is empty. During Remark there can be an
// arbitrary number of eden regions which would skew the
// results.
_g1h->eden_regions_count() * G1HeapRegion::GrainBytes;
const size_t current_used_after_gc = capacity_after_gc -
_g1h->unused_committed_regions_in_bytes() -
// Discount space used by current Eden to establish a
// situation during Remark similar to at the end of full
// GC where eden is empty. During Remark there can be an
// arbitrary number of eden regions which would skew the
// results.
_g1h->eden_regions_count() * G1HeapRegion::GrainBytes;
// Add pending allocation;
const size_t used_after_gc = current_used_after_gc +
// If the full collection was triggered by an allocation failure,
// account that allocation too. Otherwise we could shrink and then
// expand immediately to satisfy the allocation.
_g1h->allocation_used_bytes(allocation_word_size);
size_t minimum_desired_capacity = target_heap_capacity(used_after_gc, MinHeapFreeRatio);
size_t maximum_desired_capacity = target_heap_capacity(used_after_gc, MaxHeapFreeRatio);

View File

@ -44,32 +44,37 @@ 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::update_allocation_info(double allocation_time_s, size_t additional_buffer_size) {
assert(allocation_time_s >= 0.0, "Allocation time must be positive but is %.3f", allocation_time_s);
_last_allocation_time_s = allocation_time_s;
}
void G1IHOPControl::print() {
void G1IHOPControl::print_log(size_t non_young_occupancy) {
assert(_target_occupancy > 0, "Target occupancy still not updated yet.");
size_t cur_conc_mark_start_threshold = get_conc_mark_start_threshold();
log_debug(gc, ihop)("Basic information (value update), threshold: %zuB (%1.2f), target occupancy: %zuB, current occupancy: %zuB, "
log_debug(gc, ihop)("Basic information (value update), threshold: %zuB (%1.2f), target occupancy: %zuB, non-young occupancy: %zuB, "
"recent allocation size: %zuB, recent allocation duration: %1.2fms, recent old gen allocation rate: %1.2fB/s, recent marking phase length: %1.2fms",
cur_conc_mark_start_threshold,
percent_of(cur_conc_mark_start_threshold, _target_occupancy),
_target_occupancy,
G1CollectedHeap::heap()->used(),
non_young_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_length_s() * 1000.0);
}
void G1IHOPControl::send_trace_event(G1NewTracer* tracer) {
void G1IHOPControl::send_trace_event(G1NewTracer* tracer, size_t non_young_occupancy) {
assert(_target_occupancy > 0, "Target occupancy still not updated yet.");
tracer->report_basic_ihop_statistics(get_conc_mark_start_threshold(),
_target_occupancy,
G1CollectedHeap::heap()->used(),
non_young_occupancy,
_old_gen_alloc_tracker->last_period_old_gen_bytes(),
_last_allocation_time_s,
last_marking_length_s());
@ -165,27 +170,27 @@ void G1AdaptiveIHOPControl::update_marking_length(double marking_length_s) {
_marking_times_s.add(marking_length_s);
}
void G1AdaptiveIHOPControl::print() {
G1IHOPControl::print();
size_t actual_target = actual_target_threshold();
log_debug(gc, ihop)("Adaptive IHOP information (value update), threshold: %zuB (%1.2f), internal target occupancy: %zuB, "
"occupancy: %zuB, additional buffer size: %zuB, predicted old gen allocation rate: %1.2fB/s, "
void G1AdaptiveIHOPControl::print_log(size_t non_young_occupancy) {
G1IHOPControl::print_log(non_young_occupancy);
size_t actual_threshold = actual_target_threshold();
log_debug(gc, ihop)("Adaptive IHOP information (value update), threshold: %zuB (%1.2f), internal target threshold: %zuB, "
"non-young occupancy: %zuB, additional buffer size: %zuB, predicted old gen allocation rate: %1.2fB/s, "
"predicted marking phase length: %1.2fms, prediction active: %s",
get_conc_mark_start_threshold(),
percent_of(get_conc_mark_start_threshold(), actual_target),
actual_target,
G1CollectedHeap::heap()->used(),
percent_of(get_conc_mark_start_threshold(), actual_threshold),
actual_threshold,
non_young_occupancy,
_last_unrestrained_young_size,
predict(&_allocation_rate_s),
predict(&_marking_times_s) * 1000.0,
have_enough_data_for_prediction() ? "true" : "false");
}
void G1AdaptiveIHOPControl::send_trace_event(G1NewTracer* tracer) {
G1IHOPControl::send_trace_event(tracer);
void G1AdaptiveIHOPControl::send_trace_event(G1NewTracer* tracer, size_t non_young_occupancy) {
G1IHOPControl::send_trace_event(tracer, non_young_occupancy);
tracer->report_adaptive_ihop_statistics(get_conc_mark_start_threshold(),
actual_target_threshold(),
G1CollectedHeap::heap()->used(),
non_young_occupancy,
_last_unrestrained_young_size,
predict(&_allocation_rate_s),
predict(&_marking_times_s),

View File

@ -55,7 +55,11 @@ class G1IHOPControl : public CHeapObj<mtGC> {
// Most recent time from the end of the concurrent start to the start of the first
// mixed gc.
virtual double last_marking_length_s() const = 0;
public:
virtual void print_log(size_t non_young_occupancy);
virtual void send_trace_event(G1NewTracer* tracer, size_t non_young_occupancy);
public:
virtual ~G1IHOPControl() { }
// Get the current non-young occupancy at which concurrent marking should start.
@ -76,8 +80,7 @@ class G1IHOPControl : public CHeapObj<mtGC> {
// the first mixed gc.
virtual void update_marking_length(double marking_length_s) = 0;
virtual void print();
virtual void send_trace_event(G1NewTracer* tracer);
void report_statistics(G1NewTracer* tracer, size_t non_young_occupancy);
};
// The returned concurrent mark starting occupancy threshold is a fixed value
@ -139,6 +142,10 @@ class G1AdaptiveIHOPControl : public G1IHOPControl {
double last_mutator_period_old_allocation_rate() const;
protected:
virtual double last_marking_length_s() const { return _marking_times_s.last(); }
virtual void print_log(size_t non_young_occupancy);
virtual void send_trace_event(G1NewTracer* tracer, size_t non_young_occupancy);
public:
G1AdaptiveIHOPControl(double ihop_percent,
G1OldGenAllocationTracker const* old_gen_alloc_tracker,
@ -150,9 +157,6 @@ class G1AdaptiveIHOPControl : public G1IHOPControl {
virtual void update_allocation_info(double allocation_time_s, size_t additional_buffer_size);
virtual void update_marking_length(double marking_length_s);
virtual void print();
virtual void send_trace_event(G1NewTracer* tracer);
};
#endif // SHARE_GC_G1_G1IHOPCONTROL_HPP

View File

@ -749,22 +749,14 @@ bool G1Policy::need_to_start_conc_mark(const char* source, size_t allocation_wor
}
size_t marking_initiating_used_threshold = _ihop_control->get_conc_mark_start_threshold();
size_t cur_used_bytes = _g1h->non_young_capacity_bytes();
size_t allocation_byte_size = allocation_word_size * HeapWordSize;
// For humongous allocations, we need to consider that we actually use full regions
// for allocations. So compare the threshold to this size.
if (_g1h->is_humongous(allocation_word_size)) {
allocation_byte_size = G1HeapRegion::align_up_to_region_byte_size(allocation_byte_size);
}
size_t marking_request_bytes = cur_used_bytes + allocation_byte_size;
size_t non_young_occupancy = _g1h->non_young_occupancy_after_allocation(allocation_word_size);
bool result = false;
if (marking_request_bytes > marking_initiating_used_threshold) {
if (non_young_occupancy > marking_initiating_used_threshold) {
result = collector_state()->in_young_only_phase();
log_debug(gc, ergo, ihop)("%s occupancy: %zuB allocation request: %zuB threshold: %zuB (%1.2f) source: %s",
log_debug(gc, ergo, ihop)("%s non-young occupancy: %zuB allocation request: %zuB threshold: %zuB (%1.2f) source: %s",
result ? "Request concurrent cycle initiation (occupancy higher than threshold)" : "Do not request concurrent cycle initiation (still doing mixed collections)",
cur_used_bytes, allocation_byte_size, marking_initiating_used_threshold, (double) marking_initiating_used_threshold / _g1h->capacity() * 100, source);
non_young_occupancy, allocation_word_size * HeapWordSize, marking_initiating_used_threshold, (double) marking_initiating_used_threshold / _g1h->capacity() * 100, source);
}
return result;
}
@ -995,10 +987,10 @@ void G1Policy::record_young_collection_end(bool concurrent_operation_is_full_mar
update_young_length_bounds();
_old_gen_alloc_tracker.reset_after_gc(_g1h->humongous_regions_count() * G1HeapRegion::GrainBytes);
update_ihop_prediction(app_time_ms / 1000.0,
G1GCPauseTypeHelper::is_young_only_pause(this_pause));
_ihop_control->send_trace_event(_g1h->gc_tracer_stw());
if (update_ihop_prediction(app_time_ms / 1000.0,
G1GCPauseTypeHelper::is_young_only_pause(this_pause))) {
_ihop_control->report_statistics(_g1h->gc_tracer_stw(), _g1h->non_young_occupancy_after_allocation(allocation_word_size));
}
} 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.
@ -1045,7 +1037,7 @@ G1IHOPControl* G1Policy::create_ihop_control(const G1OldGenAllocationTracker* ol
}
}
void G1Policy::update_ihop_prediction(double mutator_time_s,
bool G1Policy::update_ihop_prediction(double mutator_time_s,
bool this_gc_was_young_only) {
// Always try to update IHOP prediction. Even evacuation failures give information
// about e.g. whether to start IHOP earlier next time.
@ -1082,13 +1074,7 @@ void G1Policy::update_ihop_prediction(double mutator_time_s,
report = true;
}
if (report) {
report_ihop_statistics();
}
}
void G1Policy::report_ihop_statistics() {
_ihop_control->print();
return report;
}
void G1Policy::record_young_gc_pause_end(bool evacuation_failed) {

View File

@ -60,10 +60,10 @@ class G1Policy: public CHeapObj<mtGC> {
static G1IHOPControl* create_ihop_control(const G1OldGenAllocationTracker* old_gen_alloc_tracker,
const G1Predictions* predictor);
// Update the IHOP control with necessary statistics.
void update_ihop_prediction(double mutator_time_s,
// 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,
bool this_gc_was_young_only);
void report_ihop_statistics();
G1Predictions _predictor;
G1Analytics* _analytics;

View File

@ -98,13 +98,13 @@ void G1NewTracer::report_evacuation_statistics(const G1EvacSummary& young_summar
void G1NewTracer::report_basic_ihop_statistics(size_t threshold,
size_t target_ccupancy,
size_t current_occupancy,
size_t non_young_occupancy,
size_t last_allocation_size,
double last_allocation_duration,
double last_marking_length) {
send_basic_ihop_statistics(threshold,
target_ccupancy,
current_occupancy,
non_young_occupancy,
last_allocation_size,
last_allocation_duration,
last_marking_length);
@ -206,7 +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 current_occupancy,
size_t non_young_occupancy,
size_t last_allocation_size,
double last_allocation_duration,
double last_marking_length) {
@ -216,7 +216,7 @@ void G1NewTracer::send_basic_ihop_statistics(size_t threshold,
evt.set_threshold(threshold);
evt.set_targetOccupancy(target_occupancy);
evt.set_thresholdPercentage(target_occupancy > 0 ? ((double)threshold / target_occupancy) : 0.0);
evt.set_currentOccupancy(current_occupancy);
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);

View File

@ -73,13 +73,13 @@ private:
void send_basic_ihop_statistics(size_t threshold,
size_t target_occupancy,
size_t current_occupancy,
size_t non_young_occupancy,
size_t last_allocation_size,
double last_allocation_duration,
double last_marking_length);
void send_adaptive_ihop_statistics(size_t threshold,
size_t internal_target_occupancy,
size_t current_occupancy,
size_t non_young_occupancy,
size_t additional_buffer_size,
double predicted_allocation_rate,
double predicted_marking_length,