8383892: Shenandoah: Decouple allocation rate sampling from GC cycle

Reviewed-by: kdnilsen, ruili, serb
This commit is contained in:
William Kemper 2026-05-22 22:35:01 +00:00
parent 153257f296
commit df8ce1f50b
39 changed files with 1592 additions and 1231 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, 2019, Red Hat, Inc. All rights reserved.
* Copyright (c) 2018, 2026, Red Hat, Inc. All rights reserved.
* Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
@ -27,71 +27,11 @@
#define SHARE_GC_SHENANDOAH_HEURISTICS_SHENANDOAHADAPTIVEHEURISTICS_HPP
#include "gc/shenandoah/heuristics/shenandoahHeuristics.hpp"
#include "gc/shenandoah/shenandoahFreeSet.hpp"
#include "gc/shenandoah/shenandoahAllocRate.hpp"
#include "gc/shenandoah/shenandoahCycleDuration.hpp"
#include "gc/shenandoah/shenandoahPhaseTimings.hpp"
#include "gc/shenandoah/shenandoahRegulatorThread.hpp"
#include "gc/shenandoah/shenandoahSharedVariables.hpp"
#include "memory/allocation.hpp"
#include "utilities/numberSeq.hpp"
/**
* ShenandoahAllocationRate maintains a truncated history of recently sampled allocation rates for the purpose of providing
* informed estimates of current and future allocation rates based on weighted averages and standard deviations of the
* truncated history. More recently sampled allocations are weighted more heavily than older samples when computing
* averages and standard deviations.
*/
class ShenandoahAllocationRate : public CHeapObj<mtGC> {
public:
explicit ShenandoahAllocationRate();
// Reset the _last_sample_value to zero, _last_sample_time to current time.
void allocation_counter_reset();
// Force an allocation rate sample to be taken, even if the time since last sample is not greater than
// 1s/ShenandoahAdaptiveSampleFrequencyHz, except when current_time - _last_sample_time < MinSampleTime (2 ms).
// The sampled allocation rate is computed from (allocated - _last_sample_value) / (current_time - _last_sample_time).
// Return the newly computed rate if the sample is taken, zero if it is not an appropriate time to add a sample.
// In the case that a new sample is not taken, overwrite unaccounted_bytes_allocated with bytes allocated since
// the previous sample was taken (allocated - _last_sample_value). Otherwise, overwrite unaccounted_bytes_allocated
// with 0.
double force_sample(size_t allocated, size_t &unaccounted_bytes_allocated);
// Add an allocation rate sample if the time since last sample is greater than 1s/ShenandoahAdaptiveSampleFrequencyHz.
// The sampled allocation rate is computed from (allocated - _last_sample_value) / (current_time - _last_sample_time).
// Return the newly computed rate if the sample is taken, zero if it is not an appropriate time to add a sample.
double sample(size_t allocated);
// Return an estimate of the upper bound on allocation rate, with the upper bound computed as the weighted average
// of recently sampled instantaneous allocation rates added to sds times the standard deviation computed for the
// sequence of recently sampled average allocation rates.
double upper_bound(double sds) const;
// Test whether rate significantly diverges from the computed average allocation rate. If so, return true.
// Otherwise, return false. Significant divergence is recognized if (rate - _rate.avg()) / _rate.sd() > threshold.
bool is_spiking(double rate, double threshold) const;
private:
// Return the instantaneous rate calculated from (allocated - _last_sample_value) / (time - _last_sample_time).
// Return Sentinel value 0.0 if (time - _last_sample_time) == 0 or if (allocated <= _last_sample_value).
double instantaneous_rate(double time, size_t allocated) const;
// Time at which previous allocation rate sample was collected.
double _last_sample_time;
// Bytes allocated as of the time at which previous allocation rate sample was collected.
size_t _last_sample_value;
// The desired interval of time between consecutive samples of the allocation rate.
double _interval_sec;
// Holds a sequence of the most recently sampled instantaneous allocation rates
TruncatedSeq _rate;
// Holds a sequence of the most recently computed weighted average of allocation rates, with each weighted average
// computed immediately after an instantaneous rate was sampled
TruncatedSeq _rate_avg;
};
/*
* The adaptive heuristic tracks the allocation behavior and average cycle
@ -106,38 +46,18 @@ class ShenandoahAllocationRate : public CHeapObj<mtGC> {
*/
class ShenandoahAdaptiveHeuristics : public ShenandoahHeuristics {
public:
ShenandoahAdaptiveHeuristics(ShenandoahSpaceInfo* space_info);
explicit ShenandoahAdaptiveHeuristics(ShenandoahSpaceInfo* space_info);
virtual ~ShenandoahAdaptiveHeuristics();
void initialize() override;
virtual void initialize() override;
virtual void post_initialize() override;
virtual void adjust_penalty(intx step) override;
void post_initialize() override;
// At the end of GC(N), we idle GC until necessary to start the next GC. Compute the threshold of memory that can be allocated
// before we need to start the next GC.
void start_idle_span() override;
// Having observed a new allocation rate sample, add this to the acceleration history so that we can determine if allocation
// rate is accelerating.
void add_rate_to_acceleration_history(double timestamp, double rate);
// Compute and return the current allocation rate, the current rate of acceleration, and the amount of memory that we expect
// to consume if we start GC right now and gc takes predicted_cycle_time to complete.
size_t accelerated_consumption(double& acceleration, double& current_rate,
double avg_rate_words_per_sec, double predicted_cycle_time) const;
void choose_collection_set_from_regiondata(ShenandoahCollectionSet* cset,
RegionData* data, size_t size,
size_t actual_free) override;
void record_cycle_start() override;
void record_success_concurrent() override;
void record_degenerated() override;
void record_success_full() override;
bool should_start_gc() override;
@ -145,47 +65,33 @@ public:
bool is_diagnostic() override { return false; }
bool is_experimental() override { return false; }
// In preparation for a span during which GC will be idle, compute the headroom adjustment that will be used to
// detect when GC needs to trigger.
void compute_headroom_adjustment() override;
private:
// These are used to adjust the margin of error and the spike threshold
// in response to GC cycle outcomes. These values are shared, but the
// margin of error and spike threshold trend in opposite directions.
const static double FULL_PENALTY_SD;
const static double DEGENERATE_PENALTY_SD;
const static double MINIMUM_CONFIDENCE;
const static double MAXIMUM_CONFIDENCE;
const static double LOWEST_EXPECTED_AVAILABLE_AT_END;
const static double HIGHEST_EXPECTED_AVAILABLE_AT_END;
const static size_t GC_TIME_SAMPLE_SIZE;
friend class ShenandoahAllocationRate;
// Used to record the last trigger that signaled to start a GC.
// This itself is used to decide whether or not to adjust the margin of
// error for the average cycle time and allocation rate or the allocation
// spike detection threshold.
enum Trigger {
SPIKE, RATE, OTHER
};
void adjust_last_trigger_parameters(double amount);
void adjust_margin_of_error(double amount);
void adjust_spike_threshold(double amount);
// Returns number of words that can be allocated before we need to trigger next GC, given available in bytes.
inline size_t allocatable(size_t available) const {
return (available > _headroom_adjustment)? (available - _headroom_adjustment) / HeapWordSize: 0;
// Returns number of bytes that can be allocated before we need to trigger next GC, given available in bytes.
size_t allocatable(size_t available) const {
return available > _headroom_adjustment ? available - _headroom_adjustment : 0;
}
protected:
ShenandoahAllocationRate _allocation_rate;
void adjust_penalty(intx step) override;
void choose_collection_set_from_regiondata(ShenandoahCollectionSet* cset,
RegionData* data, size_t size,
size_t actual_free) override;
// Invocations of should_start_gc() happen approximately once per ms. Queries of allocation rate only happen if a
// a certain amount of time has passed since the previous query.
size_t _allocated_at_previous_query;
double _time_of_previous_allocation_query;
ShenandoahCycleDuration _cycles;
// Used to record the last trigger that signaled to start a GC.
// This itself is used to decide whether to adjust the margin of
// error for the average cycle time.
enum Trigger {
RATE, OTHER
};
// The margin of error expressed in standard deviations to add to our
// average cycle time and allocation rate. As this value increases we
@ -194,18 +100,9 @@ protected:
// concurrent GCs.
double _margin_of_error_sd;
// The allocation spike threshold is expressed in standard deviations.
// If the standard deviation of the most recent sample of the allocation
// rate exceeds this threshold, a GC cycle is started. As this value
// decreases the sensitivity to allocation spikes increases. In other
// words, lowering the spike threshold will tend to increase the number
// of concurrent GCs.
double _spike_threshold_sd;
// Remember which trigger is responsible for the last GC cycle. When the
// outcome of the cycle is evaluated we will adjust the parameters for the
// corresponding triggers. Note that successful outcomes will raise
// the spike threshold and lower the margin of error.
// corresponding triggers.
Trigger _last_trigger;
// Keep track of the available memory at the end of a GC cycle. This
@ -213,67 +110,29 @@ protected:
// source of feedback to adjust trigger parameters.
TruncatedSeq _available;
ShenandoahFreeSet* _free_set;
// This represents the time at which the allocation rate was most recently sampled for the purpose of detecting acceleration.
double _previous_acceleration_sample_timestamp;
size_t _total_allocations_at_start_of_idle;
// bytes of headroom at which we should trigger GC
size_t _headroom_adjustment;
// Keep track of GC_TIME_SAMPLE_SIZE most recent concurrent GC cycle times
uint _gc_time_first_sample_index;
uint _gc_time_num_samples;
double* const _gc_time_timestamps;
double* const _gc_time_samples;
double* const _gc_time_xy; // timestamp * sample
double* const _gc_time_xx; // timestamp squared
double _gc_time_sum_of_timestamps;
double _gc_time_sum_of_samples;
double _gc_time_sum_of_xy;
double _gc_time_sum_of_xx;
double _gc_time_m; // slope
double _gc_time_b; // y-intercept
double _gc_time_sd; // sd on deviance from prediction
// In preparation for a span during which GC will be idle, compute the headroom adjustment that will be used to
// detect when GC needs to trigger.
void compute_headroom_adjustment() override;
void add_gc_time(double timestamp_at_start, double duration);
void add_degenerated_gc_time(double timestamp_at_start, double duration);
double predict_gc_time(double timestamp_at_start);
// Keep track of SPIKE_ACCELERATION_SAMPLE_SIZE most recent spike allocation rate measurements. Note that it is
// typical to experience a small spike following end of GC cycle, as mutator threads refresh their TLABs. But
// there is generally an abundance of memory at this time as well, so this will not generally trigger GC.
uint _spike_acceleration_buffer_size;
uint _spike_acceleration_first_sample_index;
uint _spike_acceleration_num_samples;
double* const _spike_acceleration_rate_samples; // holds rates in words/second
double* const _spike_acceleration_rate_timestamps;
// A conservative minimum threshold of free space that we'll try to maintain when possible.
// For example, we might trigger a concurrent gc if we are likely to drop below
// this threshold, or we might consider this when dynamically resizing generations
// in the generational case. Controlled by global flag ShenandoahMinFreeThreshold.
size_t min_free_threshold();
size_t min_free_threshold(size_t capacity) const;
void accept_trigger_with_type(Trigger trigger_type) {
_last_trigger = trigger_type;
ShenandoahHeuristics::accept_trigger();
accept_trigger();
}
public:
// Sample the allocation rate at GC trigger time if possible. Return the number of allocated bytes that were
// not accounted for in the sample. This must be called before resetting bytes allocated since gc start.
size_t force_alloc_rate_sample(size_t bytes_allocated) override {
size_t unaccounted_bytes;
_allocation_rate.force_sample(bytes_allocated, unaccounted_bytes);
return unaccounted_bytes;
}
bool trigger_min_free_threshold(size_t available, size_t capacity);
bool trigger_learning(size_t available, size_t capacity);
bool trigger_average_allocation_rate(const ShenandoahAnticipatedConsumption& rate, size_t allocatable_bytes);
bool trigger_accelerating_allocation_rate(const ShenandoahAnticipatedConsumption& rate, size_t allocatable_bytes);
private:
void maybe_log_rate_trigger_parameters(const ShenandoahAnticipatedConsumption & consumption, size_t allocatable_bytes) const;
};
#endif // SHARE_GC_SHENANDOAH_HEURISTICS_SHENANDOAHADAPTIVEHEURISTICS_HPP

View File

@ -23,11 +23,11 @@
*
*/
#include "gc/shenandoah/heuristics/shenandoahCompactHeuristics.hpp"
#include "gc/shenandoah/shenandoahCollectionSet.hpp"
#include "gc/shenandoah/shenandoahHeap.inline.hpp"
#include "gc/shenandoah/shenandoahHeapRegion.inline.hpp"
#include "gc/shenandoah/shenandoahUtils.hpp"
#include "logging/log.hpp"
#include "logging/logTag.hpp"
@ -46,29 +46,27 @@ ShenandoahCompactHeuristics::ShenandoahCompactHeuristics(ShenandoahSpaceInfo* sp
}
bool ShenandoahCompactHeuristics::should_start_gc() {
size_t capacity = ShenandoahHeap::heap()->soft_max_capacity();
size_t available = _space_info->soft_mutator_available();
size_t bytes_allocated = _space_info->bytes_allocated_since_gc_start();
const size_t capacity = ShenandoahHeap::heap()->soft_max_capacity();
const size_t available = _space_info->soft_mutator_available();
const size_t bytes_allocated = estimate_bytes_allocated_since_gc_start();
log_debug(gc, ergo)("should_start_gc calculation: available: " PROPERFMT ", soft_max_capacity: " PROPERFMT ", "
"allocated_since_gc_start: " PROPERFMT,
PROPERFMTARGS(available), PROPERFMTARGS(capacity), PROPERFMTARGS(bytes_allocated));
size_t threshold_bytes_allocated = capacity / 100 * ShenandoahAllocationThreshold;
size_t min_threshold = capacity / 100 * ShenandoahMinFreeThreshold;
const size_t threshold_bytes_allocated = capacity / 100 * ShenandoahAllocationThreshold;
const size_t min_threshold = capacity / 100 * ShenandoahMinFreeThreshold;
if (available < min_threshold) {
log_trigger("Free (Soft) (%zu%s) is below minimum threshold (%zu%s)",
byte_size_in_proper_unit(available), proper_unit_for_byte_size(available),
byte_size_in_proper_unit(min_threshold), proper_unit_for_byte_size(min_threshold));
log_trigger("Free (Soft) (" PROPERFMT ") is below minimum threshold (" PROPERFMT ")",
PROPERFMTARGS(available), PROPERFMTARGS(min_threshold));
accept_trigger();
return true;
}
if (bytes_allocated > threshold_bytes_allocated) {
log_trigger("Allocated since last cycle (%zu%s) is larger than allocation threshold (%zu%s)",
byte_size_in_proper_unit(bytes_allocated), proper_unit_for_byte_size(bytes_allocated),
byte_size_in_proper_unit(threshold_bytes_allocated), proper_unit_for_byte_size(threshold_bytes_allocated));
log_trigger("Allocated since last cycle started (" PROPERFMT ") is larger than allocation threshold (" PROPERFMT ")",
PROPERFMTARGS(bytes_allocated), PROPERFMTARGS(threshold_bytes_allocated));
accept_trigger();
return true;
}
@ -80,21 +78,27 @@ void ShenandoahCompactHeuristics::choose_collection_set_from_regiondata(Shenando
RegionData* data, size_t size,
size_t actual_free) {
// Do not select too large CSet that would overflow the available free space
size_t max_cset = actual_free * 3 / 4;
const size_t max_cset = actual_free * 3 / 4;
log_info(gc, ergo)("CSet Selection. Actual Free: %zu%s, Max CSet: %zu%s",
byte_size_in_proper_unit(actual_free), proper_unit_for_byte_size(actual_free),
byte_size_in_proper_unit(max_cset), proper_unit_for_byte_size(max_cset));
size_t threshold = ShenandoahHeapRegion::region_size_bytes() * ShenandoahGarbageThreshold / 100;
log_info(gc, ergo)("CSet Selection. Actual Free: " PROPERFMT ", Max CSet: " PROPERFMT,
PROPERFMTARGS(actual_free), PROPERFMTARGS(max_cset));
const size_t threshold = ShenandoahHeapRegion::region_size_bytes() * ShenandoahGarbageThreshold / 100;
size_t live_cset = 0;
for (size_t idx = 0; idx < size; idx++) {
ShenandoahHeapRegion* r = data[idx].get_region();
size_t new_cset = live_cset + r->get_live_data_bytes();
const size_t new_cset = live_cset + r->get_live_data_bytes();
if (new_cset < max_cset && r->garbage() > threshold) {
live_cset = new_cset;
cset->add_region(r);
}
}
}
size_t ShenandoahCompactHeuristics::estimate_bytes_allocated_since_gc_start() const {
ShenandoahHeap* heap = ShenandoahHeap::heap();
const double average_allocation_rate = heap->alloc_rate().weighted_average();
const double now = os::elapsedTime();
const double elapsed_seconds = now - cycle_start_time_seconds();
return shenandoah_safe_size_cast(average_allocation_rate * elapsed_seconds);
}

View File

@ -1,5 +1,6 @@
/*
* Copyright (c) 2018, 2019, Red Hat, Inc. All rights reserved.
* Copyright (c) 2018, 2026, Red Hat, Inc. All rights reserved.
* Copyright Amazon.com Inc. 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
@ -36,14 +37,17 @@ public:
explicit ShenandoahCompactHeuristics(ShenandoahSpaceInfo* space_info);
bool should_start_gc() override;
const char* name() override { return "Compact"; }
bool is_diagnostic() override { return false; }
bool is_experimental() override { return false; }
protected:
void choose_collection_set_from_regiondata(ShenandoahCollectionSet* cset,
RegionData* data, size_t size,
size_t actual_free) override;
const char* name() override { return "Compact"; }
bool is_diagnostic() override { return false; }
bool is_experimental() override { return false; }
private:
size_t estimate_bytes_allocated_since_gc_start() const;
};
#endif // SHARE_GC_SHENANDOAH_HEURISTICS_SHENANDOAHCOMPACTHEURISTICS_HPP

View File

@ -1,6 +1,6 @@
/*
* Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* 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
@ -24,6 +24,7 @@
*/
#include "gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.hpp"
#include "gc/shenandoah/shenandoahAllocRate.inline.hpp"
#include "gc/shenandoah/shenandoahCollectionSet.hpp"
#include "gc/shenandoah/shenandoahCollectorPolicy.hpp"
#include "gc/shenandoah/shenandoahGeneration.hpp"
@ -32,6 +33,7 @@
#include "gc/shenandoah/shenandoahInPlacePromoter.hpp"
#include "gc/shenandoah/shenandoahOldGeneration.hpp"
#include "gc/shenandoah/shenandoahTrace.hpp"
#include "gc/shenandoah/shenandoahUtils.hpp"
#include "gc/shenandoah/shenandoahYoungGeneration.hpp"
#include "logging/log.hpp"
#include "utilities/quickSort.hpp"
@ -48,10 +50,16 @@ static int compare_by_aged_live(AgedRegionData a, AgedRegionData b) {
void ShenandoahGenerationalHeuristics::post_initialize() {
ShenandoahHeuristics::post_initialize();
_free_set = ShenandoahHeap::heap()->free_set();
compute_headroom_adjustment();
}
void ShenandoahGenerationalHeuristics::record_cycle_end() {
ShenandoahAdaptiveHeuristics::record_cycle_end();
ShenandoahAllocationRate& alloc_rate = ShenandoahHeap::heap()->alloc_rate();
alloc_rate.update_minimum_sample_size(_space_info->soft_mutator_available());
}
inline void assert_no_in_place_promotions() {
#ifdef ASSERT
class ShenandoahNoInPlacePromotions : public ShenandoahHeapRegionClosure {
@ -373,7 +381,7 @@ void ShenandoahGenerationalHeuristics::adjust_evacuation_budgets(ShenandoahHeap*
ShenandoahYoungGeneration* const young_generation = heap->young_generation();
const size_t old_evacuated = collection_set->get_live_bytes_in_old_regions();
size_t old_evacuated_committed = (size_t) (ShenandoahOldEvacWaste * double(old_evacuated));
size_t old_evacuated_committed = shenandoah_safe_size_cast(ShenandoahOldEvacWaste * static_cast<double>(old_evacuated));
size_t old_evacuation_reserve = old_generation->get_evacuation_reserve();
if (old_evacuated_committed > old_evacuation_reserve) {
@ -391,11 +399,11 @@ void ShenandoahGenerationalHeuristics::adjust_evacuation_budgets(ShenandoahHeap*
old_generation->set_evacuation_reserve(old_evacuation_reserve);
}
size_t young_advance_promoted = collection_set->get_live_bytes_in_tenurable_regions();
size_t young_advance_promoted_reserve_used = (size_t) (ShenandoahPromoEvacWaste * double(young_advance_promoted));
const double young_advance_promoted = collection_set->get_live_bytes_in_tenurable_regions();
size_t young_advance_promoted_reserve_used = shenandoah_safe_size_cast(ShenandoahPromoEvacWaste * young_advance_promoted);
size_t young_evacuated = collection_set->get_live_bytes_in_untenurable_regions();
size_t young_evacuated_reserve_used = (size_t) (ShenandoahEvacWaste * double(young_evacuated));
const double young_evacuated = collection_set->get_live_bytes_in_untenurable_regions();
const size_t young_evacuated_reserve_used = shenandoah_safe_size_cast(ShenandoahEvacWaste * young_evacuated);
// In top_off_collection_set(), we shrunk planned future reserve by _add_regions_to_old * region_size_bytes, but we
// didn't shrink available. The current reserve is not affected by the planned future reserve. Current available is

View File

@ -55,6 +55,9 @@ public:
void post_initialize() override;
void record_cycle_end() override;
protected:
// Wraps budget computation, subclass region selection, budget adjustment, and tracing.
void choose_collection_set_from_regiondata(ShenandoahCollectionSet* set,
RegionData* data, size_t data_size,

View File

@ -25,10 +25,11 @@
#include "gc/shenandoah/heuristics/shenandoahGlobalHeuristics.hpp"
#include "gc/shenandoah/shenandoahAsserts.hpp"
#include "gc/shenandoah/shenandoahCollectorPolicy.hpp"
#include "gc/shenandoah/shenandoahGenerationalHeap.inline.hpp"
#include "gc/shenandoah/shenandoahGlobalGeneration.hpp"
#include "gc/shenandoah/shenandoahHeapRegion.inline.hpp"
#include "gc/shenandoah/shenandoahUtils.hpp"
#include "gc/shenandoah/shenandoahYoungGeneration.hpp"
#include "utilities/quickSort.hpp"
bool ShenandoahEvacuationBudget::try_reserve(size_t bytes) {
@ -248,17 +249,17 @@ void ShenandoahGlobalCSetBudget::assert_budget_constraints_hold(size_t original_
assert(young_evac.live_bytes() * young_evac.waste_factor() <=
young_evac.reserve() + young_evac.region_count(),
"Young evac consumption (%zu) exceeds reserve (%zu) + region count (%zu)",
(size_t)(young_evac.live_bytes() * young_evac.waste_factor()),
shenandoah_safe_size_cast(young_evac.live_bytes() * young_evac.waste_factor()),
young_evac.reserve(), young_evac.region_count());
assert(old_evac.live_bytes() * old_evac.waste_factor() <=
old_evac.reserve() + old_evac.region_count(),
"Old evac consumption (%zu) exceeds reserve (%zu) + region count (%zu)",
(size_t)(old_evac.live_bytes() * old_evac.waste_factor()),
shenandoah_safe_size_cast(old_evac.live_bytes() * old_evac.waste_factor()),
old_evac.reserve(), old_evac.region_count());
assert(promo.live_bytes() * promo.waste_factor() <=
promo.reserve() + promo.region_count(),
"Promo consumption (%zu) exceeds reserve (%zu) + region count (%zu)",
(size_t)(promo.live_bytes() * promo.waste_factor()),
shenandoah_safe_size_cast(promo.live_bytes() * promo.waste_factor()),
promo.reserve(), promo.region_count());
size_t total_post_reserves = young_evac.reserve() + old_evac.reserve() + promo.reserve();

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, 2020, Red Hat, Inc. All rights reserved.
* Copyright (c) 2018, 2026, Red Hat, Inc. All rights reserved.
* Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
@ -26,10 +26,12 @@
#include "gc/shared/gcCause.hpp"
#include "gc/shenandoah/heuristics/shenandoahHeuristics.hpp"
#include "gc/shenandoah/shenandoahAllocRate.inline.hpp"
#include "gc/shenandoah/shenandoahCollectorPolicy.hpp"
#include "gc/shenandoah/shenandoahHeapRegion.inline.hpp"
#include "gc/shenandoah/shenandoahMarkingContext.inline.hpp"
#include "gc/shenandoah/shenandoahTrace.hpp"
#include "gc/shenandoah/shenandoahYoungGeneration.hpp"
#include "logging/log.hpp"
#include "logging/logTag.hpp"
#include "runtime/globals_extension.hpp"
@ -60,7 +62,6 @@ ShenandoahHeuristics::ShenandoahHeuristics(ShenandoahSpaceInfo* space_info) :
_last_cycle_end(0),
_gc_times_learned(0),
_gc_time_penalties(0),
_gc_cycle_time_history(new TruncatedSeq(Moving_Average_Samples, ShenandoahAdaptiveDecayFactor)),
_metaspace_oom()
{
size_t num_regions = ShenandoahHeap::heap()->num_regions();
@ -174,6 +175,12 @@ void ShenandoahHeuristics::record_cycle_start() {
void ShenandoahHeuristics::record_cycle_end() {
_last_cycle_end = os::elapsedTime();
ShenandoahHeap* heap = ShenandoahHeap::heap();
if (!heap->mode()->is_generational()) {
const size_t available = _space_info->soft_mutator_available();
heap->alloc_rate().update_minimum_sample_size(available);
}
}
bool ShenandoahHeuristics::should_start_gc() {
@ -247,7 +254,6 @@ void ShenandoahHeuristics::log_trigger(const char* fmt, ...) {
}
void ShenandoahHeuristics::record_success_concurrent() {
_gc_cycle_time_history->add(elapsed_cycle_time());
_gc_times_learned++;
adjust_penalty(Concurrent_Adjust);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, 2019, Red Hat, Inc. All rights reserved.
* Copyright (c) 2018, 2026, Red Hat, Inc. All rights reserved.
* Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
@ -83,7 +83,7 @@ private:
double _most_recent_planned_sleep_interval;
protected:
static const uint Moving_Average_Samples = 10; // Number of samples to store in moving averages
static constexpr uint Moving_Average_Samples = 10; // Number of samples to store in moving averages
bool _start_gc_is_pending; // True denotes that GC has been triggered, so no need to trigger again.
size_t _declined_trigger_count; // This counts how many times since previous GC finished that this
@ -181,7 +181,6 @@ protected:
size_t _gc_times_learned;
intx _gc_time_penalties;
TruncatedSeq* _gc_cycle_time_history;
// There may be many threads that contend to set this flag
ShenandoahSharedFlag _metaspace_oom;
@ -230,6 +229,10 @@ public:
// Default implementation does nothing.
}
double cycle_start_time_seconds() const {
return _cycle_start;
}
virtual void record_cycle_start();
void record_degenerated_cycle_start(bool out_of_cycle);
@ -278,11 +281,6 @@ public:
double elapsed_cycle_time() const;
double elapsed_degenerated_cycle_time() const;
virtual size_t force_alloc_rate_sample(size_t bytes_allocated) {
// do nothing
return 0;
}
// Format prefix and emit log message indicating a GC cycle hs been triggered
void log_trigger(const char* fmt, ...) ATTRIBUTE_PRINTF(2, 3);

View File

@ -44,11 +44,6 @@ public:
virtual size_t available() const = 0;
virtual size_t used() const = 0;
// Return an approximation of the bytes allocated since GC start. The value returned is monotonically non-decreasing
// in time within each GC cycle. For certain GC cycles, the value returned may include some bytes allocated before
// the start of the current GC cycle.
virtual size_t bytes_allocated_since_gc_start() const = 0;
// Return true if this region belongs to this space.
virtual bool contains(ShenandoahHeapRegion* region) const = 0;
};

View File

@ -40,14 +40,11 @@ ShenandoahStaticHeuristics::ShenandoahStaticHeuristics(ShenandoahSpaceInfo* spac
bool ShenandoahStaticHeuristics::should_start_gc() {
size_t capacity = ShenandoahHeap::heap()->soft_max_capacity();
size_t available = _space_info->soft_mutator_available();
size_t allocated = _space_info->bytes_allocated_since_gc_start();
log_debug(gc, ergo)("should_start_gc calculation: available: " PROPERFMT ", soft_max_capacity: " PROPERFMT ", "
"allocated_since_gc_start: " PROPERFMT,
PROPERFMTARGS(available), PROPERFMTARGS(capacity), PROPERFMTARGS(allocated));
log_debug(gc, ergo)("should_start_gc calculation: available: " PROPERFMT ", soft_max_capacity: " PROPERFMT,
PROPERFMTARGS(available), PROPERFMTARGS(capacity));
size_t threshold_available = capacity / 100 * ShenandoahMinFreeThreshold;
if (available < threshold_available) {
log_trigger("Free (Soft) (" PROPERFMT ") is below minimum threshold (" PROPERFMT ")",
PROPERFMTARGS(available), PROPERFMTARGS(threshold_available));

View File

@ -1,6 +1,6 @@
/*
* Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* 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
@ -23,13 +23,17 @@
*
*/
#include "gc/shared/gc_globals.hpp"
#include "gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.hpp"
#include "gc/shenandoah/heuristics/shenandoahOldHeuristics.hpp"
#include "gc/shenandoah/heuristics/shenandoahYoungHeuristics.hpp"
#include "gc/shenandoah/shenandoahCollectorPolicy.hpp"
#include "gc/shenandoah/shenandoahAllocRate.inline.hpp"
#include "gc/shenandoah/shenandoahGenerationalHeap.inline.hpp"
#include "gc/shenandoah/shenandoahHeap.hpp"
#include "gc/shenandoah/shenandoahHeapRegion.inline.hpp"
#include "gc/shenandoah/shenandoahOldGeneration.hpp"
#include "gc/shenandoah/shenandoahYoungGeneration.hpp"
#include "utilities/globalDefinitions.hpp"
#include "utilities/quickSort.hpp"
ShenandoahYoungHeuristics::ShenandoahYoungHeuristics(ShenandoahYoungGeneration* generation)
@ -80,7 +84,7 @@ void ShenandoahYoungHeuristics::choose_young_collection_set(ShenandoahCollection
// If this is mixed evacuation, the old-gen candidate regions have already been added.
size_t cur_cset = 0;
size_t cur_young_garbage = cset->garbage();
const size_t max_cset = (size_t) (heap->young_generation()->get_evacuation_reserve() / ShenandoahEvacWaste);
const size_t max_cset = shenandoah_safe_size_cast(heap->young_generation()->get_evacuation_reserve() / ShenandoahEvacWaste);
const size_t free_target = (capacity * ShenandoahMinFreeThreshold) / 100 + max_cset;
const size_t min_garbage = (free_target > actual_free) ? (free_target - actual_free) : 0;
@ -113,38 +117,24 @@ void ShenandoahYoungHeuristics::choose_young_collection_set(ShenandoahCollection
}
}
bool ShenandoahYoungHeuristics::should_start_gc() {
auto heap = ShenandoahGenerationalHeap::heap();
ShenandoahOldGeneration* old_generation = heap->old_generation();
ShenandoahOldHeuristics* old_heuristics = old_generation->heuristics();
// Checks that an old cycle has run for at least ShenandoahMinimumOldTimeMs before allowing a young cycle.
bool ShenandoahYoungHeuristics::old_collection_needs_more_time(ShenandoahOldGeneration* old_generation, ShenandoahOldHeuristics* old_heuristics) {
if (ShenandoahMinimumOldTimeMs > 0) {
if (old_generation->is_preparing_for_mark() || old_generation->is_concurrent_mark_in_progress()) {
size_t old_time_elapsed = size_t(old_heuristics->elapsed_cycle_time() * 1000);
if (old_time_elapsed < ShenandoahMinimumOldTimeMs) {
// Do not decline_trigger() when waiting for minimum quantum of Old-gen marking. It is not at our discretion
// to trigger at this time.
log_debug(gc)("Young heuristics declines to trigger because old_time_elapsed < ShenandoahMinimumOldTimeMs");
return false;
}
const auto old_time_elapsed = shenandoah_safe_size_cast(old_heuristics->elapsed_cycle_time() * 1000);
return old_time_elapsed < ShenandoahMinimumOldTimeMs;
}
}
return false;
}
// inherited triggers have already decided to start a cycle, so no further evaluation is required
if (ShenandoahAdaptiveHeuristics::should_start_gc()) {
// ShenandoahAdaptiveHeuristics::should_start_gc() has already accepted trigger, or declined it.
return true;
}
bool ShenandoahYoungHeuristics::trigger_expedite_promotions(ShenandoahGenerationalHeap* heap, ShenandoahOldGeneration* old_generation) {
// Get through promotions and mixed evacuations as quickly as possible. These cycles sometimes require significantly
// more time than traditional young-generation cycles so start them up as soon as possible. This is a "mitigation"
// for the reality that old-gen and young-gen activities are not truly "concurrent". If there is old-gen work to
// be done, we start up the young-gen GC threads so they can do some of this old-gen work. As implemented, promotion
// gets priority over old-gen marking.
size_t promo_expedite_threshold = percent_of(heap->young_generation()->max_capacity(), ShenandoahExpeditePromotionsThreshold);
size_t promo_potential = old_generation->get_promotion_potential();
const size_t promo_expedite_threshold = percent_of(heap->young_generation()->max_capacity(), ShenandoahExpeditePromotionsThreshold);
const size_t promo_potential = old_generation->get_promotion_potential();
if (promo_potential > promo_expedite_threshold) {
// Detect unsigned arithmetic underflow
assert(promo_potential < heap->capacity(), "Sanity");
@ -152,8 +142,11 @@ bool ShenandoahYoungHeuristics::should_start_gc() {
accept_trigger();
return true;
}
return false;
}
size_t mixed_candidates = old_heuristics->unprocessed_old_collection_candidates();
bool ShenandoahYoungHeuristics::trigger_expedite_mixed(ShenandoahGenerationalHeap* heap, ShenandoahOldHeuristics* old_heuristics) {
const size_t mixed_candidates = old_heuristics->unprocessed_old_collection_candidates();
if (mixed_candidates > ShenandoahExpediteMixedThreshold && !heap->is_concurrent_weak_root_in_progress()) {
// We need to run young GC in order to open up some free heap regions so we can finish mixed evacuations.
// If concurrent weak root processing is in progress, it means the old cycle has chosen mixed collection
@ -163,6 +156,33 @@ bool ShenandoahYoungHeuristics::should_start_gc() {
accept_trigger();
return true;
}
return false;
}
bool ShenandoahYoungHeuristics::should_start_gc() {
auto heap = ShenandoahGenerationalHeap::heap();
ShenandoahOldGeneration* old_generation = heap->old_generation();
ShenandoahOldHeuristics* old_heuristics = old_generation->heuristics();
// Checks that an old cycle has run for at least ShenandoahMinimumOldTimeMs before allowing a young cycle.
if (old_collection_needs_more_time(old_generation, old_heuristics)) {
log_debug(gc)("Young heuristics declines to trigger because old_time_elapsed < ShenandoahMinimumOldTimeMs");
return false;
}
if (ShenandoahAdaptiveHeuristics::should_start_gc()) {
// Inherited triggers have already decided to start a cycle, so no further evaluation is required
// ShenandoahAdaptiveHeuristics::should_start_gc() has already accepted trigger, or declined it.
return true;
}
if (trigger_expedite_promotions(heap, old_generation)) {
return true;
}
if (trigger_expedite_mixed(heap, old_heuristics)) {
return true;
}
// Don't decline_trigger() here That was done in ShenandoahAdaptiveHeuristics::should_start_gc()
return false;
@ -173,20 +193,16 @@ bool ShenandoahYoungHeuristics::should_start_gc() {
// generation at the end of the current cycle (as represented by young_regions_to_be_reclaimed) and on the anticipated
// amount of time required to perform a GC.
size_t ShenandoahYoungHeuristics::bytes_of_allocation_runway_before_gc_trigger(size_t young_regions_to_be_reclaimed) {
size_t capacity = _space_info->max_capacity();
size_t usage = _space_info->used();
size_t available = (capacity > usage)? capacity - usage: 0;
size_t allocated = _free_set->get_bytes_allocated_since_gc_start();
size_t anticipated_available = available + young_regions_to_be_reclaimed * ShenandoahHeapRegion::region_size_bytes();
const size_t capacity = _space_info->max_capacity();
const size_t usage = _space_info->used();
const size_t available = (capacity > usage) ? capacity - usage: 0;
const size_t anticipated_available = available + young_regions_to_be_reclaimed * ShenandoahHeapRegion::region_size_bytes();
size_t spike_headroom = capacity * ShenandoahAllocSpikeFactor / 100;
size_t penalties = capacity * _gc_time_penalties / 100;
const size_t spike_headroom = capacity * ShenandoahAllocSpikeFactor / 100;
const size_t penalties = capacity * _gc_time_penalties / 100;
double rate = _allocation_rate.sample(allocated);
// At what value of available, would avg and spike triggers occur?
// At what value of available, would avg rate trigger occur?
// if allocation_headroom < avg_cycle_time * avg_alloc_rate, then we experience avg trigger
// if allocation_headroom < avg_cycle_time * rate, then we experience spike trigger if is_spiking
//
// allocation_headroom =
// 0, if penalties > available or if penalties + spike_headroom > available
@ -199,34 +215,19 @@ size_t ShenandoahYoungHeuristics::bytes_of_allocation_runway_before_gc_trigger(s
// since avg_cycle_time * avg_alloc_rate > 0, the first test is sufficient to test both conditions
//
// thus, evac_slack_avg is MIN2(0, available - avg_cycle_time * avg_alloc_rate + penalties + spike_headroom)
//
// similarly, evac_slack_spiking is MIN2(0, available - avg_cycle_time * rate + penalties + spike_headroom)
// but evac_slack_spiking is only relevant if is_spiking, as defined below.
double avg_cycle_time = _gc_cycle_time_history->davg() + (_margin_of_error_sd * _gc_cycle_time_history->dsd());
double avg_alloc_rate = _allocation_rate.upper_bound(_margin_of_error_sd);
const double avg_cycle_time = _cycles.predict_duration(os::elapsedTime(), _margin_of_error_sd);
const double avg_alloc_rate = ShenandoahHeap::heap()->alloc_rate().upper_bound(_margin_of_error_sd);
const double remaining_before_gc = avg_cycle_time * avg_alloc_rate + penalties + spike_headroom;
size_t evac_slack_avg;
if (anticipated_available > avg_cycle_time * avg_alloc_rate + penalties + spike_headroom) {
evac_slack_avg = anticipated_available - (avg_cycle_time * avg_alloc_rate + penalties + spike_headroom);
if (anticipated_available > remaining_before_gc) {
evac_slack_avg = shenandoah_safe_size_cast(anticipated_available - remaining_before_gc);
} else {
// we have no slack because it's already time to trigger
evac_slack_avg = 0;
}
bool is_spiking = _allocation_rate.is_spiking(rate, _spike_threshold_sd);
size_t evac_slack_spiking;
if (is_spiking) {
if (anticipated_available > avg_cycle_time * rate + penalties + spike_headroom) {
evac_slack_spiking = anticipated_available - (avg_cycle_time * rate + penalties + spike_headroom);
} else {
// we have no slack because it's already time to trigger
evac_slack_spiking = 0;
}
} else {
evac_slack_spiking = evac_slack_avg;
}
size_t threshold = min_free_threshold();
size_t evac_min_threshold = (anticipated_available > threshold)? anticipated_available - threshold: 0;
return MIN3(evac_slack_spiking, evac_slack_avg, evac_min_threshold);
const size_t threshold = min_free_threshold(capacity);
const size_t evac_min_threshold = anticipated_available > threshold ? anticipated_available - threshold : 0;
return MIN2(evac_slack_avg, evac_min_threshold);
}

View File

@ -27,6 +27,8 @@
#include "gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.hpp"
class ShenandoahYoungGeneration;
class ShenandoahOldGeneration;
class ShenandoahOldHeuristics;
/*
* This is a specialization of the generational heuristic which chooses
@ -37,20 +39,26 @@ class ShenandoahYoungHeuristics : public ShenandoahGenerationalHeuristics {
public:
explicit ShenandoahYoungHeuristics(ShenandoahYoungGeneration* generation);
void select_collection_set_regions(ShenandoahCollectionSet* cset,
RegionData* data, size_t size,
size_t actual_free) override;
bool should_start_gc() override;
size_t bytes_of_allocation_runway_before_gc_trigger(size_t young_regions_to_be_reclaimed);
protected:
void select_collection_set_regions(ShenandoahCollectionSet* cset,
RegionData* data, size_t size,
size_t actual_free) override;
private:
void choose_young_collection_set(ShenandoahCollectionSet* cset,
const RegionData* data,
size_t size, size_t actual_free) const;
bool old_collection_needs_more_time(ShenandoahOldGeneration* old_generation,
ShenandoahOldHeuristics* old_heuristics);
bool trigger_expedite_promotions(ShenandoahGenerationalHeap* heap, ShenandoahOldGeneration* old_generation);
bool trigger_expedite_mixed(ShenandoahGenerationalHeap* heap, ShenandoahOldHeuristics* old_heuristics);
};
#endif // SHARE_GC_SHENANDOAH_HEURISTICS_SHENANDOAHYOUNGHEURISTICS_HPP

View File

@ -0,0 +1,175 @@
/*
* Copyright Amazon.com Inc. 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_SHENANDOAH_SHENANDOAHALLOCRATE_HPP
#define SHARE_GC_SHENANDOAH_SHENANDOAHALLOCRATE_HPP
#include "gc/shenandoah/shenandoahWeightedSeq.hpp"
#include "runtime/atomic.hpp"
#include "runtime/mutex.hpp"
#include "runtime/mutexLocker.hpp"
#include "runtime/os.hpp"
#include "utilities/globalDefinitions.hpp"
class ShenandoahAllocationClock {
public:
static jlong elapsed_counter() {
return os::elapsed_counter();
}
static jlong elapsed_frequency() {
return os::elapsed_frequency();
}
};
// Snapshot values used by heuristic triggers to avoid lock contention
struct ShenandoahAnticipatedConsumption {
template<typename Clock> friend class ShenandoahAllocRate;
explicit ShenandoahAnticipatedConsumption(double duration_seconds)
: _duration_seconds(duration_seconds)
, _baseline(0.0)
, _momentary(0.0)
, _acceleration(0.0)
, _predicted_rate(0.0) {
}
// Anticipated duration in seconds of next gc cycle
double duration_seconds() const {
return _duration_seconds;
}
// Consumption in bytes based on baseline allocation rate for the next gc cycle
size_t baseline_consumption() const;
double baseline_rate() const {
return _baseline;
}
// Consumption in bytes based on momentary allocation rate for the next gc cycle
size_t momentary_consumption() const;
double momentary_rate() const {
return _momentary;
}
// Consumption in bytes based on an accelerating allocation rate for the next gc cycle
size_t accelerated_consumption() const;
// The acceleration of the allocation rate (based on slope of linear regression)
double acceleration() const {
return _acceleration;
}
// Predicated allocation rate based on weighted linear regression
double predicted_rate() const {
return _predicted_rate;
}
private:
double _duration_seconds;
double _baseline;
double _momentary;
double _acceleration;
double _predicted_rate;
};
// This class tracks three moving averages of the allocation rate:
// 1. Momentary: this is the shortest and acts as a sort of 'spike' detector
// 2. Recent: larger than momentary, these samples are used to detect 'acceleration' of the rate
// 3. Baseline: the largest sample window, this is meant to establish the baseline allocation rate
//
// Samples are taken whenever the accumulating count of bytes allocated exceeds the
// minimum sample size. The minimum sample size is generally derived from the heap
// capacity. The thinking is that larger heaps require less frequent sampling. Note
// that as the allocation rate increases, the timeliness of the averages and other
// estimates increases.
template<typename Clock = ShenandoahAllocationClock>
class ShenandoahAllocRate {
static constexpr size_t ALLOC_SAMPLE_PORTION = 128;
static constexpr size_t ALLOC_SAMPLE_MIN = M;
static constexpr size_t ALLOC_SAMPLE_MAX = G;
PaddedMonitor _sample_lock;
Atomic<size_t> _allocated_bytes_since_last_sample;
Atomic<size_t> _minimum_sample_size; // bytes, read by mutator, updated by gc
jlong _last_sample_time;
ShenandoahWeightedSeq _baseline;
ShenandoahWeightedSeq _recent;
ShenandoahWeightedSeq _momentary;
public:
explicit ShenandoahAllocRate(const uint minimum_sample_size = ALLOC_SAMPLE_MIN,
const uint baseline_window_size = ShenandoahAllocRateSampleWindow,
const uint recent_window_size = ShenandoahRecentAllocRateSampleWindow,
const uint momentary_window_size = ShenandoahMomentaryAllocRateSampleWindow)
: _sample_lock(Mutex::nosafepoint - 2, "ShenandoahAllocSample_lock", true)
, _allocated_bytes_since_last_sample(0)
, _minimum_sample_size(minimum_sample_size)
, _last_sample_time(Clock::elapsed_counter())
, _baseline(baseline_window_size)
, _recent(recent_window_size)
, _momentary(momentary_window_size)
{
}
// Update minimum sample size based on the given available bytes
void update_minimum_sample_size(size_t available);
// Set minimum sample size in bytes
void set_minimum_sample_size(const size_t minimum_sample_size) {
_minimum_sample_size.store_relaxed(minimum_sample_size);
}
// Indicate that this many bytes have been allocated (by the mutator).
void allocated(size_t allocated_bytes);
// Returns a snapshot of the parameters necessary to evaluate allocation rate triggers.
// Note that momentary consumption and accelerated consumption may both be zero, but may
// not both be non-zero. The `time_delta` parameter is the anticipated duration of the
// next gc cycle. The `standard_deviations` parameter is the margin of error applied to
// the baseline allocation rate expressed as a multiple of the standard deviation.
ShenandoahAnticipatedConsumption snapshot(double time_delta, double standard_deviations);
// Returns the weighted average of the samples.
double weighted_average() {
MonitorLocker locker(&_sample_lock, Mutex::_no_safepoint_check_flag);
return _baseline.weighted_average();
}
// Returns the upper bound of the confidence interval about the mean in terms of the given deviation.
double upper_bound(const double standard_deviations) {
MonitorLocker locker(&_sample_lock, Mutex::_no_safepoint_check_flag);
return upper_bound_no_lock(standard_deviations);
}
private:
double upper_bound_no_lock(const double standard_deviations) const {
assert(_sample_lock.is_locked(), "Caller must hold lock");
return _baseline.weighted_average() + standard_deviations * _baseline.weighted_sd();
}
};
typedef ShenandoahAllocRate<> ShenandoahAllocationRate;
#endif // SHARE_GC_SHENANDOAH_SHENANDOAHALLOCRATE_HPP

View File

@ -0,0 +1,120 @@
/*
* Copyright Amazon.com Inc. 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_SHENANDOAH_SHENANDOAHALLOCRATE_INLINE_HPP
#define SHARE_GC_SHENANDOAH_SHENANDOAHALLOCRATE_INLINE_HPP
#include "gc/shenandoah/shenandoahAllocRate.hpp"
#include "gc/shenandoah/shenandoahUtils.hpp"
#include "logging/log.hpp"
inline size_t ShenandoahAnticipatedConsumption::baseline_consumption() const {
return shenandoah_safe_size_cast(_baseline * _duration_seconds);
}
inline size_t ShenandoahAnticipatedConsumption::momentary_consumption() const {
return shenandoah_safe_size_cast(_momentary * _duration_seconds);
}
inline size_t ShenandoahAnticipatedConsumption::accelerated_consumption() const {
const double consumption = _predicted_rate * _duration_seconds + 0.5 * _acceleration * _duration_seconds * _duration_seconds;
return shenandoah_safe_size_cast(consumption);
}
template<typename Clock>
void ShenandoahAllocRate<Clock>::update_minimum_sample_size(const size_t available) {
const size_t min_sample_size = clamp(available / ALLOC_SAMPLE_PORTION, ALLOC_SAMPLE_MIN, ALLOC_SAMPLE_MAX);
log_info(gc, ergo)("Adjust minimum allocation sample size to: " PROPERFMT, PROPERFMTARGS(min_sample_size));
set_minimum_sample_size(min_sample_size);
}
template<typename Clock>
void ShenandoahAllocRate<Clock>::allocated(const size_t allocated_bytes) {
size_t unsampled = _allocated_bytes_since_last_sample.add_then_fetch(allocated_bytes);
const size_t minimum_sample_size = _minimum_sample_size.load_relaxed();
if (unsampled < minimum_sample_size) {
// Not enough to sample yet
return;
}
if (!_sample_lock.try_lock()) {
// Another thread has the lock and will take the sample
return;
}
unsampled = _allocated_bytes_since_last_sample.load_relaxed();
if (unsampled < minimum_sample_size) {
// Another thread has sampled and reset the allocated bytes under the lock
_sample_lock.unlock();
return;
}
const jlong now = Clock::elapsed_counter();
const jlong elapsed = now - _last_sample_time;
if (elapsed <= 0) {
// Avoid sampling nonsense allocation rates
_sample_lock.unlock();
return;
}
_last_sample_time = now;
// We are recording this sample, deduct it from the counter. It may be increased
// concurrently by other threads outside the lock, so we still use an atomic access.
_allocated_bytes_since_last_sample.sub_then_fetch(unsampled);
const double timestamp = static_cast<double>(_last_sample_time) / Clock::elapsed_frequency();
const double rate_seconds = static_cast<double>(unsampled) * Clock::elapsed_frequency() / elapsed;
_baseline.add(timestamp, rate_seconds);
_recent.add(timestamp, rate_seconds);
_momentary.add(timestamp, rate_seconds);
_sample_lock.unlock();
log_trace(gc, sampling)("Recorded %.3f/s at %.3fs", rate_seconds, timestamp);
}
template<typename Clock>
ShenandoahAnticipatedConsumption ShenandoahAllocRate<Clock>::snapshot(const double time_delta, const double standard_deviations) {
ShenandoahAnticipatedConsumption result(time_delta);
MonitorLocker locker(&_sample_lock, Mutex::_no_safepoint_check_flag);
result._baseline = upper_bound_no_lock(standard_deviations);
if (_recent.weighted_average() <= _baseline.weighted_average()) {
// We are not accelerating, just use the momentary average.
result._momentary = _momentary.weighted_average();
} else {
result._acceleration = _recent.slope();
result._predicted_rate = _recent.predict_y(_recent.last());
}
return result;
}
#endif // SHARE_GC_SHENANDOAH_SHENANDOAHALLOCRATE_INLINE_HPP

View File

@ -193,7 +193,7 @@ void ShenandoahArguments::initialize() {
// TLAB sizing policy makes resizing decisions before each GC cycle. It averages
// historical data, assigning more recent data the weight according to TLABAllocationWeight.
// Current default is good for generational collectors that run frequent young GCs.
// With Shenandoah, GC cycles are much less frequent, so we need we need sizing policy
// With Shenandoah, GC cycles are much less frequent, so we need sizing policy
// to converge faster over smaller number of resizing decisions.
if (strcmp(ShenandoahGCMode, "generational") && FLAG_IS_DEFAULT(TLABAllocationWeight)) {
FLAG_SET_DEFAULT(TLABAllocationWeight, 90);
@ -202,7 +202,7 @@ void ShenandoahArguments::initialize() {
if (GCCardSizeInBytes < ShenandoahMinCardSizeInBytes) {
vm_exit_during_initialization(
err_msg("GCCardSizeInBytes ( %u ) must be >= %u\n", GCCardSizeInBytes, (unsigned int) ShenandoahMinCardSizeInBytes));
err_msg("GCCardSizeInBytes ( %u ) must be >= %u\n", GCCardSizeInBytes, ShenandoahMinCardSizeInBytes));
}
// Gen shen does not support any ShenandoahGCHeuristics value except for the default "adaptive"
@ -213,6 +213,16 @@ void ShenandoahArguments::initialize() {
FLAG_SET_ERGO(ShenandoahGCHeuristics, "adaptive");
}
if (ShenandoahMomentaryAllocRateSampleWindow > ShenandoahRecentAllocRateSampleWindow
|| ShenandoahRecentAllocRateSampleWindow > ShenandoahAllocRateSampleWindow) {
vm_exit_during_initialization(
err_msg("Relation must hold: ShenandoahMomentaryAllocRateSampleWindow (%u) "
"<= ShenandoahRecentAllocRateSampleWindow (%u) "
"<= ShenandoahAllocRateSampleWindow (%u)",
ShenandoahMomentaryAllocRateSampleWindow, ShenandoahRecentAllocRateSampleWindow,
ShenandoahAllocRateSampleWindow));
}
FullGCForwarding::initialize_flags(MaxHeapSize);
}

View File

@ -138,11 +138,6 @@ void ShenandoahControlThread::run_service() {
heuristics->cancel_trigger_request();
if (mode != stw_degenerated) {
// If mode is stw_degenerated, count bytes allocated from the start of the conc GC that experienced alloc failure.
heap->reset_bytes_allocated_since_gc_start();
}
MetaspaceCombinedStats meta_sizes = MetaspaceUtils::get_combined_statistics();
// If GC was requested, we are sampling the counters even without actual triggers

View File

@ -0,0 +1,53 @@
/*
* Copyright Amazon.com Inc. 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/shenandoah/shenandoahCycleDuration.hpp"
#include "logging/log.hpp"
#include "logging/logTag.hpp"
#include "runtime/mutexLocker.hpp"
#include <cmath>
ShenandoahCycleDuration::ShenandoahCycleDuration(uint size)
: _gc_times_lock(Mutex::nosafepoint - 2, "ShenandoahCycleTimes_lock", true)
, _gc_times(size) {}
void ShenandoahCycleDuration::record_duration(double timestamp_at_start, double duration) {
log_debug(gc, sampling)("Cycle started at: %.3f, completed in %.3fs", timestamp_at_start, duration);
MonitorLocker locker(&_gc_times_lock, Mutex::_no_safepoint_check_flag);
_gc_times.add(timestamp_at_start, duration);
}
double ShenandoahCycleDuration::predict_duration(double timestamp_at_start, double margin_of_error) {
MonitorLocker locker(&_gc_times_lock, Mutex::_no_safepoint_check_flag);
const double prediction = _gc_times.predict_y(timestamp_at_start);
if (std::isfinite(prediction) && prediction > 0.0) {
return prediction + _gc_times.residual_sd() * margin_of_error;
}
// return average time, rather than negative or zero time
return _gc_times.average() + _gc_times.sd() * margin_of_error;
}

View File

@ -0,0 +1,47 @@
/*
* Copyright Amazon.com Inc. 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_SHENANDOAH_SHENANDOAHCYCLEDURATION_HPP
#define SHARE_GC_SHENANDOAH_SHENANDOAHCYCLEDURATION_HPP
#include "gc/shenandoah/shenandoahWeightedSeq.hpp"
#include "runtime/mutex.hpp"
class ShenandoahCycleDuration {
// To enable detection of GC time trends, we keep separate track of the recent history of gc time. During initialization,
// for example, the amount of live memory may be increasing, which is likely to cause the GC times to increase. This history
// allows us to predict increasing GC times rather than always assuming average recent GC time is the best predictor.
static constexpr uint GC_TIME_SAMPLE_SIZE = 15;
// Written by control thread, read by regulator thread
Monitor _gc_times_lock;
ShenandoahWeightedSeq _gc_times;
public:
explicit ShenandoahCycleDuration(uint size = GC_TIME_SAMPLE_SIZE);
void record_duration(double timestamp_at_start, double duration);
double predict_duration(double timestamp_at_start, double margin_of_error);
};
#endif // SHARE_GC_SHENANDOAH_SHENANDOAHCYCLEDURATION_HPP

View File

@ -289,32 +289,6 @@ void ShenandoahFreeSet::resize_old_collector_capacity(size_t regions) {
// else, old generation is already appropriately sized
}
void ShenandoahFreeSet::reset_bytes_allocated_since_gc_start(size_t initial_bytes_allocated) {
shenandoah_assert_heaplocked();
// Future inquiries of get_total_bytes_allocated() will return the sum of
// _total_bytes_previously_allocated and _mutator_bytes_allocated_since_gc_start.
// Since _mutator_bytes_allocated_since_gc_start does not start at zero, we subtract initial_bytes_allocated so as
// to not double count these allocated bytes.
size_t original_mutator_bytes_allocated_since_gc_start = _mutator_bytes_allocated_since_gc_start;
// Setting _mutator_bytes_allocated_since_gc_start before _total_bytes_previously_allocated reduces the damage
// in the case that the control or regulator thread queries get_bytes_allocated_since_previous_sample() between
// the two assignments.
//
// These are not declared as volatile so the compiler or hardware may reorder the assignments. The implementation of
// get_bytes_allocated_since_previous_cycle() is robust to this possibility, as are triggering heuristics. The current
// implementation assumes we are better off to tolerate the very rare race rather than impose a synchronization penalty
// on every update and fetch. (Perhaps it would be better to make the opposite tradeoff for improved maintainability.)
_mutator_bytes_allocated_since_gc_start = initial_bytes_allocated;
_total_bytes_previously_allocated += original_mutator_bytes_allocated_since_gc_start - initial_bytes_allocated;
}
void ShenandoahFreeSet::increase_bytes_allocated(size_t bytes) {
shenandoah_assert_heaplocked();
_mutator_bytes_allocated_since_gc_start += bytes;
}
inline idx_t ShenandoahRegionPartitions::leftmost(ShenandoahFreeSetPartitionId which_partition) const {
assert (which_partition < NumPartitions, "selected free partition must be valid");
idx_t idx = _leftmosts[int(which_partition)];
@ -1229,8 +1203,6 @@ inline void ShenandoahRegionPartitions::assert_bounds_sanity() {
ShenandoahFreeSet::ShenandoahFreeSet(ShenandoahHeap* heap, size_t max_regions) :
_heap(heap),
_partitions(max_regions, this),
_total_bytes_previously_allocated(0),
_mutator_bytes_at_last_sample(0),
_total_humongous_waste(0),
_alloc_bias_weight(0),
_total_young_used(0),
@ -1242,8 +1214,7 @@ ShenandoahFreeSet::ShenandoahFreeSet(ShenandoahHeap* heap, size_t max_regions) :
_young_unaffiliated_regions(0),
_global_unaffiliated_regions(0),
_total_young_regions(0),
_total_global_regions(0),
_mutator_bytes_allocated_since_gc_start(0)
_total_global_regions(0)
{
clear_internal();
}
@ -1660,7 +1631,6 @@ HeapWord* ShenandoahFreeSet::try_allocate_in(ShenandoahHeapRegion* r, Shenandoah
if (req.is_mutator_alloc()) {
assert(req.is_young(), "Mutator allocations always come from young generation.");
_partitions.increase_used(ShenandoahFreeSetPartitionId::Mutator, req.actual_size() * HeapWordSize);
increase_bytes_allocated(req.actual_size() * HeapWordSize);
} else {
assert(req.is_gc_alloc(), "Should be gc_alloc since req wasn't mutator alloc");
@ -1699,7 +1669,7 @@ HeapWord* ShenandoahFreeSet::try_allocate_in(ShenandoahHeapRegion* r, Shenandoah
size_t waste_bytes = _partitions.retire_from_partition(orig_partition, idx, r->used());
DEBUG_ONLY(boundary_changed = true;)
if (req.is_mutator_alloc() && (waste_bytes > 0)) {
increase_bytes_allocated(waste_bytes);
req.set_waste(waste_bytes / HeapWordSize);
}
}
@ -1871,15 +1841,9 @@ HeapWord* ShenandoahFreeSet::allocate_contiguous(ShenandoahAllocRequest& req, bo
}
}
_partitions.decrease_empty_region_counts(ShenandoahFreeSetPartitionId::Mutator, num);
if (waste_bytes > 0) {
// For humongous allocations, waste_bytes are included in total_used. Since this is not humongous,
// we need to account separately for the waste_bytes.
increase_bytes_allocated(waste_bytes);
}
}
_partitions.increase_used(ShenandoahFreeSetPartitionId::Mutator, total_used);
increase_bytes_allocated(total_used);
req.set_actual_size(words_size);
// If !is_humongous, the "waste" is made availabe for new allocation
if (waste_bytes > 0) {

View File

@ -441,9 +441,6 @@ private:
ShenandoahHeap* const _heap;
ShenandoahRegionPartitions _partitions;
size_t _total_bytes_previously_allocated;
size_t _mutator_bytes_at_last_sample;
// Temporarily holds mutator_Free allocatable bytes between prepare_to_rebuild() and finish_rebuild()
size_t _prepare_to_rebuild_mutator_free;
@ -519,8 +516,6 @@ private:
size_t _total_young_regions;
size_t _total_global_regions;
size_t _mutator_bytes_allocated_since_gc_start;
// If only affiliation changes are promote-in-place and generation sizes have not changed,
// we have AffiliatedChangesAreGlobalNeutral
// If only affiliation changes are non-empty regions moved from Mutator to Collector and young size has not changed,
@ -668,37 +663,6 @@ public:
return _partitions.shrink_interval_if_range_modifies_either_boundary(partition, low_idx, high_idx, num_regions);
}
void reset_bytes_allocated_since_gc_start(size_t initial_bytes_allocated);
void increase_bytes_allocated(size_t bytes);
// Return an approximation of the bytes allocated since GC start. The value returned is monotonically non-decreasing
// in time within each GC cycle. For certain GC cycles, the value returned may include some bytes allocated before
// the start of the current GC cycle.
inline size_t get_bytes_allocated_since_gc_start() const {
return _mutator_bytes_allocated_since_gc_start;
}
inline size_t get_total_bytes_allocated() {
return _mutator_bytes_allocated_since_gc_start + _total_bytes_previously_allocated;
}
inline size_t get_bytes_allocated_since_previous_sample() {
const size_t total_bytes_allocated = get_total_bytes_allocated();
// total_bytes_allocated could overflow (wraps around) size_t in rare condition, we are relying on
// wrap-around arithmetic of size_t type to produce meaningful result when total_bytes_allocated overflows
// its 64-bit counter. The expression below is equivalent to code:
// if (total_bytes < _mutator_bytes_at_last_sample) {
// // overflow
// return total_bytes + (SIZE_T_MAX - _mutator_bytes_at_last_sample) + 1;
// } else {
// return total_bytes - _mutator_bytes_at_last_sample;
// }
const size_t result = total_bytes_allocated - _mutator_bytes_at_last_sample;
_mutator_bytes_at_last_sample = total_bytes_allocated;
return result;
}
// Public because ShenandoahRegionPartitions assertions require access.
inline size_t alloc_capacity(ShenandoahHeapRegion *r) const;
inline size_t alloc_capacity(size_t idx) const;

View File

@ -256,11 +256,6 @@ void ShenandoahGenerationalControlThread::run_gc_cycle(const ShenandoahGCRequest
GCIdMark gc_id_mark;
if ((gc_mode() != servicing_old) && (gc_mode() != stw_degenerated)) {
// If mode is stw_degenerated, count bytes allocated from the start of the conc GC that experienced alloc failure.
_heap->reset_bytes_allocated_since_gc_start();
}
MetaspaceCombinedStats meta_sizes = MetaspaceUtils::get_combined_statistics();
// If GC was requested, we are sampling the counters even without actual triggers

View File

@ -28,8 +28,9 @@
#include "gc/shenandoah/shenandoahGlobalGeneration.hpp"
#include "gc/shenandoah/shenandoahHeap.hpp"
#include "gc/shenandoah/shenandoahHeapRegion.inline.hpp"
#include "gc/shenandoah/shenandoahOldGeneration.hpp"
#include "gc/shenandoah/shenandoahUtils.hpp"
#include "gc/shenandoah/shenandoahVerifier.hpp"
#include "gc/shenandoah/shenandoahYoungGeneration.hpp"
const char* ShenandoahGlobalGeneration::name() const {
@ -49,10 +50,6 @@ size_t ShenandoahGlobalGeneration::used() const {
return _free_set->global_used();
}
size_t ShenandoahGlobalGeneration::bytes_allocated_since_gc_start() const {
return _free_set->get_bytes_allocated_since_gc_start();
}
size_t ShenandoahGlobalGeneration::get_affiliated_region_count() const {
return _free_set->global_affiliated_regions();
}
@ -61,7 +58,6 @@ size_t ShenandoahGlobalGeneration::get_humongous_waste() const {
return _free_set->total_humongous_waste();
}
size_t ShenandoahGlobalGeneration::used_regions() const {
return _free_set->global_affiliated_regions();
}

View File

@ -26,8 +26,6 @@
#define SHARE_VM_GC_SHENANDOAH_SHENANDOAHGLOBALGENERATION_HPP
#include "gc/shenandoah/shenandoahGeneration.hpp"
#include "gc/shenandoah/shenandoahOldGeneration.hpp"
#include "gc/shenandoah/shenandoahYoungGeneration.hpp"
// A "generation" that represents the whole heap.
class ShenandoahGlobalGeneration : public ShenandoahGeneration {
@ -46,7 +44,6 @@ public:
public:
const char* name() const override;
size_t bytes_allocated_since_gc_start() const override;
size_t used() const override;
size_t used_regions() const override;
size_t used_regions_size() const override;

View File

@ -42,6 +42,7 @@
#include "gc/shenandoah/mode/shenandoahGenerationalMode.hpp"
#include "gc/shenandoah/mode/shenandoahPassiveMode.hpp"
#include "gc/shenandoah/mode/shenandoahSATBMode.hpp"
#include "gc/shenandoah/shenandoahAllocRate.inline.hpp"
#include "gc/shenandoah/shenandoahAllocRequest.hpp"
#include "gc/shenandoah/shenandoahBarrierSet.hpp"
#include "gc/shenandoah/shenandoahClosures.inline.hpp"
@ -822,8 +823,7 @@ bool ShenandoahHeap::check_soft_max_changed() {
size_t new_soft_max = AtomicAccess::load(&SoftMaxHeapSize);
size_t old_soft_max = soft_max_capacity();
if (new_soft_max != old_soft_max) {
new_soft_max = MAX2(min_capacity(), new_soft_max);
new_soft_max = MIN2(max_capacity(), new_soft_max);
new_soft_max = clamp(new_soft_max, min_capacity(), max_capacity());
if (new_soft_max != old_soft_max) {
log_info(gc)("Soft Max Heap Size: %zu%s -> %zu%s",
byte_size_in_proper_unit(old_soft_max), proper_unit_for_byte_size(old_soft_max),
@ -1033,35 +1033,41 @@ HeapWord* ShenandoahHeap::allocate_memory_under_lock(ShenandoahAllocRequest& req
HeapWord* result = _free_set->allocate(req, in_new_region);
// Record the plab configuration for this result and register the object.
if (result != nullptr && req.is_old()) {
if (req.is_lab_alloc()) {
old_generation()->configure_plab_for_current_thread(req);
} else {
// Register the newly allocated object while we're holding the global lock since there's no synchronization
// built in to the implementation of register_object(). There are potential races when multiple independent
// threads are allocating objects, some of which might span the same card region. For example, consider
// a card table's memory region within which three objects are being allocated by three different threads:
//
// objects being "concurrently" allocated:
// [-----a------][-----b-----][--------------c------------------]
// [---- card table memory range --------------]
//
// Before any objects are allocated, this card's memory range holds no objects. Note that allocation of object a
// wants to set the starts-object, first-start, and last-start attributes of the preceding card region.
// Allocation of object b wants to set the starts-object, first-start, and last-start attributes of this card region.
// Allocation of object c also wants to set the starts-object, first-start, and last-start attributes of this
// card region.
//
// The thread allocating b and the thread allocating c can "race" in various ways, resulting in confusion, such as
// last-start representing object b while first-start represents object c. This is why we need to require all
// register_object() invocations to be "mutually exclusive" with respect to each card's memory range.
old_generation()->card_scan()->register_object(result);
if (result != nullptr) {
if (req.is_mutator_alloc()) {
_alloc_rate.allocated((req.actual_size() + req.waste()) * HeapWordSize);
}
if (req.is_promotion()) {
// Shared promotion.
const size_t actual_size = req.actual_size() * HeapWordSize;
log_debug(gc, plab)("Expend shared promotion of %zu bytes", actual_size);
old_generation()->expend_promoted(actual_size);
if (req.is_old()) {
if (req.is_lab_alloc()) {
old_generation()->configure_plab_for_current_thread(req);
} else {
// Register the newly allocated object while we're holding the global lock since there's no synchronization
// built in to the implementation of register_object(). There are potential races when multiple independent
// threads are allocating objects, some of which might span the same card region. For example, consider
// a card table's memory region within which three objects are being allocated by three different threads:
//
// objects being "concurrently" allocated:
// [-----a------][-----b-----][--------------c------------------]
// [---- card table memory range --------------]
//
// Before any objects are allocated, this card's memory range holds no objects. Note that allocation of object a
// wants to set the starts-object, first-start, and last-start attributes of the preceding card region.
// Allocation of object b wants to set the starts-object, first-start, and last-start attributes of this card region.
// Allocation of object c also wants to set the starts-object, first-start, and last-start attributes of this
// card region.
//
// The thread allocating b and the thread allocating c can "race" in various ways, resulting in confusion, such as
// last-start representing object b while first-start represents object c. This is why we need to require all
// register_object() invocations to be "mutually exclusive" with respect to each card's memory range.
old_generation()->card_scan()->register_object(result);
if (req.is_promotion()) {
// Shared promotion.
const size_t actual_size = req.actual_size() * HeapWordSize;
log_debug(gc, plab)("Expend shared promotion of %zu bytes", actual_size);
old_generation()->expend_promoted(actual_size);
}
}
}
}
@ -2312,7 +2318,7 @@ void ShenandoahHeap::stop() {
// Step 2. Wait until GC worker exits normally (this will cancel any ongoing GC).
control_thread()->stop();
// Stop 4. Shutdown uncommit thread.
// Step 3. Shutdown uncommit thread.
if (_uncommit_thread != nullptr) {
_uncommit_thread->stop();
}
@ -2423,27 +2429,6 @@ address ShenandoahHeap::in_cset_fast_test_addr() {
return (address) heap->collection_set()->biased_map_address();
}
void ShenandoahHeap::reset_bytes_allocated_since_gc_start() {
// It is important to force_alloc_rate_sample() before the associated generation's bytes_allocated has been reset.
// Note that we obtain heap lock to prevent additional allocations between sampling bytes_allocated_since_gc_start()
// and reset_bytes_allocated_since_gc_start()
{
ShenandoahHeapLocker locker(lock());
// unaccounted_bytes is the bytes not accounted for by our forced sample. If the sample interval is too short,
// the "forced sample" will not happen, and any recently allocated bytes are "unaccounted for". We pretend these
// bytes are allocated after the start of subsequent gc.
size_t unaccounted_bytes;
size_t bytes_allocated = _free_set->get_bytes_allocated_since_gc_start();
if (mode()->is_generational()) {
unaccounted_bytes = young_generation()->heuristics()->force_alloc_rate_sample(bytes_allocated);
} else {
// Single-gen Shenandoah uses global heuristics.
unaccounted_bytes = heuristics()->force_alloc_rate_sample(bytes_allocated);
}
_free_set->reset_bytes_allocated_since_gc_start(unaccounted_bytes);
}
}
void ShenandoahHeap::set_degenerated_gc_in_progress(bool in_progress) {
_degenerated_gc_in_progress.set_cond(in_progress);
}

View File

@ -30,6 +30,7 @@
#include "gc/shared/collectedHeap.hpp"
#include "gc/shared/markBitMap.hpp"
#include "gc/shenandoah/mode/shenandoahMode.hpp"
#include "gc/shenandoah/shenandoahAllocRate.hpp"
#include "gc/shenandoah/shenandoahAllocRequest.hpp"
#include "gc/shenandoah/shenandoahAsserts.hpp"
#include "gc/shenandoah/shenandoahController.hpp"
@ -227,12 +228,12 @@ private:
Atomic<size_t> _committed;
shenandoah_padding(1);
ShenandoahAllocationRate _alloc_rate;
public:
void increase_committed(size_t bytes);
void decrease_committed(size_t bytes);
void reset_bytes_allocated_since_gc_start();
size_t min_capacity() const;
size_t max_capacity() const override;
size_t soft_max_capacity() const;
@ -243,6 +244,10 @@ public:
void set_soft_max_capacity(size_t v);
ShenandoahAllocationRate& alloc_rate() {
return _alloc_rate;
}
// ---------- Periodic Tasks
//
public:

View File

@ -796,11 +796,6 @@ size_t ShenandoahOldGeneration::used() const {
return _free_set->old_used();
}
size_t ShenandoahOldGeneration::bytes_allocated_since_gc_start() const {
assert(ShenandoahHeap::heap()->mode()->is_generational(), "NON_GEN implies not generational");
return 0;
}
size_t ShenandoahOldGeneration::get_affiliated_region_count() const {
return _free_set->old_affiliated_regions();
}

View File

@ -353,7 +353,6 @@ public:
static const char* state_name(State state);
size_t bytes_allocated_since_gc_start() const override;
size_t used() const override;
size_t used_regions() const override;
size_t used_regions_size() const override;

View File

@ -26,15 +26,10 @@
#include "gc/shared/gcCause.hpp"
#include "gc/shared/gcTrace.hpp"
#include "gc/shared/gcWhen.hpp"
#include "gc/shared/referenceProcessorStats.hpp"
#include "gc/shenandoah/heuristics/shenandoahHeuristics.hpp"
#include "gc/shenandoah/shenandoahCollectorPolicy.hpp"
#include "gc/shenandoah/shenandoahHeap.inline.hpp"
#include "gc/shenandoah/shenandoahOldGeneration.hpp"
#include "gc/shenandoah/shenandoahReferenceProcessor.hpp"
#include "gc/shenandoah/shenandoahUtils.hpp"
#include "gc/shenandoah/shenandoahYoungGeneration.hpp"
#include "jfr/jfrEvents.hpp"
#include "utilities/debug.hpp"

View File

@ -41,6 +41,9 @@
#include "runtime/vmThread.hpp"
#include "services/memoryService.hpp"
#include <cmath>
#include <limits>
class GCTimer;
class ShenandoahGeneration;
@ -235,5 +238,20 @@ public:
}
};
// Casting a double that cannot be represented as a size_t may result in undefined behavior.
// This small function checks if the given double is representable in a size_t and returns
// that representation if it is. Otherwise, if the double cannot be safely cast to a size_t
// it returns zero.
inline size_t shenandoah_safe_size_cast(const double d) {
static constexpr double size_max_as_double = static_cast<double>(std::numeric_limits<size_t>::max());
if (std::isnan(d) || d < 0 || d >= size_max_as_double) {
// NaN is unordered, all comparisons will be false.
// +Inf is always greater than, -Inf is always less than
return 0;
}
return static_cast<size_t>(d);
}
#endif // SHARE_GC_SHENANDOAH_SHENANDOAHUTILS_HPP

View File

@ -0,0 +1,190 @@
/*
* Copyright Amazon.com Inc. 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/shenandoah/shenandoahWeightedSeq.hpp"
#include "memory/allocation.hpp"
#include <cmath>
ShenandoahWeightedSeq::ShenandoahWeightedSeq(uint size)
: _size(size),
_first_sample_index(0),
_num_samples(0),
_x_values(NEW_C_HEAP_ARRAY(double, _size, mtGC)),
_y_values(NEW_C_HEAP_ARRAY(double, _size, mtGC)),
_weights(NEW_C_HEAP_ARRAY(double, _size, mtGC)),
_x_origin(0),
_y_origin(0),
_x_sum(0),
_y_sum(0),
_weighted_y_sum(0),
_weighted_sum(0),
_weighted_yy_sum(0),
_xy_sum(0),
_xx_sum(0),
_yy_sum(0),
_slope(0.0),
_y_intercept(0.0),
_residual_sd(0.0) {
}
ShenandoahWeightedSeq::~ShenandoahWeightedSeq() {
FREE_C_HEAP_ARRAY(_x_values);
FREE_C_HEAP_ARRAY(_y_values);
FREE_C_HEAP_ARRAY(_weights);
}
void ShenandoahWeightedSeq::add(double x, double y) {
if (_num_samples == 0) {
add(x, y, 0.0);
} else {
const uint index = (_first_sample_index + _num_samples - 1) % _size;
const double weight = x - _x_values[index];
add(x, y, weight);
}
}
void ShenandoahWeightedSeq::deduct_oldest_and_rebase(const double x_absolute, const double y_absolute, const double weight) {
// Suppose we want to shift _x_origin by delta. Our accumulators for x
// components are based on the relative value 'x - x_origin', call this 'a'.
// We want to update our accumulators to hold 'a - delta'.
// Our new value for
// updated sum(x) = sum(a - delta)
// = sum(a) - n * delta.
// Similarly
// updated sum(x^2) = sum((a - delta)^2)
// = sum(a^2 - 2 * delta * a + delta^2)
// = sum(a^2) - 2 * delta * sum(a) + n * delta^2
// Finally
// updated sum(xy) = sum(a - delta) * y
// = sum(xy) - delta * sum(y)
const double x_delta = x_absolute - _x_origin;
const double y_delta = y_absolute - _y_origin;
// order matters here, we must use old _x_sum
_xx_sum = _xx_sum - 2.0 * x_delta * _x_sum + _num_samples * x_delta * x_delta;
_xy_sum = _xy_sum - x_delta * _y_sum;
_x_sum = _x_sum - _num_samples * x_delta;
_x_origin = x_absolute;
// similarly, rebase y
_yy_sum = _yy_sum - 2.0 * y_delta * _y_sum + _num_samples * y_delta * y_delta;
_xy_sum = _xy_sum - y_delta * _x_sum;
_y_sum = _y_sum - _num_samples * y_delta;
_y_origin = y_absolute;
// and our weighted sums
_weighted_yy_sum = _weighted_yy_sum - 2.0 * y_delta * _weighted_y_sum + _weighted_sum * y_delta * y_delta;
_weighted_y_sum = _weighted_y_sum - _weighted_sum * y_delta;
_weighted_sum -= weight;
}
void ShenandoahWeightedSeq::add_latest(double x_absolute, double y_absolute, double weight) {
const double x_delta = x_absolute - _x_origin;
const double y_delta = y_absolute - _y_origin;
_x_sum += x_delta;
_y_sum += y_delta;
_xy_sum += x_delta * y_delta;
_xx_sum += x_delta * x_delta;
_yy_sum += y_delta * y_delta;
_weighted_sum += weight;
_weighted_y_sum += y_delta * weight;
_weighted_yy_sum += y_delta * y_delta * weight;
}
void ShenandoahWeightedSeq::add(double x, double y, double weight) {
// Update best-fit linear regression
const uint index = (_first_sample_index + _num_samples) % _size;
if (_num_samples == _size) {
deduct_oldest_and_rebase(_x_values[index], _y_values[index], _weights[index]);
} else if (_num_samples == 0) {
_x_origin = x;
_y_origin = y;
}
_x_values[index] = x;
_y_values[index] = y;
_weights[index] = weight;
add_latest(x, y, weight);
if (_num_samples < _size) {
_num_samples++;
} else {
_first_sample_index = (_first_sample_index + 1) % _size;
}
const double x_spread = _num_samples * _xx_sum - _x_sum * _x_sum;
if (x_spread <= 0.0 || _num_samples < 2) {
// All samples are the sample point, can't make a line
_slope = 0;
_y_intercept = y - _y_origin;
_residual_sd = 0.0;
return;
}
_slope = (_num_samples * _xy_sum - _x_sum * _y_sum) / x_spread;
_y_intercept = (_y_sum - _slope * _x_sum) / _num_samples;
const double total_sum_of_squares = _yy_sum - _y_sum * _y_sum / _num_samples;
const double sum_of_cross_deviations = _xy_sum - _x_sum * _y_sum / _num_samples;
const double residual_sum_of_squares = total_sum_of_squares - _slope * sum_of_cross_deviations;
_residual_sd = std::sqrt(MAX2(residual_sum_of_squares, 0.0) / _num_samples);
}
double ShenandoahWeightedSeq::predict(double x_absolute, double margin_of_error) const {
const double prediction = predict_y(x_absolute) + _residual_sd * margin_of_error;
if (prediction <= 0.0) {
// return average time, rather than negative or zero time
return average();
}
return prediction;
}
double ShenandoahWeightedSeq::weighted_average() const {
if (_weighted_sum <= 0.0) {
return 0.0;
}
return _weighted_y_sum / _weighted_sum + _y_origin;
}
double ShenandoahWeightedSeq::weighted_sd() const {
if (_weighted_sum <= 0.0) {
return 0.0;
}
const double weighted_mean = _weighted_y_sum / _weighted_sum;
const double variance = _weighted_yy_sum / _weighted_sum - weighted_mean * weighted_mean;
return std::sqrt(MAX2(variance, 0.0));
}
double ShenandoahWeightedSeq::sd() const {
if (_num_samples < 2) {
return 0.0;
}
const double mean = _y_sum / _num_samples;
const double variance = _yy_sum / _num_samples - mean * mean;
return std::sqrt(MAX2(variance, 0.0));
}

View File

@ -0,0 +1,130 @@
/*
* Copyright Amazon.com Inc. 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_SHENANDOAH_SHENANDOAHWEIGHTEDSEQ_HPP
#define SHARE_GC_SHENANDOAH_SHENANDOAHWEIGHTEDSEQ_HPP
#include "utilities/globalDefinitions.hpp"
// Provides a weighted sequence of x, y pairs. Various statistical properties
// such as weighted mean, standard deviation, the line of best fit and the
// residual deviation (deviation about the line of best fit) are available.
// These attributes are maintained incrementally as we expect this structure
// to be read more often than it is written.
class ShenandoahWeightedSeq {
uint _size;
uint _first_sample_index;
uint _num_samples;
double* const _x_values;
double* const _y_values;
double* const _weights;
// Values stored in the x,y accumulators will be reduced to avoid arithmetic
// errors caused by loss of precision when working with large doubles. This
// is particularly important for the common use case when x is a monotonically
// increasing timestamp
double _x_origin;
double _y_origin;
double _x_sum;
double _y_sum;
double _weighted_y_sum;
double _weighted_sum;
double _weighted_yy_sum;
double _xy_sum;
double _xx_sum;
double _yy_sum;
double _slope; // slope
double _y_intercept; // y-intercept
double _residual_sd; // sd on deviance from prediction
public:
explicit ShenandoahWeightedSeq(uint size);
~ShenandoahWeightedSeq();
// Return last item x value added to the sequence (zero if sequence is empty).
double last() const {
if (_num_samples == 0) {
return 0.0;
}
const uint index = (_first_sample_index + _num_samples - 1) % _size;
return _x_values[index];
}
// Add x, y to the sequence. Weight will be calculated as x - last().
void add(double x, double y);
// Add x, y to the sequence using given weight.
void add(double x, double y, double weight);
// Predict the next value in the sequence for a given x. Uses average
// if the prediction is <= 0. This is a legacy method visible only for
// testing.
double predict(double x, double margin_of_error) const;
// The standard deviation of the samples about the line of best fit rather
// than deviation about the mean.
double residual_sd() const { return _residual_sd; }
// An unweighted mean.
double average() const { return _y_sum / MAX2(_num_samples, 1u) + _y_origin; }
// The weighted mean for the sequence.
double weighted_average() const;
// Standard deviation for the weighted mean.
double weighted_sd() const;
// An unweighted standard deviation of the unweighted mean
double sd() const;
// The slope for a line of best fit through the samples
double slope() const { return _slope; }
// Predict the y-value for the given x value based on linear reg
double predict_y(double x_absolute) const {
return _slope * (x_absolute - _x_origin) + _y_intercept + _y_origin;
}
// Provides the slope and y-intercept for the line of best fit through the sequence
void fit_line(const double x_absolute, double& slope, double& intercept) const {
slope = _slope;
intercept = predict_y(x_absolute);
}
private:
// Removes about to be overwritten sample from x accumulators and rebases x origin
void deduct_oldest_and_rebase(double x, double y, double weight);
// Record the sample into the sequence, update x, y accumulators
void add_latest(double x, double y, double weight);
};
#endif // SHARE_GC_SHENANDOAH_SHENANDOAHWEIGHTEDSEQ_HPP

View File

@ -100,11 +100,6 @@ size_t ShenandoahYoungGeneration::used() const {
return _free_set->young_used();
}
size_t ShenandoahYoungGeneration::bytes_allocated_since_gc_start() const {
assert(ShenandoahHeap::heap()->mode()->is_generational(), "Young implies generational");
return _free_set->get_bytes_allocated_since_gc_start();
}
size_t ShenandoahYoungGeneration::get_affiliated_region_count() const {
return _free_set->young_affiliated_regions();
}

View File

@ -69,11 +69,10 @@ public:
// Returns true if the young generation is configured to enqueue old
// oops for the old generation mark queues.
bool is_bootstrap_cycle() {
bool is_bootstrap_cycle() const {
return _old_gen_task_queues != nullptr;
}
size_t bytes_allocated_since_gc_start() const override;
size_t used() const override;
size_t used_regions() const override;
size_t used_regions_size() const override;

View File

@ -34,22 +34,18 @@
range, \
constraint) \
\
product(uint, ShenandoahAccelerationSamplePeriod, 15, EXPERIMENTAL, \
"When at least this much time (measured in ms) has passed " \
"since the acceleration allocation rate was most recently " \
"sampled, capture another allocation rate sample for the purpose "\
"of detecting acceleration or momentary spikes in allocation " \
"rate. A smaller value allows quicker response to changes in " \
"allocation rates but is more vulnerable to noise and requires " \
"more monitoring effort.") \
product(uint, ShenandoahMomentaryAllocRateSampleWindow, 6, EXPERIMENTAL, \
"The number of samples in the momentary allocation rate moving " \
"average. This window serves to detect momentary spikes in the " \
"allocation rate. A smaller value allows quicker response to " \
"changes in the allocation rate but is more vulnerable to noise " \
"and requires more monitoring effort. Must not be greater than " \
"ShenandoahRecentAllocRateSampleWindow") \
range(1, 1000) \
\
product(uint, ShenandoahRateAccelerationSampleSize, 8, EXPERIMENTAL, \
"In selected ShenandoahControlIntervals " \
"(if ShenandoahAccelerationSamplePeriod ms have passed " \
"since previous allocation rate sample), " \
"we compute the allocation rate since the previous rate was " \
"sampled. This many samples are analyzed to determine whether " \
product(uint, ShenandoahRecentAllocRateSampleWindow, 20, EXPERIMENTAL, \
"The number of samples in the recent allocation rate moving " \
"average. These samples are analyzed to determine whether " \
"allocation rates are accelerating. Acceleration may occur " \
"due to increasing client demand or due to phase changes in " \
"an application. A larger value reduces sensitivity to " \
@ -62,30 +58,13 @@
"detected. If the last several of all samples are signficantly " \
"larger than the other samples, the best fit line through all " \
"sampled values will have an upward slope, manifesting as " \
"acceleration.") \
range(1,64) \
"acceleration. Must not be greater than ShenandoahAllocRateSampleWindow") \
range(1,5000) \
\
product(uint, ShenandoahMomentaryAllocationRateSpikeSampleSize, \
2, EXPERIMENTAL, \
"In selected ShenandoahControlIntervals " \
"(if ShenandoahAccelerationSamplePeriod ms have passed " \
"since previous allocation rate sample), we compute " \
"the allocation rate since the previous rate was sampled. " \
"The weighted average of this " \
"many most recent momentary allocation rate samples is compared " \
"against current allocation runway and anticipated GC time to " \
"determine whether a spike in momentary allocation rate " \
"justifies an early GC trigger. Momentary allocation spike " \
"detection is in addition to previously implemented " \
"ShenandoahAdaptiveInitialSpikeThreshold, the latter of which " \
"is more effective at detecting slower spikes. The latter " \
"spike detection samples at the rate specifieid by " \
"ShenandoahAdaptiveSampleFrequencyHz. The value of this " \
"parameter must be less than the value of " \
"ShenandoahRateAccelerationSampleSize. A larger value makes " \
"momentary spike detection less sensitive. A smaller value " \
"may result in excessive GC triggers.") \
range(1,64) \
product(uint, ShenandoahAllocRateSampleWindow, 100, EXPERIMENTAL, \
"The size of the moving window over which the average " \
"baseline allocation rate is maintained.") \
range(1,10000) \
\
product(uintx, ShenandoahGenerationalMinPIPUsage, 30, EXPERIMENTAL, \
"(Generational mode only) What percent of a heap region " \
@ -267,7 +246,7 @@
range(0,100) \
\
product(uintx, ShenandoahAllocationThreshold, 0, EXPERIMENTAL, \
"How many new allocations should happen since the last GC cycle " \
"How many bytes may be allocated since the last GC cycle started "\
"before some heuristics trigger the collection. In percents of " \
"(soft) max heap size. Set to zero to effectively disable.") \
range(0,100) \
@ -291,34 +270,12 @@
"to 100 effectively disables the shortcut.") \
range(0,100) \
\
product(uintx, ShenandoahAdaptiveSampleFrequencyHz, 10, EXPERIMENTAL, \
"The number of times per second to update the allocation rate " \
"moving average.") \
\
product(uintx, ShenandoahAdaptiveSampleSizeSeconds, 10, EXPERIMENTAL, \
"The size of the moving window over which the average " \
"allocation rate is maintained. The total number of samples " \
"is the product of this number and the sample frequency.") \
\
product(double, ShenandoahAdaptiveInitialConfidence, 1.8, EXPERIMENTAL, \
"The number of standard deviations used to determine an initial " \
"margin of error for the average cycle time and average " \
"allocation rate. Increasing this value will cause the " \
"heuristic to initiate more concurrent cycles." ) \
\
product(double, ShenandoahAdaptiveInitialSpikeThreshold, 1.8, EXPERIMENTAL, \
"If the most recently sampled allocation rate is more than " \
"this many standard deviations away from the moving average, " \
"then a cycle is initiated. This value controls how sensitive " \
"the heuristic is to allocation spikes. Decreasing this number " \
"increases the sensitivity. ") \
\
product(double, ShenandoahAdaptiveDecayFactor, 0.5, EXPERIMENTAL, \
"The decay factor (alpha) used for values in the weighted " \
"moving average of cycle time and allocation rate. " \
"Larger values give more weight to recent values.") \
range(0,1.0) \
\
product(uintx, ShenandoahGuaranteedGCInterval, 5*60*1000, EXPERIMENTAL, \
"Many heuristics would guarantee a concurrent GC cycle at " \
"least with this interval. This is useful when large idle " \

View File

@ -558,6 +558,14 @@ static SpecialFlag const special_jvm_flags[] = {
{ "UseNewLongLShift", JDK_Version::undefined(), JDK_Version::jdk(27), JDK_Version::jdk(28) },
{ "AggressiveHeap", JDK_Version::jdk(26), JDK_Version::jdk(27), JDK_Version::jdk(28) },
{"ShenandoahAccelerationSamplePeriod", JDK_Version::undefined(), JDK_Version::jdk(27), JDK_Version::jdk(28) },
{"ShenandoahRateAccelerationSampleSize", JDK_Version::undefined(), JDK_Version::jdk(27), JDK_Version::jdk(28) },
{"ShenandoahMomentaryAllocationRateSpikeSampleSize", JDK_Version::undefined(), JDK_Version::jdk(27), JDK_Version::jdk(28) },
{"ShenandoahAdaptiveSampleFrequencyHz", JDK_Version::undefined(), JDK_Version::jdk(27), JDK_Version::jdk(28) },
{"ShenandoahAdaptiveSampleSizeSeconds", JDK_Version::undefined(), JDK_Version::jdk(27), JDK_Version::jdk(28) },
{"ShenandoahAdaptiveInitialSpikeThreshold",JDK_Version::undefined(), JDK_Version::jdk(27), JDK_Version::jdk(28) },
{"ShenandoahAdaptiveDecayFactor", JDK_Version::undefined(), JDK_Version::jdk(27), JDK_Version::jdk(28) },
#ifdef ASSERT
{ "DummyObsoleteTestFlag", JDK_Version::undefined(), JDK_Version::jdk(18), JDK_Version::undefined() },
#endif

View File

@ -0,0 +1,161 @@
/*
* Copyright Amazon.com Inc. 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 "unittest.hpp"
#include "gc/shared/gc_globals.hpp"
#include "gc/shenandoah/shenandoahAllocRate.inline.hpp"
class ShenandoahMockClock {
public:
static volatile jlong Counter;
static jlong elapsed_counter() {
const jlong result = Counter;
Counter += NANOSECS_PER_SEC;
return result;
}
static jlong elapsed_frequency() {
return NANOSECS_PER_SEC;
}
};
volatile jlong ShenandoahMockClock::Counter = 0;
class ShenandoahAllocationRateTest : public testing::Test {
protected:
ShenandoahAllocationRateTest() {
ShenandoahMockClock::Counter = 0;
}
template<typename Clock>
static void allocate(ShenandoahAllocRate<Clock>& rate, size_t quantity) {
rate.allocated(quantity);
}
};
constexpr uint BASELINE_SAMPLES = 100;
constexpr uint RECENT_SAMPLES = 8;
constexpr uint MOMENTARY_SAMPLES = 2;
constexpr uint MINIMUM_SAMPLE_SIZE = 1024;
TEST_VM_F(ShenandoahAllocationRateTest, ignore_too_small_sample) {
ShenandoahAllocRate<ShenandoahMockClock> rate(MINIMUM_SAMPLE_SIZE, BASELINE_SAMPLES, RECENT_SAMPLES, MOMENTARY_SAMPLES);
rate.allocated(512);
EXPECT_DOUBLE_EQ(rate.weighted_average(), 0);
}
TEST_VM_F(ShenandoahAllocationRateTest, two_second_average) {
ShenandoahAllocRate<ShenandoahMockClock> rate(MINIMUM_SAMPLE_SIZE, BASELINE_SAMPLES, RECENT_SAMPLES, MOMENTARY_SAMPLES);
allocate(rate, 2048); // t = 1
allocate(rate, 2048); // t = 2
EXPECT_DOUBLE_EQ(rate.weighted_average(), 2048.0);
}
TEST_VM_F(ShenandoahAllocationRateTest, accelerated_consumption_small_number_of_samples) {
ShenandoahAllocRate<ShenandoahMockClock> rate(MINIMUM_SAMPLE_SIZE, BASELINE_SAMPLES, RECENT_SAMPLES, MOMENTARY_SAMPLES);
allocate(rate, 1024);
ShenandoahAnticipatedConsumption consumption = rate.snapshot(100, 1);
EXPECT_DOUBLE_EQ(consumption.acceleration(), 0.0);
EXPECT_DOUBLE_EQ(consumption.predicted_rate(), 0.0);
EXPECT_EQ(consumption.accelerated_consumption(), 0UL);
}
TEST_VM_F(ShenandoahAllocationRateTest, accelerated_consumption_uniform_rate) {
ShenandoahAllocRate<ShenandoahMockClock> rate(MINIMUM_SAMPLE_SIZE, BASELINE_SAMPLES, RECENT_SAMPLES, MOMENTARY_SAMPLES);
for (uint i = 0; i < BASELINE_SAMPLES; ++i) {
allocate(rate, 1024);
}
ShenandoahAnticipatedConsumption consumption = rate.snapshot(100, 1);
EXPECT_DOUBLE_EQ(rate.weighted_average(), 1024); // Average rate, 1024 bytes per tick
EXPECT_DOUBLE_EQ(consumption.acceleration(), 0.0); // No acceleration, rate is constant
EXPECT_DOUBLE_EQ(consumption.momentary_rate(), 1024); // Momentary rate is the same as the average
EXPECT_EQ(consumption.momentary_consumption(), 102400UL); // 100 clock ticks at 1024 bytes per tick
EXPECT_EQ(consumption.accelerated_consumption(), 0UL);
}
TEST_VM_F(ShenandoahAllocationRateTest, accelerated_consumption_momentary_spike) {
ShenandoahAllocRate<ShenandoahMockClock> rate(MINIMUM_SAMPLE_SIZE, BASELINE_SAMPLES, RECENT_SAMPLES, MOMENTARY_SAMPLES);
for (uint i = 0; i < BASELINE_SAMPLES; ++i) {
allocate(rate, 2048);
}
for (uint i = 0; i < RECENT_SAMPLES; ++i) {
allocate(rate, 1024);
}
for (uint i = 0; i < MOMENTARY_SAMPLES + 1; ++i) {
allocate(rate, 2048);
}
// Here we simulate a situation where we are returning from a lull (avg 1024/s) back
// to the baseline average allocation rate (2048/s). The momentary rate will reflect
// the recent samples, but we will not consider this to be an acceleration.
ShenandoahAnticipatedConsumption consumption = rate.snapshot(100, 1);
EXPECT_DOUBLE_EQ(consumption.acceleration(), 0.0);
EXPECT_DOUBLE_EQ(consumption.momentary_rate(), 2048);
EXPECT_EQ(consumption.momentary_consumption(), 204800UL);
EXPECT_EQ(consumption.accelerated_consumption(), 0UL);
}
TEST_VM_F(ShenandoahAllocationRateTest, accelerated_consumption_accelerating) {
ShenandoahAllocRate<ShenandoahMockClock> rate(256, BASELINE_SAMPLES, RECENT_SAMPLES, MOMENTARY_SAMPLES);
for (uint i = 0; i < BASELINE_SAMPLES; ++i) {
allocate(rate, 512);
}
for (uint i = 0; i < RECENT_SAMPLES; ++i) {
allocate(rate, 1024);
}
for (uint i = 0; i < MOMENTARY_SAMPLES + 1; ++i) {
allocate(rate, 2048);
}
// Setup as before, but pretend our baseline acceleration rate is lower (512). This
// will evaluate the acceleration of the rate.
ShenandoahAnticipatedConsumption consumption = rate.snapshot(100, 1);
EXPECT_GE(consumption.acceleration(), 180.0);
EXPECT_GE(consumption.predicted_rate(), 2047.0); // should be 2048, but can be 2047.9999 from fp issues
EXPECT_GE(consumption.accelerated_consumption(), 102400UL);
EXPECT_EQ(consumption.momentary_consumption(), 0UL);
}
TEST_VM_F(ShenandoahAllocationRateTest, accelerated_consumption_decelerating) {
ShenandoahAllocRate<ShenandoahMockClock> rate(MINIMUM_SAMPLE_SIZE, BASELINE_SAMPLES, RECENT_SAMPLES, MOMENTARY_SAMPLES);
for (uint i = 0; i < RECENT_SAMPLES; ++i) {
allocate(rate, 2048);
}
for (uint i = 0; i < MOMENTARY_SAMPLES + 1; ++i) {
allocate(rate, 1024);
}
// In this setup, the allocation rate is declining.
ShenandoahAnticipatedConsumption consumption = rate.snapshot(100, 1);
EXPECT_DOUBLE_EQ(consumption.acceleration(), 0.0);
EXPECT_DOUBLE_EQ(consumption.momentary_rate(), 1024.0);
EXPECT_EQ(consumption.momentary_consumption(), 102400UL);
}

View File

@ -0,0 +1,51 @@
/*
* Copyright Amazon.com Inc. 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 "unittest.hpp"
#include "gc/shenandoah/shenandoahCycleDuration.hpp"
TEST_VM(ShenandoahCycleDurationTest, empty_sanity) {
ShenandoahCycleDuration cycles(5);
EXPECT_DOUBLE_EQ(cycles.predict_duration(1.0, 1.0), 0.0);
}
TEST_VM(ShenandoahCycleDurationTest, predict_duration) {
ShenandoahCycleDuration cycles(5);
cycles.record_duration(1.0, 1.0);
cycles.record_duration(2.0, 2.0);
cycles.record_duration(3.0, 3.0);
EXPECT_DOUBLE_EQ(cycles.predict_duration(4.0, 0.0), 4.0);
}
TEST_VM(ShenandoahCycleDurationTest, fallback_to_average) {
ShenandoahCycleDuration cycles(5);
cycles.record_duration(1.0, 5.0);
cycles.record_duration(2.0, 4.0);
cycles.record_duration(3.0, 3.0);
// With this downward trend, predicted duration at 6 seconds would be zero, so
// we fall back to the average (5 + 4 + 3 / 3 = 4)
EXPECT_DOUBLE_EQ(cycles.predict_duration(6.0, 0.0), 4.0);
// Average is 4.0, sd = sqrt((25+16+9)/3 - 16) = sqrt(50/3 - 16) = sqrt(2/3) ~ 0.816
EXPECT_NEAR(cycles.predict_duration(6.0, 1.0), 4.0 + 0.816, 0.001);
}

View File

@ -0,0 +1,155 @@
/*
* Copyright Amazon.com Inc. 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 "unittest.hpp"
#include "gc/shenandoah/shenandoahWeightedSeq.hpp"
constexpr uint SAMPLE_SIZE = 3;
TEST_VM(ShenandoahWeightedSeqTest, empty_sanity) {
ShenandoahWeightedSeq seq(SAMPLE_SIZE);
EXPECT_DOUBLE_EQ(seq.predict(0, 0), 0.0);
EXPECT_DOUBLE_EQ(seq.predict(1, 0), 0.0);
}
TEST_VM(ShenandoahWeightedSeqTest, predict_flat_line) {
ShenandoahWeightedSeq seq(SAMPLE_SIZE);
seq.add(1, 2);
EXPECT_DOUBLE_EQ(seq.predict(1, 1), 2.0);
EXPECT_DOUBLE_EQ(seq.predict(2, 1), 2.0);
}
TEST_VM(ShenandoahWeightedSeqTest, predict_y_equals_x) {
ShenandoahWeightedSeq seq(SAMPLE_SIZE);
seq.add(1, 1);
seq.add(2, 2);
seq.add(3, 3);
EXPECT_DOUBLE_EQ(seq.predict(4, 1), 4.0);
EXPECT_DOUBLE_EQ(seq.predict(5, 1), 5.0);
}
TEST_VM(ShenandoahWeightedSeqTest, predict_y_equals_x_squared) {
ShenandoahWeightedSeq seq(SAMPLE_SIZE);
seq.add(1, 1);
seq.add(2, 4);
seq.add(3, 9);
EXPECT_NEAR(seq.predict(4, 0), 12.666, 0.001);
EXPECT_NEAR(seq.predict(5, 0), 16.666, 0.001);
// Give a margin of error that incorporates residuals standard deviation
EXPECT_NEAR(seq.predict(4, 1), 13.138, 0.001);
EXPECT_NEAR(seq.predict(5, 1), 17.138, 0.001);
}
TEST_VM(ShenandoahWeightedSeqTest, predict_y_equals_x_squared_overflow) {
ShenandoahWeightedSeq seq(SAMPLE_SIZE);
for (uint i = 0; i < SAMPLE_SIZE; i++) {
seq.add(1, 1);
seq.add(2, 4);
seq.add(3, 9);
}
EXPECT_NEAR(seq.predict(4, 0), 12.666, 0.001);
EXPECT_NEAR(seq.predict(5, 0), 16.666, 0.001);
// Give a margin of error that incorporates residuals standard deviation
EXPECT_NEAR(seq.predict(4, 1), 13.138, 0.001);
EXPECT_NEAR(seq.predict(5, 1), 17.138, 0.001);
}
TEST_VM(ShenandoahWeightedSeqTest, predict_y_equals_x_long_uptime) {
// Simulates a JVM running ~66 days, using os::elapsedTime() as x.
// With tight sampling (50ms intervals) this hits catastrophic cancellation
// in x_spread = samples * _xx_sum _x_sum_squared — both operands have magnitude
// whose double-precision LSB is ~0.065, but the true spread is ~0.015.
constexpr double OFFSET = 66.0 * 86400.0; // 66 days of uptime in seconds
constexpr double DT = 0.05; // 50ms between samples
ShenandoahWeightedSeq seq(SAMPLE_SIZE);
uint i = 1;
for (uint n = SAMPLE_SIZE * 2; i <= n; i++) {
seq.add(OFFSET + i * DT, i);
}
// Relationship: y = (x OFFSET)/DT, so predict(OFFSET + i·DT) should give i
EXPECT_NEAR(seq.predict(OFFSET + (i + 1) * DT, 0), i + 1, 0.001);
}
TEST_VM(ShenandoahWeightedSeqTest, identical_timestamps) {
ShenandoahWeightedSeq seq(SAMPLE_SIZE);
seq.add(100.0, 1.0);
seq.add(100.0, 2.0);
// Should return a finite prediction (e.g. the average of y).
EXPECT_TRUE(std::isfinite(seq.predict(101.0, 0)));
}
TEST_VM(ShenandoahWeightedSeqTest, simple_average_no_samples) {
ShenandoahWeightedSeq seq(SAMPLE_SIZE);
EXPECT_DOUBLE_EQ(seq.average(), 0.0);
}
TEST_VM(ShenandoahWeightedSeqTest, simple_average_one_sample) {
ShenandoahWeightedSeq seq(SAMPLE_SIZE);
seq.add(1, 1);
EXPECT_DOUBLE_EQ(seq.average(), 1.0);
}
TEST_VM(ShenandoahWeightedSeqTest, simple_average_overflow) {
ShenandoahWeightedSeq seq(SAMPLE_SIZE);
for (uint i = 0; i < SAMPLE_SIZE + 1; i++) {
seq.add(i, 1);
}
EXPECT_DOUBLE_EQ(seq.average(), 1.0);
}
TEST_VM(ShenandoahWeightedSeqTest, simple_average) {
ShenandoahWeightedSeq seq(SAMPLE_SIZE);
seq.add(1, 1);
seq.add(2, 1);
EXPECT_DOUBLE_EQ(seq.average(), 1.0);
}
TEST_VM(ShenandoahWeightedSeqTest, simple_average_2) {
ShenandoahWeightedSeq seq(SAMPLE_SIZE);
seq.add(1, 1);
seq.add(2, 2);
EXPECT_DOUBLE_EQ(seq.average(), 1.5);
}
TEST_VM(ShenandoahWeightedSeqTest, weighted_average_no_samples) {
ShenandoahWeightedSeq seq(SAMPLE_SIZE);
EXPECT_DOUBLE_EQ(seq.weighted_average(), 0.0);
}
TEST_VM(ShenandoahWeightedSeqTest, weighted_average_one_sample) {
ShenandoahWeightedSeq seq(SAMPLE_SIZE);
seq.add(1, 2, 2);
EXPECT_DOUBLE_EQ(seq.weighted_average(), 2.0);
}
TEST_VM(ShenandoahWeightedSeqTest, weighted_average_overflow) {
ShenandoahWeightedSeq seq(SAMPLE_SIZE);
for (uint i = 0; i < SAMPLE_SIZE + 1; i++) {
seq.add(i, 2, 2);
}
EXPECT_DOUBLE_EQ(seq.weighted_average(), 2.0);
}