From 18ab07318075454dec1a7f27c9e5117a26569f1e Mon Sep 17 00:00:00 2001 From: William Kemper Date: Thu, 31 Jul 2025 17:19:39 -0700 Subject: [PATCH 01/47] Some cleanup and a test harness for adaptive tenuring --- .../gc/shenandoah/shenandoahAgeCensus.cpp | 46 ++++++++---- .../gc/shenandoah/shenandoahAgeCensus.hpp | 6 +- .../shenandoah/test_shenandoahAgeCensus.cpp | 72 +++++++++++++++++++ 3 files changed, 111 insertions(+), 13 deletions(-) create mode 100644 test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp index 94c98b78f1bc0..a7eeb6867e645 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp @@ -27,8 +27,15 @@ #include "gc/shenandoah/shenandoahAgeCensus.hpp" #include "gc/shenandoah/shenandoahHeap.inline.hpp" -ShenandoahAgeCensus::ShenandoahAgeCensus() { +ShenandoahAgeCensus::ShenandoahAgeCensus() + : ShenandoahAgeCensus(ShenandoahHeap::heap()->max_workers()) +{ assert(ShenandoahHeap::heap()->mode()->is_generational(), "Only in generational mode"); +} + +ShenandoahAgeCensus::ShenandoahAgeCensus(uint max_workers) + : _max_workers(max_workers) +{ if (ShenandoahGenerationalMinTenuringAge > ShenandoahGenerationalMaxTenuringAge) { vm_exit_during_initialization( err_msg("ShenandoahGenerationalMinTenuringAge=%zu" @@ -39,6 +46,9 @@ ShenandoahAgeCensus::ShenandoahAgeCensus() { _global_age_table = NEW_C_HEAP_ARRAY(AgeTable*, MAX_SNAPSHOTS, mtGC); CENSUS_NOISE(_global_noise = NEW_C_HEAP_ARRAY(ShenandoahNoiseStats, MAX_SNAPSHOTS, mtGC);) _tenuring_threshold = NEW_C_HEAP_ARRAY(uint, MAX_SNAPSHOTS, mtGC); + CENSUS_NOISE(_skipped = 0); + NOT_PRODUCT(_counted = 0); + NOT_PRODUCT(_total = 0); for (int i = 0; i < MAX_SNAPSHOTS; i++) { // Note that we don't now get perfdata from age_table @@ -48,10 +58,9 @@ ShenandoahAgeCensus::ShenandoahAgeCensus() { _tenuring_threshold[i] = MAX_COHORTS; } if (ShenandoahGenerationalAdaptiveTenuring && !ShenandoahGenerationalCensusAtEvac) { - size_t max_workers = ShenandoahHeap::heap()->max_workers(); - _local_age_table = NEW_C_HEAP_ARRAY(AgeTable*, max_workers, mtGC); + _local_age_table = NEW_C_HEAP_ARRAY(AgeTable*, _max_workers, mtGC); CENSUS_NOISE(_local_noise = NEW_C_HEAP_ARRAY(ShenandoahNoiseStats, max_workers, mtGC);) - for (uint i = 0; i < max_workers; i++) { + for (uint i = 0; i < _max_workers; i++) { _local_age_table[i] = new AgeTable(false); CENSUS_NOISE(_local_noise[i].clear();) } @@ -61,6 +70,22 @@ ShenandoahAgeCensus::ShenandoahAgeCensus() { _epoch = MAX_SNAPSHOTS - 1; // see update_epoch() } +ShenandoahAgeCensus::~ShenandoahAgeCensus() { + for (uint i = 0; i < MAX_SNAPSHOTS; i++) { + delete _global_age_table[i]; + } + FREE_C_HEAP_ARRAY(AgeTable*, _global_age_table); + FREE_C_HEAP_ARRAY(uint, _tenuring_threshold); + CENSUS_NOISE(FREE_C_HEAP_ARRAY(ShenandoahNoiseStats, _global_noise)); + if (_local_age_table) { + for (uint i = 0; i < _max_workers; i++) { + delete _local_age_table[i]; + } + FREE_C_HEAP_ARRAY(AgeTable*, _local_age_table); + CENSUS_NOISE(FREE_C_HEAP_ARRAY(ShenandoahNoiseStats, _local_noise)); + } +} + CENSUS_NOISE(void ShenandoahAgeCensus::add(uint obj_age, uint region_age, uint region_youth, size_t size, uint worker_id) {) NO_CENSUS_NOISE(void ShenandoahAgeCensus::add(uint obj_age, uint region_age, size_t size, uint worker_id) {) if (obj_age <= markWord::max_age) { @@ -131,12 +156,11 @@ void ShenandoahAgeCensus::update_census(size_t age0_pop, AgeTable* pv1, AgeTable assert(pv1 == nullptr && pv2 == nullptr, "Error, check caller"); // Seed cohort 0 with population that may have been missed during // regular census. - _global_age_table[_epoch]->add((uint)0, age0_pop); + _global_age_table[_epoch]->add(0, age0_pop); - size_t max_workers = ShenandoahHeap::heap()->max_workers(); // Merge data from local age tables into the global age table for the epoch, // clearing the local tables. - for (uint i = 0; i < max_workers; i++) { + for (uint i = 0; i < _max_workers; i++) { // age stats _global_age_table[_epoch]->merge(_local_age_table[i]); _local_age_table[i]->clear(); // clear for next census @@ -177,8 +201,7 @@ void ShenandoahAgeCensus::reset_local() { assert(_local_age_table == nullptr, "Error"); return; } - size_t max_workers = ShenandoahHeap::heap()->max_workers(); - for (uint i = 0; i < max_workers; i++) { + for (uint i = 0; i < _max_workers; i++) { _local_age_table[i]->clear(); CENSUS_NOISE(_local_noise[i].clear();) } @@ -204,8 +227,7 @@ bool ShenandoahAgeCensus::is_clear_local() { assert(_local_age_table == nullptr, "Error"); return true; } - size_t max_workers = ShenandoahHeap::heap()->max_workers(); - for (uint i = 0; i < max_workers; i++) { + for (uint i = 0; i < _max_workers; i++) { bool clear = _local_age_table[i]->is_clear(); CENSUS_NOISE(clear |= _local_noise[i].is_clear();) if (!clear) { @@ -285,7 +307,7 @@ uint ShenandoahAgeCensus::compute_tenuring_threshold() { } upper_bound = MIN2(upper_bound, markWord::max_age); - const uint lower_bound = MAX2((uint)ShenandoahGenerationalMinTenuringAge, (uint)1); + const uint lower_bound = MAX2((uint)ShenandoahGenerationalMinTenuringAge, 1u); uint tenuring_threshold = upper_bound; for (uint i = upper_bound; i >= lower_bound; i--) { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp index 89c68f7120bb7..6868b73512502 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp @@ -115,6 +115,8 @@ class ShenandoahAgeCensus: public CHeapObj { uint *_tenuring_threshold; // An array of the last N tenuring threshold values we // computed. + uint _max_workers; // Maximum number of workers for parallel tasks + // Mortality rate of a cohort, given its population in // previous and current epochs double mortality_rate(size_t prev_pop, size_t cur_pop); @@ -165,11 +167,13 @@ class ShenandoahAgeCensus: public CHeapObj { }; ShenandoahAgeCensus(); + ShenandoahAgeCensus(uint max_workers); + ~ShenandoahAgeCensus(); // Return the local age table (population vector) for worker_id. // Only used in the case of (ShenandoahGenerationalAdaptiveTenuring && !ShenandoahGenerationalCensusAtEvac) AgeTable* get_local_age_table(uint worker_id) { - return (AgeTable*) _local_age_table[worker_id]; + return _local_age_table[worker_id]; } // Update the local age table for worker_id by size for diff --git a/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp b/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp new file mode 100644 index 0000000000000..bbcc23bf30154 --- /dev/null +++ b/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp @@ -0,0 +1,72 @@ +/* + * 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/shenandoahAgeCensus.hpp" +#include "unittest.hpp" + +class ShenandoahAgeCensusTest : public ::testing::Test { +protected: + static constexpr size_t MinimumPopulationSize = 4*K; + static void build_mortality_rate_curve(ShenandoahAgeCensus& census, const double mortality_rates[], const size_t cohorts) { + constexpr size_t current_population = MinimumPopulationSize * 10; + + // Simulate the census for the first epoch with populations that will produce the + // expected mortality rate when presented with the populations for the subsequent epoch + for (size_t i = 1; i <= cohorts; i++) { + const size_t previous_population = current_population / (1.0 - mortality_rates[i]); + census.add(i, 0, 0, previous_population, 0); + } + + const size_t previous_population = current_population / (1.0 - mortality_rates[0]); + census.update_census(previous_population); + + for (size_t i = 1; i <= cohorts; i++) { + census.add(i, 0, 0, current_population, 0); + } + census.update_census(current_population); + } +}; + +TEST_F(ShenandoahAgeCensusTest, initialize) { + ShenandoahAgeCensus census(4); + EXPECT_EQ(census.tenuring_threshold(), ShenandoahAgeCensus::MAX_COHORTS); +} + +TEST_F(ShenandoahAgeCensusTest, ignore_small_populations) { + // Small populations are ignored so we do not return early before reaching the youngest cohort. + ShenandoahAgeCensus census(4); + census.add(1, 0, 0, 32, 0); + census.add(1, 0, 0, 32, 0); + census.update_census(64); + EXPECT_EQ(1u, census.tenuring_threshold()); +} + +TEST_VM_F(ShenandoahAgeCensusTest, find_high_mortality_rate) { + ShenandoahAgeCensus census(4); + constexpr double mortality_rates[] = { + 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, + 0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 + }; + build_mortality_rate_curve(census, mortality_rates, sizeof(mortality_rates) / sizeof(double)); +} \ No newline at end of file From 280d8d16ef7f2df50846541fcb3fd0aa454cef7d Mon Sep 17 00:00:00 2001 From: William Kemper Date: Mon, 11 Aug 2025 16:19:18 -0700 Subject: [PATCH 02/47] Assert current behavior is expected --- test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp b/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp index bbcc23bf30154..c82cdd7fa27ed 100644 --- a/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp +++ b/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp @@ -69,4 +69,5 @@ TEST_VM_F(ShenandoahAgeCensusTest, find_high_mortality_rate) { 0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }; build_mortality_rate_curve(census, mortality_rates, sizeof(mortality_rates) / sizeof(double)); -} \ No newline at end of file + EXPECT_EQ(2u, census.tenuring_threshold()); +} From 47a84e088f0c6024aebf891e27af841397d63336 Mon Sep 17 00:00:00 2001 From: William Kemper Date: Tue, 12 Aug 2025 15:25:47 -0700 Subject: [PATCH 03/47] Make evac tracking a runtime feature, add logging for plab management --- .../gc/shenandoah/shenandoahControlThread.cpp | 11 +- .../gc/shenandoah/shenandoahGeneration.cpp | 1 + .../shenandoah/shenandoahGenerationalHeap.cpp | 124 ++++++++++-------- .../share/gc/shenandoah/shenandoahHeap.cpp | 20 +-- .../gc/shenandoah/shenandoahOldGeneration.cpp | 6 + .../gc/shenandoah/shenandoah_globals.hpp | 7 + 6 files changed, 101 insertions(+), 68 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp index 6290101bc49d4..8a090452e34f0 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp @@ -210,12 +210,11 @@ void ShenandoahControlThread::run_service() { ResourceMark rm; LogStream ls(lt); heap->phase_timings()->print_cycle_on(&ls); -#ifdef NOT_PRODUCT - ShenandoahEvacuationTracker* evac_tracker = heap->evac_tracker(); - ShenandoahCycleStats evac_stats = evac_tracker->flush_cycle_to_global(); - evac_tracker->print_evacuations_on(&ls, &evac_stats.workers, - &evac_stats.mutators); -#endif + if (ShenandoahEvacTracking) { + ShenandoahEvacuationTracker* evac_tracker = heap->evac_tracker(); + ShenandoahCycleStats evac_stats = evac_tracker->flush_cycle_to_global(); + evac_tracker->print_evacuations_on(&ls, &evac_stats.workers, &evac_stats.mutators); + } } } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp index f686334d3d515..eddebc361a6e1 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp @@ -421,6 +421,7 @@ void ShenandoahGeneration::adjust_evacuation_budgets(ShenandoahHeap* const heap, size_t old_consumed = old_evacuated_committed + young_advance_promoted_reserve_used; if (old_available < old_consumed) { + log_info(gc, ergo)("Old has consumed more than available, truncate young promo reserve"); // This can happen due to round-off errors when adding the results of truncated integer arithmetic. // We've already truncated old_evacuated_committed. Truncate young_advance_promoted_reserve_used here. assert(young_advance_promoted_reserve_used <= (33 * (old_available - old_evacuated_committed)) / 32, diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp index d05ae713645af..3b2c9aff65337 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp @@ -267,6 +267,7 @@ oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, Shena // the requested object does not fit within the current plab but the plab still has an "abundance" of memory, // where abundance is defined as >= ShenGenHeap::plab_min_size(). In the former case, we try shrinking the // desired PLAB size to the minimum and retry PLAB allocation to avoid cascading of shared memory allocations. + // Shrinking the desired PLAB size may allow us to eke out a small PLAB while staying beneath evacuation reserve. if (plab->words_remaining() < plab_min_size()) { ShenandoahThreadLocalData::set_plab_size(thread, plab_min_size()); copy = allocate_from_plab(thread, size, is_promotion); @@ -324,8 +325,11 @@ oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, Shena return ShenandoahBarrierSet::resolve_forwarded(p); } + if (ShenandoahEvacTracking) { + evac_tracker()->begin_evacuation(thread, size * HeapWordSize, from_region->affiliation(), target_gen); + } + // Copy the object: - NOT_PRODUCT(evac_tracker()->begin_evacuation(thread, size * HeapWordSize, from_region->affiliation(), target_gen)); Copy::aligned_disjoint_words(cast_from_oop(p), copy, size); oop copy_val = cast_to_oop(copy); @@ -346,8 +350,9 @@ oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, Shena // safe to do this on the public copy (this is also done during concurrent mark). ContinuationGCSupport::relativize_stack_chunk(copy_val); - // Record that the evacuation succeeded - NOT_PRODUCT(evac_tracker()->end_evacuation(thread, size * HeapWordSize, from_region->affiliation(), target_gen)); + if (ShenandoahEvacTracking) { + evac_tracker()->end_evacuation(thread, size * HeapWordSize, from_region->affiliation(), target_gen); + } if (target_gen == OLD_GENERATION) { old_generation()->handle_evacuation(copy, size, from_region->is_young()); @@ -408,17 +413,21 @@ inline HeapWord* ShenandoahGenerationalHeap::allocate_from_plab(Thread* thread, assert(UseTLAB, "TLABs should be enabled"); PLAB* plab = ShenandoahThreadLocalData::plab(thread); - HeapWord* obj; if (plab == nullptr) { assert(!thread->is_Java_thread() && !thread->is_Worker_thread(), "Performance: thread should have PLAB: %s", thread->name()); // No PLABs in this thread, fallback to shared allocation + log_debug(gc, plab)("Thread has no PLAB"); return nullptr; - } else if (is_promotion && !ShenandoahThreadLocalData::allow_plab_promotions(thread)) { + } + + if (is_promotion && !ShenandoahThreadLocalData::allow_plab_promotions(thread)) { + log_debug(gc, plab)("Thread is not allowed to use PLAB for promotions"); return nullptr; } + // if plab->word_size() <= 0, thread's plab not yet initialized for this pass, so allow_plab_promotions() is not trustworthy - obj = plab->allocate(size); + HeapWord* obj = plab->allocate(size); if ((obj == nullptr) && (plab->words_remaining() < plab_min_size())) { // allocate_from_plab_slow will establish allow_plab_promotions(thread) for future invocations obj = allocate_from_plab_slow(thread, size, is_promotion); @@ -436,9 +445,8 @@ inline HeapWord* ShenandoahGenerationalHeap::allocate_from_plab(Thread* thread, // Establish a new PLAB and allocate size HeapWords within it. HeapWord* ShenandoahGenerationalHeap::allocate_from_plab_slow(Thread* thread, size_t size, bool is_promotion) { - // New object should fit the PLAB size - assert(mode()->is_generational(), "PLABs only relevant to generational GC"); + const size_t plab_min_size = this->plab_min_size(); // PLABs are aligned to card boundaries to avoid synchronization with concurrent // allocations in other PLABs. @@ -451,73 +459,81 @@ HeapWord* ShenandoahGenerationalHeap::allocate_from_plab_slow(Thread* thread, si } // Expand aggressively, doubling at each refill in this epoch, ceiling at plab_max_size() - size_t future_size = MIN2(cur_size * 2, plab_max_size()); + const size_t future_size = MIN2(cur_size * 2, plab_max_size()); // Doubling, starting at a card-multiple, should give us a card-multiple. (Ceiling and floor // are card multiples.) assert(is_aligned(future_size, CardTable::card_size_in_words()), "Card multiple by construction, future_size: %zu" - ", card_size: %zu, cur_size: %zu, max: %zu", - future_size, (size_t) CardTable::card_size_in_words(), cur_size, plab_max_size()); + ", card_size: %u, cur_size: %zu, max: %zu", + future_size, CardTable::card_size_in_words(), cur_size, plab_max_size()); // Record new heuristic value even if we take any shortcut. This captures // the case when moderately-sized objects always take a shortcut. At some point, // heuristics should catch up with them. Note that the requested cur_size may // not be honored, but we remember that this is the preferred size. - log_debug(gc, free)("Set new PLAB size: %zu", future_size); + log_debug(gc, plab)("Set new PLAB size: %zu", future_size); ShenandoahThreadLocalData::set_plab_size(thread, future_size); + if (cur_size < size) { // The PLAB to be allocated is still not large enough to hold the object. Fall back to shared allocation. // This avoids retiring perfectly good PLABs in order to represent a single large object allocation. - log_debug(gc, free)("Current PLAB size (%zu) is too small for %zu", cur_size, size); + log_debug(gc, plab)("Current PLAB size (%zu) is too small for %zu", cur_size, size); return nullptr; } - // Retire current PLAB, and allocate a new one. PLAB* plab = ShenandoahThreadLocalData::plab(thread); - if (plab->words_remaining() < plab_min_size) { - // Retire current PLAB. This takes care of any PLAB book-keeping. - // retire_plab() registers the remnant filler object with the remembered set scanner without a lock. - // Since PLABs are card-aligned, concurrent registrations in other PLABs don't interfere. - retire_plab(plab, thread); - - size_t actual_size = 0; - HeapWord* plab_buf = allocate_new_plab(min_size, cur_size, &actual_size); - if (plab_buf == nullptr) { - if (min_size == plab_min_size) { - // Disable PLAB promotions for this thread because we cannot even allocate a minimal PLAB. This allows us - // to fail faster on subsequent promotion attempts. - ShenandoahThreadLocalData::disable_plab_promotions(thread); - } - return nullptr; - } else { - ShenandoahThreadLocalData::enable_plab_retries(thread); - } - // Since the allocated PLAB may have been down-sized for alignment, plab->allocate(size) below may still fail. - if (ZeroTLAB) { - // ... and clear it. - Copy::zero_to_words(plab_buf, actual_size); - } else { - // ...and zap just allocated object. -#ifdef ASSERT - // Skip mangling the space corresponding to the object header to - // ensure that the returned space is not considered parsable by - // any concurrent GC thread. - size_t hdr_size = oopDesc::header_size(); - Copy::fill_to_words(plab_buf + hdr_size, actual_size - hdr_size, badHeapWordVal); -#endif // ASSERT - } - assert(is_aligned(actual_size, CardTable::card_size_in_words()), "Align by design"); - plab->set_buf(plab_buf, actual_size); - if (is_promotion && !ShenandoahThreadLocalData::allow_plab_promotions(thread)) { - return nullptr; - } - return plab->allocate(size); - } else { + if (plab->words_remaining() >= plab_min_size) { // If there's still at least min_size() words available within the current plab, don't retire it. Let's nibble // away on this plab as long as we can. Meanwhile, return nullptr to force this particular allocation request // to be satisfied with a shared allocation. By packing more promotions into the previously allocated PLAB, we // reduce the likelihood of evacuation failures, and we reduce the need for downsizing our PLABs. + log_debug(gc, plab)("Existing PLAB is still viable (words remaining: %zu, plab_min_size: %zu", plab->words_remaining(), plab_min_size); return nullptr; } + + // The current plab has fewer words remaining than the minimum PLAB. Retire it. This takes care of any PLAB book-keeping. + // retire_plab() registers the remnant filler object with the remembered set scanner without a lock. + // Since PLABs are card-aligned, concurrent registrations in other PLABs don't interfere. + retire_plab(plab, thread); + + size_t actual_size = 0; + HeapWord* plab_buf = allocate_new_plab(min_size, cur_size, &actual_size); + if (plab_buf == nullptr) { + if (min_size == plab_min_size) { + // Disable PLAB promotions for this thread because we cannot even allocate a minimal PLAB. This allows us + // to fail faster on subsequent promotion attempts. + log_debug(gc, plab)("Disable PLAB promotions because we can't allocate minimum sized PLAB: %zu", min_size); + ShenandoahThreadLocalData::disable_plab_promotions(thread); + } + return nullptr; + } + + log_debug(gc, plab)("Allocated new PLAB of size: %zu, enable PLAB retries", actual_size); + ShenandoahThreadLocalData::enable_plab_retries(thread); + + + if (ZeroTLAB) { + // ... and clear it. + Copy::zero_to_words(plab_buf, actual_size); + } else { + // ...and zap just allocated object. +#ifdef ASSERT + // Skip mangling the space corresponding to the object header to + // ensure that the returned space is not considered parsable by + // any concurrent GC thread. + const size_t hdr_size = oopDesc::header_size(); + Copy::fill_to_words(plab_buf + hdr_size, actual_size - hdr_size, badHeapWordVal); +#endif // ASSERT + } + + assert(is_aligned(actual_size, CardTable::card_size_in_words()), "Align by design"); + plab->set_buf(plab_buf, actual_size); + if (is_promotion && !ShenandoahThreadLocalData::allow_plab_promotions(thread)) { + log_debug(gc, plab)("Thread has new PLAB of size %zu, but is not allowed to use it", actual_size); + return nullptr; + } + + // Since the allocated PLAB may have been down-sized for alignment, plab->allocate(size) below may still fail. + return plab->allocate(size); } HeapWord* ShenandoahGenerationalHeap::allocate_new_plab(size_t min_size, size_t word_size, size_t* actual_size) { @@ -564,7 +580,7 @@ void ShenandoahGenerationalHeap::retire_plab(PLAB* plab, Thread* thread) { if (top != nullptr && plab->waste() > original_waste && is_in_old(top)) { // If retiring the plab created a filler object, then we need to register it with our card scanner so it can // safely walk the region backing the plab. - log_debug(gc)("retire_plab() is registering remnant of size %zu at " PTR_FORMAT, + log_debug(gc, plab)("retire_plab() is registering remnant of size %zu at " PTR_FORMAT, plab->waste() - original_waste, p2i(top)); // No lock is necessary because the PLAB memory is aligned on card boundaries. old_generation()->card_scan()->register_object_without_lock(top); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp index 2baf7e25cbabb..0bc2832902b1f 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp @@ -1368,7 +1368,10 @@ oop ShenandoahHeap::try_evacuate_object(oop p, Thread* thread, ShenandoahHeapReg } // Copy the object: - NOT_PRODUCT(evac_tracker()->begin_evacuation(thread, size * HeapWordSize, from_region->affiliation(), target_gen)); + if (ShenandoahEvacTracking) { + evac_tracker()->begin_evacuation(thread, size * HeapWordSize, from_region->affiliation(), target_gen); + } + Copy::aligned_disjoint_words(cast_from_oop(p), copy, size); // Try to install the new forwarding pointer. @@ -1378,7 +1381,9 @@ oop ShenandoahHeap::try_evacuate_object(oop p, Thread* thread, ShenandoahHeapReg // Successfully evacuated. Our copy is now the public one! ContinuationGCSupport::relativize_stack_chunk(copy_val); shenandoah_assert_correct(nullptr, copy_val); - NOT_PRODUCT(evac_tracker()->end_evacuation(thread, size * HeapWordSize, from_region->affiliation(), target_gen)); + if (ShenandoahEvacTracking) { + evac_tracker()->end_evacuation(thread, size * HeapWordSize, from_region->affiliation(), target_gen); + } return copy_val; } else { // Failed to evacuate. We need to deal with the object that is left behind. Since this @@ -1608,12 +1613,11 @@ void ShenandoahHeap::print_tracing_info() const { ResourceMark rm; LogStream ls(lt); -#ifdef NOT_PRODUCT - evac_tracker()->print_global_on(&ls); - - ls.cr(); - ls.cr(); -#endif + if (ShenandoahEvacTracking) { + evac_tracker()->print_global_on(&ls); + ls.cr(); + ls.cr(); + } phase_timings()->print_global_on(&ls); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.cpp b/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.cpp index 5cccd395d3819..be69906904dcc 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.cpp @@ -227,6 +227,7 @@ ShenandoahOldGeneration::ShenandoahOldGeneration(uint max_queues, size_t max_cap void ShenandoahOldGeneration::set_promoted_reserve(size_t new_val) { shenandoah_assert_heaplocked_or_safepoint(); + log_info(gc, ergo)("Changing promotion reserve from %zu to %zu", _promoted_reserve, new_val); _promoted_reserve = new_val; } @@ -300,6 +301,8 @@ ShenandoahOldGeneration::configure_plab_for_current_thread(const ShenandoahAlloc if (can_promote(actual_size)) { // Assume the entirety of this PLAB will be used for promotion. This prevents promotion from overreach. // When we retire this plab, we'll unexpend what we don't really use. + log_debug(gc, plab)("Thread can promote using PLAB of %zu bytes. Expended: %zu, available: %zu", + actual_size, get_promoted_expended(), get_promoted_reserve()); expend_promoted(actual_size); ShenandoahThreadLocalData::enable_plab_promotions(thread); ShenandoahThreadLocalData::set_plab_actual_size(thread, actual_size); @@ -307,9 +310,12 @@ ShenandoahOldGeneration::configure_plab_for_current_thread(const ShenandoahAlloc // Disable promotions in this thread because entirety of this PLAB must be available to hold old-gen evacuations. ShenandoahThreadLocalData::disable_plab_promotions(thread); ShenandoahThreadLocalData::set_plab_actual_size(thread, 0); + log_debug(gc, plab)("Thread cannot promote using PLAB of %zu bytes. Expended: %zu, available: %zu", + actual_size, get_promoted_expended(), get_promoted_reserve()); } } else if (req.is_promotion()) { // Shared promotion. + log_debug(gc, plab)("Expend shared promotion of %zu bytes", actual_size); expend_promoted(actual_size); } } diff --git a/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp b/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp index c51f4f164895d..50c2733479916 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp @@ -396,6 +396,13 @@ "events.") \ range(0,100) \ \ + product(bool, ShenandoahEvacTracking, false, DIAGNOSTIC, \ + "Collect additional metrics about evacuations. Enabling this " \ + "track how many objects and how many bytes were evacuated, and " \ + "how many were abandoned. The information will be categorized " \ + "by thread type (worker or mutator) and evacuation type (young, " \ + "old, or promotion.") \ + \ product(uintx, ShenandoahMinYoungPercentage, 20, EXPERIMENTAL, \ "The minimum percentage of the heap to use for the young " \ "generation. Heuristics will not adjust the young generation " \ From 43d25e31d23a08407d3ba6b53babb890e030c39e Mon Sep 17 00:00:00 2001 From: William Kemper Date: Wed, 13 Aug 2025 12:09:01 -0700 Subject: [PATCH 04/47] Instrumentation tweaks --- .../gc/shenandoah/shenandoahGeneration.cpp | 4 +++- .../shenandoah/shenandoahGenerationalHeap.cpp | 23 ++++++++++++------- .../gc/shenandoah/shenandoahOldGeneration.cpp | 6 ++--- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp index eddebc361a6e1..d4db285453634 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp @@ -479,7 +479,9 @@ void ShenandoahGeneration::adjust_evacuation_budgets(ShenandoahHeap* const heap, // Add in the excess_old memory to hold unanticipated promotions, if any. If there are more unanticipated // promotions than fit in reserved memory, they will be deferred until a future GC pass. - size_t total_promotion_reserve = young_advance_promoted_reserve_used + excess_old; + const size_t total_promotion_reserve = young_advance_promoted_reserve_used + excess_old; + log_info(gc, ergo)("Changing promotion reserve from %zu to %zu (young advance: %zu, old excess: %zu)", + old_generation->get_promoted_reserve(), total_promotion_reserve, young_advance_promoted_reserve_used, excess_old); old_generation->set_promoted_reserve(total_promotion_reserve); old_generation->reset_promoted_expended(); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp index 3b2c9aff65337..2a60c3a0bc030 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp @@ -295,6 +295,9 @@ oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, Shena ShenandoahAllocRequest req = ShenandoahAllocRequest::for_shared_gc(size, target_gen, is_promotion); copy = allocate_memory(req); alloc_from_lab = false; + if (is_promotion && copy != nullptr) { + log_debug(gc, plab)("Made a shared promotion of size: %zu, min PLAB: %zu", size, PLAB::min_size()); + } } // else, we leave copy equal to nullptr, signaling a promotion failure below if appropriate. // We choose not to promote objects smaller than PLAB::min_size() by way of shared allocations, as this is too @@ -422,7 +425,7 @@ inline HeapWord* ShenandoahGenerationalHeap::allocate_from_plab(Thread* thread, } if (is_promotion && !ShenandoahThreadLocalData::allow_plab_promotions(thread)) { - log_debug(gc, plab)("Thread is not allowed to use PLAB for promotions"); + log_develop_trace(gc, plab)("Thread is not allowed to use PLAB for promotions"); return nullptr; } @@ -470,13 +473,13 @@ HeapWord* ShenandoahGenerationalHeap::allocate_from_plab_slow(Thread* thread, si // the case when moderately-sized objects always take a shortcut. At some point, // heuristics should catch up with them. Note that the requested cur_size may // not be honored, but we remember that this is the preferred size. - log_debug(gc, plab)("Set new PLAB size: %zu", future_size); + log_debug(gc, plab)("Set next PLAB refill size: %zu bytes", future_size * HeapWordSize); ShenandoahThreadLocalData::set_plab_size(thread, future_size); if (cur_size < size) { // The PLAB to be allocated is still not large enough to hold the object. Fall back to shared allocation. // This avoids retiring perfectly good PLABs in order to represent a single large object allocation. - log_debug(gc, plab)("Current PLAB size (%zu) is too small for %zu", cur_size, size); + log_debug(gc, plab)("Current PLAB size (%zu) is too small for %zu", cur_size * HeapWordSize, size * HeapWordSize); return nullptr; } @@ -486,7 +489,7 @@ HeapWord* ShenandoahGenerationalHeap::allocate_from_plab_slow(Thread* thread, si // away on this plab as long as we can. Meanwhile, return nullptr to force this particular allocation request // to be satisfied with a shared allocation. By packing more promotions into the previously allocated PLAB, we // reduce the likelihood of evacuation failures, and we reduce the need for downsizing our PLABs. - log_debug(gc, plab)("Existing PLAB is still viable (words remaining: %zu, plab_min_size: %zu", plab->words_remaining(), plab_min_size); + log_debug(gc, plab)("Existing PLAB is still viable (words remaining: %zu, plab_min_size: %zu)", plab->words_remaining(), plab_min_size); return nullptr; } @@ -501,13 +504,13 @@ HeapWord* ShenandoahGenerationalHeap::allocate_from_plab_slow(Thread* thread, si if (min_size == plab_min_size) { // Disable PLAB promotions for this thread because we cannot even allocate a minimal PLAB. This allows us // to fail faster on subsequent promotion attempts. - log_debug(gc, plab)("Disable PLAB promotions because we can't allocate minimum sized PLAB: %zu", min_size); + log_debug(gc, plab)("Disable PLAB promotions because we can't allocate minimum sized PLAB: %zu", min_size * HeapWordSize); ShenandoahThreadLocalData::disable_plab_promotions(thread); } return nullptr; } - log_debug(gc, plab)("Allocated new PLAB of size: %zu, enable PLAB retries", actual_size); + log_debug(gc, plab)("Allocated new PLAB of size: %zu bytes, enable PLAB retries", actual_size * HeapWordSize); ShenandoahThreadLocalData::enable_plab_retries(thread); @@ -528,7 +531,10 @@ HeapWord* ShenandoahGenerationalHeap::allocate_from_plab_slow(Thread* thread, si assert(is_aligned(actual_size, CardTable::card_size_in_words()), "Align by design"); plab->set_buf(plab_buf, actual_size); if (is_promotion && !ShenandoahThreadLocalData::allow_plab_promotions(thread)) { - log_debug(gc, plab)("Thread has new PLAB of size %zu, but is not allowed to use it", actual_size); + // Thinking here is that the thread has exhausted promotion reserve, but there may yet be old objects + // to evacuate and this plab could be used for those. + log_debug(gc, plab)("Thread has new PLAB of size %zu, but is not allowed to promote %zu. Mixed evac in progress? %s", + actual_size * HeapWordSize, size * HeapWordSize, BOOL_TO_STR(collection_set()->has_old_regions())); return nullptr; } @@ -569,6 +575,7 @@ void ShenandoahGenerationalHeap::retire_plab(PLAB* plab, Thread* thread) { ShenandoahThreadLocalData::reset_plab_promoted(thread); ShenandoahThreadLocalData::set_plab_actual_size(thread, 0); if (not_promoted > 0) { + log_debug(gc, plab)("Retire PLAB, unexpend unpromoted: %zu", not_promoted * HeapWordSize); old_generation()->unexpend_promoted(not_promoted); } const size_t original_waste = plab->waste(); @@ -581,7 +588,7 @@ void ShenandoahGenerationalHeap::retire_plab(PLAB* plab, Thread* thread) { // If retiring the plab created a filler object, then we need to register it with our card scanner so it can // safely walk the region backing the plab. log_debug(gc, plab)("retire_plab() is registering remnant of size %zu at " PTR_FORMAT, - plab->waste() - original_waste, p2i(top)); + (plab->waste() - original_waste) * HeapWordSize, p2i(top)); // No lock is necessary because the PLAB memory is aligned on card boundaries. old_generation()->card_scan()->register_object_without_lock(top); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.cpp b/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.cpp index be69906904dcc..8213b65ef8cd7 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.cpp @@ -227,7 +227,6 @@ ShenandoahOldGeneration::ShenandoahOldGeneration(uint max_queues, size_t max_cap void ShenandoahOldGeneration::set_promoted_reserve(size_t new_val) { shenandoah_assert_heaplocked_or_safepoint(); - log_info(gc, ergo)("Changing promotion reserve from %zu to %zu", _promoted_reserve, new_val); _promoted_reserve = new_val; } @@ -682,12 +681,11 @@ void ShenandoahOldGeneration::handle_failed_promotion(Thread* thread, size_t siz static size_t epoch_report_count = 0; auto heap = ShenandoahGenerationalHeap::heap(); - size_t promotion_reserve; - size_t promotion_expended; - const size_t gc_id = heap->control_thread()->get_gc_id(); if ((gc_id != last_report_epoch) || (epoch_report_count++ < MaxReportsPerEpoch)) { + size_t promotion_expended; + size_t promotion_reserve; { // Promotion failures should be very rare. Invest in providing useful diagnostic info. ShenandoahHeapLocker locker(heap->lock()); From c3b4e89c0d3308d39b68387b8bfbe788d4bb946b Mon Sep 17 00:00:00 2001 From: William Kemper Date: Wed, 13 Aug 2025 14:22:55 -0700 Subject: [PATCH 05/47] Instrumentation tweaks --- src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp | 2 ++ .../share/gc/shenandoah/shenandoahGenerationalHeap.cpp | 4 +++- src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp index 81154aff9f0ad..6a0c56cd0d532 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp @@ -834,6 +834,7 @@ class ShenandoahConcurrentEvacThreadClosure : public ThreadClosure { JavaThread* const jt = JavaThread::cast(thread); StackWatermarkSet::finish_processing(jt, _oops, StackWatermarkKind::gc); if (GENERATIONAL) { + log_debug(gc, plab)("Conc evac closure: enable plab promotions for java thread"); ShenandoahThreadLocalData::enable_plab_promotions(thread); } } @@ -853,6 +854,7 @@ class ShenandoahConcurrentEvacUpdateThreadTask : public WorkerTask { void work(uint worker_id) override { if (GENERATIONAL) { Thread* worker_thread = Thread::current(); + log_debug(gc, plab)("Conc evac update task: enable plab promotions for worker"); ShenandoahThreadLocalData::enable_plab_promotions(worker_thread); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp index 2a60c3a0bc030..3f44fe6f26acb 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp @@ -296,7 +296,9 @@ oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, Shena copy = allocate_memory(req); alloc_from_lab = false; if (is_promotion && copy != nullptr) { - log_debug(gc, plab)("Made a shared promotion of size: %zu, min PLAB: %zu", size, PLAB::min_size()); + log_debug(gc, plab)("Made a shared promotion of size: %zu, actual PLAB size for thread: %zu, min PLAB: %zu, max PLAB: %zu", + size * HeapWordSize, ShenandoahThreadLocalData::get_plab_actual_size(thread) * HeapWordSize, + PLAB::min_size() * HeapWordSize, plab_max_size() * HeapWordSize); } } // else, we leave copy equal to nullptr, signaling a promotion failure below if appropriate. diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp index 0bc2832902b1f..3f68e632d37d4 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp @@ -1260,7 +1260,7 @@ void ShenandoahHeap::evacuate_collection_set(bool concurrent) { void ShenandoahHeap::concurrent_prepare_for_update_refs() { { - // Java threads take this lock while they are being attached and added to the list of thread. + // Java threads take this lock while they are being attached and added to the list of threads. // If another thread holds this lock before we update the gc state, it will receive a stale // gc state, but they will have been added to the list of java threads and so will be corrected // by the following handshake. From be111f0c23e02f9867653ecc3b8f6ee443d0f447 Mon Sep 17 00:00:00 2001 From: William Kemper Date: Wed, 13 Aug 2025 15:39:03 -0700 Subject: [PATCH 06/47] Are some threads not getting plab promotions re-enabled? --- src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp | 2 -- src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.hpp | 3 +++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp index 6a0c56cd0d532..81154aff9f0ad 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp @@ -834,7 +834,6 @@ class ShenandoahConcurrentEvacThreadClosure : public ThreadClosure { JavaThread* const jt = JavaThread::cast(thread); StackWatermarkSet::finish_processing(jt, _oops, StackWatermarkKind::gc); if (GENERATIONAL) { - log_debug(gc, plab)("Conc evac closure: enable plab promotions for java thread"); ShenandoahThreadLocalData::enable_plab_promotions(thread); } } @@ -854,7 +853,6 @@ class ShenandoahConcurrentEvacUpdateThreadTask : public WorkerTask { void work(uint worker_id) override { if (GENERATIONAL) { Thread* worker_thread = Thread::current(); - log_debug(gc, plab)("Conc evac update task: enable plab promotions for worker"); ShenandoahThreadLocalData::enable_plab_promotions(worker_thread); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.hpp b/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.hpp index 098e20a72ec8a..f7a19c6dce17e 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.hpp @@ -38,6 +38,7 @@ #include "gc/shenandoah/shenandoahGenerationalHeap.hpp" #include "gc/shenandoah/shenandoahSATBMarkQueueSet.hpp" #include "runtime/javaThread.hpp" +#include "runtime/osThread.hpp" #include "utilities/debug.hpp" #include "utilities/sizes.hpp" @@ -201,10 +202,12 @@ class ShenandoahThreadLocalData { } static void enable_plab_promotions(Thread* thread) { + log_debug(gc, plab)("Enable PLAB promotions for thread: %d (java? %s)", thread->osthread()->thread_id(), BOOL_TO_STR(thread->is_Java_thread())); data(thread)->_plab_allows_promotion = true; } static void disable_plab_promotions(Thread* thread) { + log_debug(gc, plab)("Disable PLAB promotions for thread: %d", thread->osthread()->thread_id()); data(thread)->_plab_allows_promotion = false; } From 042953042dabd5d04c9b977bd6ea00e4322f24a7 Mon Sep 17 00:00:00 2001 From: William Kemper Date: Thu, 14 Aug 2025 10:26:19 -0700 Subject: [PATCH 07/47] Re-enable plab promotions for all threads when plabs are retired --- .../gc/shenandoah/shenandoahConcurrentGC.cpp | 23 ++++--------------- .../share/gc/shenandoah/shenandoahHeap.cpp | 5 ++++ 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp index 81154aff9f0ad..d4c7ad5df50bf 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp @@ -823,7 +823,6 @@ bool ShenandoahConcurrentGC::has_in_place_promotions(ShenandoahHeap* heap) { return heap->mode()->is_generational() && heap->old_generation()->has_in_place_promotions(); } -template class ShenandoahConcurrentEvacThreadClosure : public ThreadClosure { private: OopClosure* const _oops; @@ -833,13 +832,9 @@ class ShenandoahConcurrentEvacThreadClosure : public ThreadClosure { void do_thread(Thread* thread) override { JavaThread* const jt = JavaThread::cast(thread); StackWatermarkSet::finish_processing(jt, _oops, StackWatermarkKind::gc); - if (GENERATIONAL) { - ShenandoahThreadLocalData::enable_plab_promotions(thread); - } } }; -template class ShenandoahConcurrentEvacUpdateThreadTask : public WorkerTask { private: ShenandoahJavaThreadsIterator _java_threads; @@ -851,30 +846,20 @@ class ShenandoahConcurrentEvacUpdateThreadTask : public WorkerTask { } void work(uint worker_id) override { - if (GENERATIONAL) { - Thread* worker_thread = Thread::current(); - ShenandoahThreadLocalData::enable_plab_promotions(worker_thread); - } - // ShenandoahEvacOOMScope has to be setup by ShenandoahContextEvacuateUpdateRootsClosure. // Otherwise, may deadlock with watermark lock ShenandoahContextEvacuateUpdateRootsClosure oops_cl; - ShenandoahConcurrentEvacThreadClosure thr_cl(&oops_cl); + ShenandoahConcurrentEvacThreadClosure thr_cl(&oops_cl); _java_threads.threads_do(&thr_cl, worker_id); } }; void ShenandoahConcurrentGC::op_thread_roots() { - ShenandoahHeap* const heap = ShenandoahHeap::heap(); + const ShenandoahHeap* const heap = ShenandoahHeap::heap(); assert(heap->is_evacuation_in_progress(), "Checked by caller"); ShenandoahGCWorkerPhase worker_phase(ShenandoahPhaseTimings::conc_thread_roots); - if (heap->mode()->is_generational()) { - ShenandoahConcurrentEvacUpdateThreadTask task(heap->workers()->active_workers()); - heap->workers()->run_task(&task); - } else { - ShenandoahConcurrentEvacUpdateThreadTask task(heap->workers()->active_workers()); - heap->workers()->run_task(&task); - } + ShenandoahConcurrentEvacUpdateThreadTask task(heap->workers()->active_workers()); + heap->workers()->run_task(&task); } void ShenandoahConcurrentGC::op_weak_refs() { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp index 3f68e632d37d4..f29bcfb6ebf80 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp @@ -1216,6 +1216,11 @@ class ShenandoahRetireGCLABClosure : public ThreadClosure { // 1. We need to make the plab memory parsable by remembered-set scanning. // 2. We need to establish a trustworthy UpdateWaterMark value within each old-gen heap region ShenandoahGenerationalHeap::heap()->retire_plab(plab, thread); + + // Re-enable promotions for the next evacuation phase. + ShenandoahThreadLocalData::enable_plab_promotions(thread); + + // Reset the fill size for next evacuation phase. if (_resize && ShenandoahThreadLocalData::plab_size(thread) > 0) { ShenandoahThreadLocalData::set_plab_size(thread, 0); } From 7dd61a3515cbc681fb7a5b25e7be448a006972df Mon Sep 17 00:00:00 2001 From: William Kemper Date: Thu, 14 Aug 2025 12:02:11 -0700 Subject: [PATCH 08/47] Remove unused ShenandoahThreadLocalData::_paced_time --- .../gc/shenandoah/shenandoahThreadLocalData.cpp | 1 - .../gc/shenandoah/shenandoahThreadLocalData.hpp | 14 -------------- 2 files changed, 15 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.cpp b/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.cpp index dd500462d0ffc..ace5ab5e69abf 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.cpp @@ -37,7 +37,6 @@ ShenandoahThreadLocalData::ShenandoahThreadLocalData() : _card_table(nullptr), _gclab(nullptr), _gclab_size(0), - _paced_time(0), _plab(nullptr), _plab_desired_size(0), _plab_actual_size(0), diff --git a/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.hpp b/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.hpp index f7a19c6dce17e..ad6340f8c78ca 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.hpp @@ -59,8 +59,6 @@ class ShenandoahThreadLocalData { PLAB* _gclab; size_t _gclab_size; - double _paced_time; - // Thread-local allocation buffer only used in generational mode. // Used both by mutator threads and by GC worker threads // for evacuations within the old generation and @@ -240,18 +238,6 @@ class ShenandoahThreadLocalData { return data(thread)->_plab_actual_size; } - static void add_paced_time(Thread* thread, double v) { - data(thread)->_paced_time += v; - } - - static double paced_time(Thread* thread) { - return data(thread)->_paced_time; - } - - static void reset_paced_time(Thread* thread) { - data(thread)->_paced_time = 0; - } - // Evacuation OOM handling static bool is_oom_during_evac(Thread* thread) { return data(thread)->_oom_during_evac; From 101cdf2f3a1faa8922884ccdb786e3841670a379 Mon Sep 17 00:00:00 2001 From: William Kemper Date: Thu, 14 Aug 2025 15:56:42 -0700 Subject: [PATCH 09/47] Log percentage of population which is above tenuring threshold --- .../share/gc/shenandoah/shenandoahAgeCensus.cpp | 15 ++++++++++++--- .../gc/shenandoah/shenandoahOldGeneration.cpp | 4 ++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp index 94c98b78f1bc0..8225cf4a5fa88 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp @@ -346,6 +346,7 @@ void ShenandoahAgeCensus::print() { const uint tt = tenuring_threshold(); + size_t total_tenurable = 0; size_t total= 0; for (uint i = 1; i < MAX_COHORTS; i++) { const size_t prev_pop = prev_pv->sizes[i-1]; // (i-1) OK because i >= 1 @@ -358,11 +359,19 @@ void ShenandoahAgeCensus::print() { i, prev_pop*oopSize, cur_pop*oopSize, mr); } total += cur_pop; - if (i == tt) { - // Underline the cohort for tenuring threshold (if < MAX_COHORTS) - log_info(gc, age)("----------------------------------------------------------------------------"); + + if ( i >= tt) { + total_tenurable += cur_pop; + if (i == tt) { + // Underline the cohort for tenuring threshold (if < MAX_COHORTS) + log_info(gc, age)("----------------------------------------------------------------------------"); + } } } + + log_info(gc, age)("%.3f of population meets tenuring threshold (%u). Total: (%zu), Tenurable: (%zu)", + double(total_tenurable) / double(MAX2(total, 1UL)), tt, total, total_tenurable); + CENSUS_NOISE(_global_noise[cur_epoch].print(total);) } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.cpp b/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.cpp index 8213b65ef8cd7..fbf86f7abc566 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.cpp @@ -309,8 +309,8 @@ ShenandoahOldGeneration::configure_plab_for_current_thread(const ShenandoahAlloc // Disable promotions in this thread because entirety of this PLAB must be available to hold old-gen evacuations. ShenandoahThreadLocalData::disable_plab_promotions(thread); ShenandoahThreadLocalData::set_plab_actual_size(thread, 0); - log_debug(gc, plab)("Thread cannot promote using PLAB of %zu bytes. Expended: %zu, available: %zu", - actual_size, get_promoted_expended(), get_promoted_reserve()); + log_debug(gc, plab)("Thread cannot promote using PLAB of %zu bytes. Expended: %zu, available: %zu, mixed evacuations? %s", + actual_size, get_promoted_expended(), get_promoted_reserve(), BOOL_TO_STR(ShenandoahHeap::heap()->collection_set()->has_old_regions())); } } else if (req.is_promotion()) { // Shared promotion. From c6a3467483eb208da6b5dc9835a2f5c891d4da51 Mon Sep 17 00:00:00 2001 From: William Kemper Date: Thu, 14 Aug 2025 16:36:08 -0700 Subject: [PATCH 10/47] Tone down some log messages, draw the tenuring threshold line above the first cohort that would be tenured --- .../share/gc/shenandoah/shenandoahAgeCensus.cpp | 15 ++++++++------- .../gc/shenandoah/shenandoahThreadLocalData.hpp | 4 ++-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp index 8225cf4a5fa88..755a98939c51b 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp @@ -352,13 +352,6 @@ void ShenandoahAgeCensus::print() { const size_t prev_pop = prev_pv->sizes[i-1]; // (i-1) OK because i >= 1 const size_t cur_pop = cur_pv->sizes[i]; double mr = mortality_rate(prev_pop, cur_pop); - // Suppress printing when everything is zero - if (prev_pop + cur_pop > 0) { - log_info(gc, age) - (" - age %3u: prev %10zu bytes, curr %10zu bytes, mortality %.2f ", - i, prev_pop*oopSize, cur_pop*oopSize, mr); - } - total += cur_pop; if ( i >= tt) { total_tenurable += cur_pop; @@ -367,6 +360,14 @@ void ShenandoahAgeCensus::print() { log_info(gc, age)("----------------------------------------------------------------------------"); } } + + // Suppress printing when everything is zero + if (prev_pop + cur_pop > 0) { + log_info(gc, age) + (" - age %3u: prev %10zu bytes, curr %10zu bytes, mortality %.2f ", + i, prev_pop*oopSize, cur_pop*oopSize, mr); + } + total += cur_pop; } log_info(gc, age)("%.3f of population meets tenuring threshold (%u). Total: (%zu), Tenurable: (%zu)", diff --git a/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.hpp b/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.hpp index ad6340f8c78ca..31b62bad088b6 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.hpp @@ -200,12 +200,12 @@ class ShenandoahThreadLocalData { } static void enable_plab_promotions(Thread* thread) { - log_debug(gc, plab)("Enable PLAB promotions for thread: %d (java? %s)", thread->osthread()->thread_id(), BOOL_TO_STR(thread->is_Java_thread())); + log_develop_trace(gc, plab)("Enable PLAB promotions for thread: %d (java? %s)", thread->osthread()->thread_id(), BOOL_TO_STR(thread->is_Java_thread())); data(thread)->_plab_allows_promotion = true; } static void disable_plab_promotions(Thread* thread) { - log_debug(gc, plab)("Disable PLAB promotions for thread: %d", thread->osthread()->thread_id()); + log_develop_trace(gc, plab)("Disable PLAB promotions for thread: %d", thread->osthread()->thread_id()); data(thread)->_plab_allows_promotion = false; } From 038aa46086940d5735e0c080f644910c66b9cc8d Mon Sep 17 00:00:00 2001 From: William Kemper Date: Fri, 15 Aug 2025 13:12:47 -0700 Subject: [PATCH 11/47] Remove census-at-evac option Some of the code is left behind to continue collecting the census when adaptive tenuring is disabled. --- .../share/gc/shenandoah/shenandoahAgeCensus.cpp | 8 ++++---- .../share/gc/shenandoah/shenandoahAgeCensus.hpp | 13 ++++++------- .../share/gc/shenandoah/shenandoahEvacTracker.cpp | 4 ++-- .../share/gc/shenandoah/shenandoahGeneration.cpp | 2 +- .../gc/shenandoah/shenandoahGenerationalHeap.cpp | 2 +- .../share/gc/shenandoah/shenandoahMark.inline.hpp | 2 +- .../share/gc/shenandoah/shenandoah_globals.hpp | 4 ---- 7 files changed, 15 insertions(+), 20 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp index 755a98939c51b..56bed0d3b89cb 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp @@ -47,7 +47,7 @@ ShenandoahAgeCensus::ShenandoahAgeCensus() { // Sentinel value _tenuring_threshold[i] = MAX_COHORTS; } - if (ShenandoahGenerationalAdaptiveTenuring && !ShenandoahGenerationalCensusAtEvac) { + if (ShenandoahGenerationalAdaptiveTenuring) { size_t max_workers = ShenandoahHeap::heap()->max_workers(); _local_age_table = NEW_C_HEAP_ARRAY(AgeTable*, max_workers, mtGC); CENSUS_NOISE(_local_noise = NEW_C_HEAP_ARRAY(ShenandoahNoiseStats, max_workers, mtGC);) @@ -127,7 +127,7 @@ void ShenandoahAgeCensus::update_census(size_t age0_pop, AgeTable* pv1, AgeTable prepare_for_census_update(); assert(_global_age_table[_epoch]->is_clear(), "Dirty decks"); CENSUS_NOISE(assert(_global_noise[_epoch].is_clear(), "Dirty decks");) - if (ShenandoahGenerationalAdaptiveTenuring && !ShenandoahGenerationalCensusAtEvac) { + if (ShenandoahGenerationalAdaptiveTenuring) { assert(pv1 == nullptr && pv2 == nullptr, "Error, check caller"); // Seed cohort 0 with population that may have been missed during // regular census. @@ -173,7 +173,7 @@ void ShenandoahAgeCensus::reset_global() { // Reset the local age tables, clearing any partial census. void ShenandoahAgeCensus::reset_local() { - if (!ShenandoahGenerationalAdaptiveTenuring || ShenandoahGenerationalCensusAtEvac) { + if (!ShenandoahGenerationalAdaptiveTenuring) { assert(_local_age_table == nullptr, "Error"); return; } @@ -200,7 +200,7 @@ bool ShenandoahAgeCensus::is_clear_global() { // Is local census information clear? bool ShenandoahAgeCensus::is_clear_local() { - if (!ShenandoahGenerationalAdaptiveTenuring || ShenandoahGenerationalCensusAtEvac) { + if (!ShenandoahGenerationalAdaptiveTenuring) { assert(_local_age_table == nullptr, "Error"); return true; } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp index 89c68f7120bb7..056f492073a0b 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp @@ -167,9 +167,9 @@ class ShenandoahAgeCensus: public CHeapObj { ShenandoahAgeCensus(); // Return the local age table (population vector) for worker_id. - // Only used in the case of (ShenandoahGenerationalAdaptiveTenuring && !ShenandoahGenerationalCensusAtEvac) - AgeTable* get_local_age_table(uint worker_id) { - return (AgeTable*) _local_age_table[worker_id]; + // Only used in the case of ShenandoahGenerationalAdaptiveTenuring + AgeTable* get_local_age_table(uint worker_id) const { + return _local_age_table[worker_id]; } // Update the local age table for worker_id by size for @@ -189,9 +189,8 @@ class ShenandoahAgeCensus: public CHeapObj { #endif // SHENANDOAH_CENSUS_NOISE // Update the census data, and compute the new tenuring threshold. - // This method should be called at the end of each marking (or optionally - // evacuation) cycle to update the tenuring threshold to be used in - // the next cycle. + // This method should be called at the end of each marking cycle to update + // the tenuring threshold to be used in the next cycle. // age0_pop is the population of Cohort 0 that may have been missed in // the regular census during the marking cycle, corresponding to objects // allocated when the concurrent marking was in progress. @@ -219,7 +218,7 @@ class ShenandoahAgeCensus: public CHeapObj { // Return the net size of objects encountered (counted or skipped) in census // at most recent epoch. - size_t get_total() { return _total; } + size_t get_total() const { return _total; } #endif // !PRODUCT // Print the age census information diff --git a/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.cpp b/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.cpp index 499e1342083ed..7883e2c5b2949 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.cpp @@ -45,7 +45,7 @@ ShenandoahEvacuationStats::ShenandoahEvacuations* ShenandoahEvacuationStats::get } ShenandoahEvacuationStats::ShenandoahEvacuationStats() - : _use_age_table(ShenandoahGenerationalCensusAtEvac || !ShenandoahGenerationalAdaptiveTenuring), + : _use_age_table(!ShenandoahGenerationalAdaptiveTenuring), _age_table(nullptr) { if (_use_age_table) { _age_table = new AgeTable(false); @@ -168,7 +168,7 @@ ShenandoahCycleStats ShenandoahEvacuationTracker::flush_cycle_to_global() { _mutators_global.accumulate(&mutators); _workers_global.accumulate(&workers); - if (ShenandoahGenerationalCensusAtEvac || !ShenandoahGenerationalAdaptiveTenuring) { + if (!ShenandoahGenerationalAdaptiveTenuring) { // Ingest mutator & worker collected population vectors into the heap's // global census data, and use it to compute an appropriate tenuring threshold // for use in the next cycle. diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp index d4db285453634..fd4a868d3dfb7 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp @@ -683,7 +683,7 @@ void ShenandoahGeneration::prepare_regions_and_collection_set(bool concurrent) { } // Tally the census counts and compute the adaptive tenuring threshold - if (is_generational && ShenandoahGenerationalAdaptiveTenuring && !ShenandoahGenerationalCensusAtEvac) { + if (is_generational && ShenandoahGenerationalAdaptiveTenuring) { // Objects above TAMS weren't included in the age census. Since they were all // allocated in this cycle they belong in the age 0 cohort. We walk over all // young regions and sum the volume of objects between TAMS and top. diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp index 3f44fe6f26acb..f1d5b0720fb4f 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp @@ -367,7 +367,7 @@ oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, Shena assert(target_gen == YOUNG_GENERATION, "Error"); // We record this census only when simulating pre-adaptive tenuring behavior, or // when we have been asked to record the census at evacuation rather than at mark - if (ShenandoahGenerationalCensusAtEvac || !ShenandoahGenerationalAdaptiveTenuring) { + if (!ShenandoahGenerationalAdaptiveTenuring) { evac_tracker()->record_age(thread, size * HeapWordSize, ShenandoahHeap::get_object_age(copy_val)); } } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp index 2dc0813e51354..0a95ee9f14966 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp @@ -118,7 +118,7 @@ inline void ShenandoahMark::count_liveness(ShenandoahLiveData* live_data, oop ob // Age census for objects in the young generation if (GENERATION == YOUNG || (GENERATION == GLOBAL && region->is_young())) { assert(heap->mode()->is_generational(), "Only if generational"); - if (ShenandoahGenerationalAdaptiveTenuring && !ShenandoahGenerationalCensusAtEvac) { + if (ShenandoahGenerationalAdaptiveTenuring) { assert(region->is_young(), "Only for young objects"); uint age = ShenandoahHeap::get_object_age(obj); ShenandoahAgeCensus* const census = ShenandoahGenerationalHeap::heap()->age_census(); diff --git a/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp b/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp index 50c2733479916..67dce54226179 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp @@ -70,10 +70,6 @@ "many consecutive young-gen collections have been " \ "completed following the preceding old-gen collection.") \ \ - product(bool, ShenandoahGenerationalCensusAtEvac, false, EXPERIMENTAL, \ - "(Generational mode only) Object age census at evacuation, " \ - "rather than during marking.") \ - \ product(bool, ShenandoahGenerationalAdaptiveTenuring, true, EXPERIMENTAL, \ "(Generational mode only) Dynamically adapt tenuring age.") \ \ From 090ae877a48f8ed3493902b07f760e446e15ba39 Mon Sep 17 00:00:00 2001 From: William Kemper Date: Fri, 15 Aug 2025 14:30:32 -0700 Subject: [PATCH 12/47] Little clean up as I read --- .../share/gc/shenandoah/shenandoahGeneration.cpp | 9 +++++---- .../share/gc/shenandoah/shenandoahGeneration.hpp | 2 +- .../gc/shenandoah/shenandoahOldGeneration.hpp | 6 +++--- .../share/gc/shenandoah/shenandoah_globals.hpp | 16 ++++++++-------- 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp index fd4a868d3dfb7..0f5facbaea05d 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp @@ -528,7 +528,7 @@ inline void assert_no_in_place_promotions() { // that this allows us to more accurately budget memory to hold the results of evacuation. Memory for evacuation // of aged regions must be reserved in the old generation. Memory for evacuation of all other regions must be // reserved in the young generation. -size_t ShenandoahGeneration::select_aged_regions(size_t old_available) { +size_t ShenandoahGeneration::select_aged_regions(const size_t old_promotion_reserve) { // There should be no regions configured for subsequent in-place-promotions carried over from the previous cycle. assert_no_in_place_promotions(); @@ -540,7 +540,6 @@ size_t ShenandoahGeneration::select_aged_regions(size_t old_available) { const uint tenuring_threshold = heap->age_census()->tenuring_threshold(); const size_t old_garbage_threshold = (ShenandoahHeapRegion::region_size_bytes() * ShenandoahOldGarbageThreshold) / 100; - size_t old_consumed = 0; size_t promo_potential = 0; size_t candidates = 0; @@ -625,8 +624,10 @@ size_t ShenandoahGeneration::select_aged_regions(size_t old_available) { // Note that we keep going even if one region is excluded from selection. // Subsequent regions may be selected if they have smaller live data. } + // Sort in increasing order according to live data bytes. Note that candidates represents the number of regions // that qualify to be promoted by evacuation. + size_t old_consumed = 0; if (candidates > 0) { size_t selected_regions = 0; size_t selected_live = 0; @@ -635,7 +636,7 @@ size_t ShenandoahGeneration::select_aged_regions(size_t old_available) { ShenandoahHeapRegion* const region = sorted_regions[i]._region; size_t region_live_data = sorted_regions[i]._live_data; size_t promotion_need = (size_t) (region_live_data * ShenandoahPromoEvacWaste); - if (old_consumed + promotion_need <= old_available) { + if (old_consumed + promotion_need <= old_promotion_reserve) { old_consumed += promotion_need; candidate_regions_for_promotion_by_copy[region->index()] = true; selected_regions++; @@ -651,7 +652,7 @@ size_t ShenandoahGeneration::select_aged_regions(size_t old_available) { } log_debug(gc)("Preselected %zu regions containing %zu live bytes," " consuming: %zu of budgeted: %zu", - selected_regions, selected_live, old_consumed, old_available); + selected_regions, selected_live, old_consumed, old_promotion_reserve); } heap->old_generation()->set_pad_for_promote_in_place(promote_in_place_pad); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.hpp b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.hpp index 2b7aca342dad1..5edbe0d8a2ef5 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.hpp @@ -97,7 +97,7 @@ class ShenandoahGeneration : public CHeapObj, public ShenandoahSpaceInfo { // regions, which are marked in the preselected_regions() indicator // array of the heap's collection set, which should be initialized // to false. - size_t select_aged_regions(size_t old_available); + size_t select_aged_regions(size_t old_promotion_reserve); size_t available(size_t capacity) const; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.hpp b/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.hpp index abc865c31cd1e..a9be1c8b34be0 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.hpp @@ -65,14 +65,14 @@ class ShenandoahOldGeneration : public ShenandoahGeneration { // remaining in a PLAB when it is retired. size_t _promoted_expended; - // Represents the quantity of live bytes we expect to promote in place during the next - // evacuation cycle. This value is used by the young heuristic to trigger mixed collections. + // Represents the quantity of live bytes we expect to promote during the next evacuation + // cycle. This value is used by the young heuristic to trigger mixed collections. // It is also used when computing the optimum size for the old generation. size_t _promotion_potential; // When a region is selected to be promoted in place, the remaining free memory is filled // in to prevent additional allocations (preventing premature promotion of newly allocated - // objects. This field records the total amount of padding used for such regions. + // objects). This field records the total amount of padding used for such regions. size_t _pad_for_promote_in_place; // During construction of the collection set, we keep track of regions that are eligible diff --git a/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp b/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp index 67dce54226179..dfdcffae0e45e 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp @@ -371,13 +371,13 @@ \ product(uintx, ShenandoahOldEvacRatioPercent, 75, EXPERIMENTAL, \ "The maximum proportion of evacuation from old-gen memory, " \ - "expressed as a percentage. The default value 75 denotes that no" \ - "more than 75% of the collection set evacuation workload may be " \ - "towards evacuation of old-gen heap regions. This limits both the"\ - "promotion of aged regions and the compaction of existing old " \ - "regions. A value of 75 denotes that the total evacuation work" \ - "may increase to up to four times the young gen evacuation work." \ - "A larger value allows quicker promotion and allows" \ + "expressed as a percentage. The default value 75 denotes that " \ + "no more than 75% of the collection set evacuation workload may " \ + "be towards evacuation of old-gen heap regions. This limits both "\ + "the promotion of aged regions and the compaction of existing " \ + "old regions. A value of 75 denotes that the total evacuation " \ + "work may increase to up to four times the young gen evacuation " \ + "work. A larger value allows quicker promotion and allows " \ "a smaller number of mixed evacuations to process " \ "the entire list of old-gen collection candidates at the cost " \ "of an increased disruption of the normal cadence of young-gen " \ @@ -385,7 +385,7 @@ "focus entirely on old-gen memory, allowing no young-gen " \ "regions to be collected, likely resulting in subsequent " \ "allocation failures because the allocation pool is not " \ - "replenished. A value of 0 allows a mixed evacuation to" \ + "replenished. A value of 0 allows a mixed evacuation to " \ "focus entirely on young-gen memory, allowing no old-gen " \ "regions to be collected, likely resulting in subsequent " \ "promotion failures and triggering of stop-the-world full GC " \ From 071bc8883ddd9e241b6e367413c22a9a94e2a1df Mon Sep 17 00:00:00 2001 From: William Kemper Date: Fri, 15 Aug 2025 16:54:14 -0700 Subject: [PATCH 13/47] Deduplicate some collection set logging --- .../shenandoahGenerationalHeuristics.cpp | 71 +++++-------------- .../heuristics/shenandoahHeuristics.cpp | 22 +----- .../gc/shenandoah/shenandoahCollectionSet.cpp | 35 +++++++++ .../gc/shenandoah/shenandoahCollectionSet.hpp | 9 +-- .../shenandoahCollectionSet.inline.hpp | 8 +-- 5 files changed, 63 insertions(+), 82 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.cpp index 08fd45993462b..bf0087aabe1b6 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.cpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.cpp @@ -187,59 +187,24 @@ void ShenandoahGenerationalHeuristics::choose_collection_set(ShenandoahCollectio heap->shenandoah_policy()->record_mixed_cycle(); } - size_t cset_percent = (total_garbage == 0) ? 0 : (collection_set->garbage() * 100 / total_garbage); - size_t collectable_garbage = collection_set->garbage() + immediate_garbage; - size_t collectable_garbage_percent = (total_garbage == 0) ? 0 : (collectable_garbage * 100 / total_garbage); - - log_info(gc, ergo)("Collectable Garbage: %zu%s (%zu%%), " - "Immediate: %zu%s (%zu%%), %zu regions, " - "CSet: %zu%s (%zu%%), %zu regions", - - byte_size_in_proper_unit(collectable_garbage), - proper_unit_for_byte_size(collectable_garbage), - collectable_garbage_percent, - - byte_size_in_proper_unit(immediate_garbage), - proper_unit_for_byte_size(immediate_garbage), - immediate_percent, - immediate_regions, - - byte_size_in_proper_unit(collection_set->garbage()), - proper_unit_for_byte_size(collection_set->garbage()), - cset_percent, - collection_set->count()); - - if (collection_set->garbage() > 0) { - size_t young_evac_bytes = collection_set->get_young_bytes_reserved_for_evacuation(); - size_t promote_evac_bytes = collection_set->get_young_bytes_to_be_promoted(); - size_t old_evac_bytes = collection_set->get_old_bytes_reserved_for_evacuation(); - size_t total_evac_bytes = young_evac_bytes + promote_evac_bytes + old_evac_bytes; - log_info(gc, ergo)("Evacuation Targets: YOUNG: %zu%s, " - "PROMOTE: %zu%s, " - "OLD: %zu%s, " - "TOTAL: %zu%s", - byte_size_in_proper_unit(young_evac_bytes), proper_unit_for_byte_size(young_evac_bytes), - byte_size_in_proper_unit(promote_evac_bytes), proper_unit_for_byte_size(promote_evac_bytes), - byte_size_in_proper_unit(old_evac_bytes), proper_unit_for_byte_size(old_evac_bytes), - byte_size_in_proper_unit(total_evac_bytes), proper_unit_for_byte_size(total_evac_bytes)); - - ShenandoahEvacuationInformation evacInfo; - evacInfo.set_collection_set_regions(collection_set->count()); - evacInfo.set_collection_set_used_before(collection_set->used()); - evacInfo.set_collection_set_used_after(collection_set->live()); - evacInfo.set_collected_old(old_evac_bytes); - evacInfo.set_collected_promoted(promote_evac_bytes); - evacInfo.set_collected_young(young_evac_bytes); - evacInfo.set_regions_promoted_humongous(humongous_regions_promoted); - evacInfo.set_regions_promoted_regular(regular_regions_promoted_in_place); - evacInfo.set_regular_promoted_garbage(regular_regions_promoted_garbage); - evacInfo.set_regular_promoted_free(regular_regions_promoted_free); - evacInfo.set_regions_immediate(immediate_regions); - evacInfo.set_immediate_size(immediate_garbage); - evacInfo.set_free_regions(free_regions); - - ShenandoahTracer().report_evacuation_info(&evacInfo); - } + collection_set->summarize(total_garbage, immediate_garbage, immediate_regions); + + ShenandoahEvacuationInformation evacInfo; + evacInfo.set_collection_set_regions(collection_set->count()); + evacInfo.set_collection_set_used_before(collection_set->used()); + evacInfo.set_collection_set_used_after(collection_set->live()); + evacInfo.set_collected_old(collection_set->get_old_bytes_reserved_for_evacuation()); + evacInfo.set_collected_promoted(collection_set->get_young_bytes_to_be_promoted()); + evacInfo.set_collected_young(collection_set->get_young_bytes_reserved_for_evacuation()); + evacInfo.set_regions_promoted_humongous(humongous_regions_promoted); + evacInfo.set_regions_promoted_regular(regular_regions_promoted_in_place); + evacInfo.set_regular_promoted_garbage(regular_regions_promoted_garbage); + evacInfo.set_regular_promoted_free(regular_regions_promoted_free); + evacInfo.set_regions_immediate(immediate_regions); + evacInfo.set_immediate_size(immediate_garbage); + evacInfo.set_free_regions(free_regions); + + ShenandoahTracer().report_evacuation_info(&evacInfo); } diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.cpp index b151a75e6e7e5..c8a0c3dc51837 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.cpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.cpp @@ -153,27 +153,7 @@ void ShenandoahHeuristics::choose_collection_set(ShenandoahCollectionSet* collec choose_collection_set_from_regiondata(collection_set, candidates, cand_idx, immediate_garbage + free); } - size_t cset_percent = (total_garbage == 0) ? 0 : (collection_set->garbage() * 100 / total_garbage); - size_t collectable_garbage = collection_set->garbage() + immediate_garbage; - size_t collectable_garbage_percent = (total_garbage == 0) ? 0 : (collectable_garbage * 100 / total_garbage); - - log_info(gc, ergo)("Collectable Garbage: %zu%s (%zu%%), " - "Immediate: %zu%s (%zu%%), %zu regions, " - "CSet: %zu%s (%zu%%), %zu regions", - - byte_size_in_proper_unit(collectable_garbage), - proper_unit_for_byte_size(collectable_garbage), - collectable_garbage_percent, - - byte_size_in_proper_unit(immediate_garbage), - proper_unit_for_byte_size(immediate_garbage), - immediate_percent, - immediate_regions, - - byte_size_in_proper_unit(collection_set->garbage()), - proper_unit_for_byte_size(collection_set->garbage()), - cset_percent, - collection_set->count()); + collection_set->summarize(total_garbage, immediate_garbage, immediate_regions); } void ShenandoahHeuristics::record_cycle_start() { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.cpp b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.cpp index 25b900f8d7772..c7f02ab7295c4 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.cpp @@ -199,3 +199,38 @@ void ShenandoahCollectionSet::print_on(outputStream* out) const { } assert(regions == count(), "Must match"); } + +void ShenandoahCollectionSet::summarize(size_t total_garbage, size_t immediate_garbage, size_t immediate_regions) const { + const LogTarget(Info, gc, ergo) lt; + LogStream ls(lt); + if (lt.is_enabled()) { + const size_t cset_percent = (total_garbage == 0) ? 0 : (garbage() * 100 / total_garbage); + const size_t collectable_garbage = garbage() + immediate_garbage; + const size_t collectable_garbage_percent = (total_garbage == 0) ? 0 : (collectable_garbage * 100 / total_garbage); + const size_t immediate_percent = (total_garbage == 0) ? 0 : (immediate_garbage * 100 / total_garbage); + + ls.print_cr("Collectable Garbage: " PROPERFMT " (%zu%%), " + "Immediate: " PROPERFMT " (%zu%%), %zu regions, " + "CSet: " PROPERFMT " (%zu%%), %zu regions", + PROPERFMTARGS(collectable_garbage), + collectable_garbage_percent, + + PROPERFMTARGS(immediate_garbage), + immediate_percent, + immediate_regions, + + PROPERFMTARGS(garbage()), + cset_percent, + count()); + + if (garbage() > 0) { + const size_t young_evac_bytes = get_young_bytes_reserved_for_evacuation(); + const size_t promote_evac_bytes = get_young_bytes_to_be_promoted(); + const size_t old_evac_bytes = get_old_bytes_reserved_for_evacuation(); + const size_t total_evac_bytes = young_evac_bytes + promote_evac_bytes + old_evac_bytes; + ls.print_cr("Evacuation Targets: " + "YOUNG: " PROPERFMT ", " "PROMOTE: " PROPERFMT ", " "OLD: " PROPERFMT ", " "TOTAL: " PROPERFMT, + PROPERFMTARGS(young_evac_bytes), PROPERFMTARGS(promote_evac_bytes), PROPERFMTARGS(old_evac_bytes), PROPERFMTARGS(total_evac_bytes)); + } + } +} diff --git a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.hpp b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.hpp index 4f9f6fc20522f..df9b6ed29d1d6 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.hpp @@ -104,16 +104,17 @@ class ShenandoahCollectionSet : public CHeapObj { inline bool is_in_loc(void* loc) const; void print_on(outputStream* out) const; + void summarize(size_t total_garbage, size_t immediate_garbage, size_t immediate_regions) const; // It is not known how many of these bytes will be promoted. - inline size_t get_young_bytes_reserved_for_evacuation(); - inline size_t get_old_bytes_reserved_for_evacuation(); + inline size_t get_young_bytes_reserved_for_evacuation() const; + inline size_t get_old_bytes_reserved_for_evacuation() const; - inline size_t get_young_bytes_to_be_promoted(); + inline size_t get_young_bytes_to_be_promoted() const; size_t get_young_available_bytes_collected() { return _young_available_bytes_collected; } - inline size_t get_old_garbage(); + inline size_t get_old_garbage() const; bool is_preselected(size_t region_idx) { assert(_preselected_regions != nullptr, "Missing etsablish after abandon"); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.inline.hpp index 791e9c73b28e3..4adcec4fbb552 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.inline.hpp @@ -54,19 +54,19 @@ bool ShenandoahCollectionSet::is_in_loc(void* p) const { return _biased_cset_map[index] == 1; } -size_t ShenandoahCollectionSet::get_old_bytes_reserved_for_evacuation() { +size_t ShenandoahCollectionSet::get_old_bytes_reserved_for_evacuation() const { return _old_bytes_to_evacuate; } -size_t ShenandoahCollectionSet::get_young_bytes_reserved_for_evacuation() { +size_t ShenandoahCollectionSet::get_young_bytes_reserved_for_evacuation() const { return _young_bytes_to_evacuate - _young_bytes_to_promote; } -size_t ShenandoahCollectionSet::get_young_bytes_to_be_promoted() { +size_t ShenandoahCollectionSet::get_young_bytes_to_be_promoted() const { return _young_bytes_to_promote; } -size_t ShenandoahCollectionSet::get_old_garbage() { +size_t ShenandoahCollectionSet::get_old_garbage() const { return _old_garbage; } From 7c2de249c995b1aa48e934d6ae610a97311e9742 Mon Sep 17 00:00:00 2001 From: William Kemper Date: Mon, 18 Aug 2025 15:07:40 -0700 Subject: [PATCH 14/47] Idle clean ups, logging improvements --- .../heuristics/shenandoahOldHeuristics.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.cpp index 2d0bbfd5e4a3c..bafd8fc31c83c 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.cpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.cpp @@ -102,13 +102,11 @@ bool ShenandoahOldHeuristics::prime_collection_set(ShenandoahCollectionSet* coll const size_t old_evacuation_reserve = _old_generation->get_evacuation_reserve(); const size_t old_evacuation_budget = (size_t) ((double) old_evacuation_reserve / ShenandoahOldEvacWaste); size_t unfragmented_available = _old_generation->free_unaffiliated_regions() * ShenandoahHeapRegion::region_size_bytes(); - size_t fragmented_available; - size_t excess_fragmented_available; + size_t fragmented_available = 0; + size_t excess_fragmented_available = 0; if (unfragmented_available > old_evacuation_budget) { unfragmented_available = old_evacuation_budget; - fragmented_available = 0; - excess_fragmented_available = 0; } else { assert(_old_generation->available() >= old_evacuation_budget, "Cannot budget more than is available"); fragmented_available = _old_generation->available() - unfragmented_available; @@ -141,7 +139,7 @@ bool ShenandoahOldHeuristics::prime_collection_set(ShenandoahCollectionSet* coll // If region r is evacuated to fragmented memory (to free memory within a partially used region), then we need // to decrease the capacity of the fragmented memory by the scaled loss. - size_t live_data_for_evacuation = r->get_live_data_bytes(); + const size_t live_data_for_evacuation = r->get_live_data_bytes(); size_t lost_available = r->free(); if ((lost_available > 0) && (excess_fragmented_available > 0)) { @@ -169,7 +167,9 @@ bool ShenandoahOldHeuristics::prime_collection_set(ShenandoahCollectionSet* coll // We were not able to account for the lost free memory within fragmented memory, so we need to take this // allocation out of unfragmented memory. Unfragmented memory does not need to account for loss of free. if (live_data_for_evacuation > unfragmented_available) { - // There is not room to evacuate this region or any that come after it in within the candidates array. + // There is no room to evacuate this region or any that come after it in within the candidates array. + log_info(gc, ergo)("Not enough unfragmented memory (%zu) to hold evacuees (%zu) from region: (%zu)", + unfragmented_available, live_data_for_evacuation, r->index()); break; } else { unfragmented_available -= live_data_for_evacuation; @@ -187,7 +187,9 @@ bool ShenandoahOldHeuristics::prime_collection_set(ShenandoahCollectionSet* coll evacuation_need = 0; } if (evacuation_need > unfragmented_available) { - // There is not room to evacuate this region or any that come after it in within the candidates array. + // There is no room to evacuate this region or any that come after it in within the candidates array. + log_info(gc, ergo)("Not enough memory (%zu) to hold evacuees (%zu) from region: (%zu)", + unfragmented_available, live_data_for_evacuation, r->index()); break; } else { unfragmented_available -= evacuation_need; From 8d7cd393e131c7728e5fb636b8442b8c8776b49c Mon Sep 17 00:00:00 2001 From: William Kemper Date: Mon, 18 Aug 2025 17:35:09 -0700 Subject: [PATCH 15/47] Update unit test, fix slowdebug build issue --- .../gc/shenandoah/shenandoahAgeCensus.cpp | 4 +- .../shenandoah/test_shenandoahAgeCensus.cpp | 47 +++++++++++++------ 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp index a7eeb6867e645..ffec1b86c981b 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp @@ -156,7 +156,7 @@ void ShenandoahAgeCensus::update_census(size_t age0_pop, AgeTable* pv1, AgeTable assert(pv1 == nullptr && pv2 == nullptr, "Error, check caller"); // Seed cohort 0 with population that may have been missed during // regular census. - _global_age_table[_epoch]->add(0, age0_pop); + _global_age_table[_epoch]->add(0u, age0_pop); // Merge data from local age tables into the global age table for the epoch, // clearing the local tables. @@ -318,7 +318,7 @@ uint ShenandoahAgeCensus::compute_tenuring_threshold() { const size_t prev_pop = prev_pv->sizes[i-1]; const double mr = mortality_rate(prev_pop, cur_pop); if (prev_pop > ShenandoahGenerationalTenuringCohortPopulationThreshold && - mr > ShenandoahGenerationalTenuringMortalityRateThreshold) { + mr > 0 && ((mr - ShenandoahGenerationalTenuringMortalityRateThreshold) > 0.001)) { // This is the oldest cohort that has high mortality. // We ignore any cohorts that had a very low population count, or // that have a lower mortality rate than we care to age in young; these diff --git a/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp b/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp index c82cdd7fa27ed..d18ceef95ba5c 100644 --- a/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp +++ b/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp @@ -29,22 +29,17 @@ class ShenandoahAgeCensusTest : public ::testing::Test { protected: static constexpr size_t MinimumPopulationSize = 4*K; static void build_mortality_rate_curve(ShenandoahAgeCensus& census, const double mortality_rates[], const size_t cohorts) { - constexpr size_t current_population = MinimumPopulationSize * 10; + constexpr size_t initial_population = MinimumPopulationSize * 1000; // Simulate the census for the first epoch with populations that will produce the // expected mortality rate when presented with the populations for the subsequent epoch - for (size_t i = 1; i <= cohorts; i++) { - const size_t previous_population = current_population / (1.0 - mortality_rates[i]); - census.add(i, 0, 0, previous_population, 0); + size_t population = initial_population; + for (size_t i = 0; i < cohorts; i++) { + population = population * (1.0 - mortality_rates[i]); + census.add(i + 1, 0, 0, population, 0); } - const size_t previous_population = current_population / (1.0 - mortality_rates[0]); - census.update_census(previous_population); - - for (size_t i = 1; i <= cohorts; i++) { - census.add(i, 0, 0, current_population, 0); - } - census.update_census(current_population); + census.update_census(initial_population); } }; @@ -62,12 +57,34 @@ TEST_F(ShenandoahAgeCensusTest, ignore_small_populations) { EXPECT_EQ(1u, census.tenuring_threshold()); } -TEST_VM_F(ShenandoahAgeCensusTest, find_high_mortality_rate) { +TEST_F(ShenandoahAgeCensusTest, find_high_mortality_rate) { ShenandoahAgeCensus census(4); constexpr double mortality_rates[] = { - 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, - 0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 + 0.9, 0.7, 0.5, 0.3, 0.1, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }; - build_mortality_rate_curve(census, mortality_rates, sizeof(mortality_rates) / sizeof(double)); + size_t mortality_rates_count = sizeof(mortality_rates) / sizeof(double); + + // Initial threshold, no data + EXPECT_EQ(16u, census.tenuring_threshold()); + + // No deaths in previous data, everybody seems to survive, set threshold to 1 (tenure everything). + build_mortality_rate_curve(census, mortality_rates, mortality_rates_count); + EXPECT_EQ(1u, census.tenuring_threshold()); + + // mr = 0.7 from 1 -> 2, above mr threshold of 0.1 + build_mortality_rate_curve(census, mortality_rates, mortality_rates_count); EXPECT_EQ(2u, census.tenuring_threshold()); + + // mr = 0.5 from 2 -> 3, above mr threshold of 0.1 + build_mortality_rate_curve(census, mortality_rates, mortality_rates_count); + EXPECT_EQ(3u, census.tenuring_threshold()); + + // mr = 0.3 from 3 -> 4, above mr threshold of 0.1 + build_mortality_rate_curve(census, mortality_rates, mortality_rates_count); + EXPECT_EQ(4u, census.tenuring_threshold()); + + // mr = 0.1 from 4 -> 5, not above mr threshold of 0.1, stay at 4 + build_mortality_rate_curve(census, mortality_rates, mortality_rates_count); + EXPECT_EQ(4u, census.tenuring_threshold()); } From b13e8b1c121f50b3ef365d10a5e9b8327df9721f Mon Sep 17 00:00:00 2001 From: William Kemper Date: Tue, 19 Aug 2025 11:22:37 -0700 Subject: [PATCH 16/47] Remove outdated comment --- test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp b/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp index d18ceef95ba5c..2f5e27c04619b 100644 --- a/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp +++ b/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp @@ -31,8 +31,6 @@ class ShenandoahAgeCensusTest : public ::testing::Test { static void build_mortality_rate_curve(ShenandoahAgeCensus& census, const double mortality_rates[], const size_t cohorts) { constexpr size_t initial_population = MinimumPopulationSize * 1000; - // Simulate the census for the first epoch with populations that will produce the - // expected mortality rate when presented with the populations for the subsequent epoch size_t population = initial_population; for (size_t i = 0; i < cohorts; i++) { population = population * (1.0 - mortality_rates[i]); From 86c429a28c432b6ebb1c38f88452c5984226b56e Mon Sep 17 00:00:00 2001 From: William Kemper Date: Tue, 19 Aug 2025 11:52:16 -0700 Subject: [PATCH 17/47] Add more census updates, exhibit current behavior in test --- .../gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp b/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp index 2f5e27c04619b..2b27a22b58a40 100644 --- a/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp +++ b/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp @@ -82,7 +82,13 @@ TEST_F(ShenandoahAgeCensusTest, find_high_mortality_rate) { build_mortality_rate_curve(census, mortality_rates, mortality_rates_count); EXPECT_EQ(4u, census.tenuring_threshold()); - // mr = 0.1 from 4 -> 5, not above mr threshold of 0.1, stay at 4 + // mr = 0.1 from 4 -> 5, not above mr threshold of 0.1, stay at 4? build_mortality_rate_curve(census, mortality_rates, mortality_rates_count); - EXPECT_EQ(4u, census.tenuring_threshold()); + EXPECT_EQ(5u, census.tenuring_threshold()); + + build_mortality_rate_curve(census, mortality_rates, mortality_rates_count); + EXPECT_EQ(5u, census.tenuring_threshold()); + + build_mortality_rate_curve(census, mortality_rates, mortality_rates_count); + EXPECT_EQ(5u, census.tenuring_threshold()); } From 704753f526d9a2c715f28355436cbcb7c62cd272 Mon Sep 17 00:00:00 2001 From: William Kemper Date: Tue, 19 Aug 2025 13:49:06 -0700 Subject: [PATCH 18/47] Idle clean ups --- .../gc/shenandoah/shenandoahGeneration.cpp | 34 +++++++++---------- .../share/gc/shenandoah/shenandoahOldGC.cpp | 9 ++--- .../gc/shenandoah/shenandoahOldGeneration.hpp | 1 + 3 files changed, 20 insertions(+), 24 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp index 0f5facbaea05d..ab0a65a9fef7b 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp @@ -368,10 +368,10 @@ void ShenandoahGeneration::compute_evacuation_budgets(ShenandoahHeap* const heap // void ShenandoahGeneration::adjust_evacuation_budgets(ShenandoahHeap* const heap, ShenandoahCollectionSet* const collection_set) { shenandoah_assert_generational(); - // We may find that old_evacuation_reserve and/or loaned_for_young_evacuation are not fully consumed, in which case we may - // be able to increase regions_available_to_loan + // We may find that old_evacuation_reserve is not fully consumed, in which case we may be able to transfer old + // unaffiliated regions back to young. - // The role of adjust_evacuation_budgets() is to compute the correct value of regions_available_to_loan and to make + // The role of adjust_evacuation_budgets() is to compute the correct value of regions to transfer to young and to make // effective use of this memory, including the remnant memory within these regions that may result from rounding loan to // integral number of regions. Excess memory that is available to be loaned is applied to an allocation supplement, // which allows mutators to allocate memory beyond the current capacity of young-gen on the promise that the loan @@ -383,11 +383,11 @@ void ShenandoahGeneration::adjust_evacuation_budgets(ShenandoahHeap* const heap, // available that results from a decrease in memory consumed by old evacuation is not necessarily available to be loaned // to young-gen. - size_t region_size_bytes = ShenandoahHeapRegion::region_size_bytes(); + const size_t region_size_bytes = ShenandoahHeapRegion::region_size_bytes(); ShenandoahOldGeneration* const old_generation = heap->old_generation(); ShenandoahYoungGeneration* const young_generation = heap->young_generation(); - size_t old_evacuated = collection_set->get_old_bytes_reserved_for_evacuation(); + const size_t old_evacuated = collection_set->get_old_bytes_reserved_for_evacuation(); size_t old_evacuated_committed = (size_t) (ShenandoahOldEvacWaste * double(old_evacuated)); size_t old_evacuation_reserve = old_generation->get_evacuation_reserve(); @@ -404,17 +404,17 @@ void ShenandoahGeneration::adjust_evacuation_budgets(ShenandoahHeap* const heap, old_generation->set_evacuation_reserve(old_evacuation_reserve); } - size_t young_advance_promoted = collection_set->get_young_bytes_to_be_promoted(); + const size_t young_advance_promoted = collection_set->get_young_bytes_to_be_promoted(); size_t young_advance_promoted_reserve_used = (size_t) (ShenandoahPromoEvacWaste * double(young_advance_promoted)); - size_t young_evacuated = collection_set->get_young_bytes_reserved_for_evacuation(); - size_t young_evacuated_reserve_used = (size_t) (ShenandoahEvacWaste * double(young_evacuated)); + const size_t young_evacuated = collection_set->get_young_bytes_reserved_for_evacuation(); + const size_t young_evacuated_reserve_used = (size_t) (ShenandoahEvacWaste * double(young_evacuated)); - size_t total_young_available = young_generation->available_with_reserve(); + const size_t total_young_available = young_generation->available_with_reserve(); assert(young_evacuated_reserve_used <= total_young_available, "Cannot evacuate more than is available in young"); young_generation->set_evacuation_reserve(young_evacuated_reserve_used); - size_t old_available = old_generation->available(); + const size_t old_available = old_generation->available(); // Now that we've established the collection set, we know how much memory is really required by old-gen for evacuation // and promotion reserves. Try shrinking OLD now in case that gives us a bit more runway for mutator allocations during // evac and update phases. @@ -431,18 +431,18 @@ void ShenandoahGeneration::adjust_evacuation_budgets(ShenandoahHeap* const heap, old_consumed = old_evacuated_committed + young_advance_promoted_reserve_used; } - assert(old_available >= old_consumed, "Cannot consume (%zu) more than is available (%zu)", - old_consumed, old_available); + assert(old_available >= old_consumed, "Cannot consume (%zu) more than is available (%zu)", old_consumed, old_available); + // TODO: We want to reserve more of old for unknown promotions (i.e., tenurable objects in untenurable regions) size_t excess_old = old_available - old_consumed; - size_t unaffiliated_old_regions = old_generation->free_unaffiliated_regions(); - size_t unaffiliated_old = unaffiliated_old_regions * region_size_bytes; + const size_t unaffiliated_old_regions = old_generation->free_unaffiliated_regions(); + const size_t unaffiliated_old = unaffiliated_old_regions * region_size_bytes; assert(old_available >= unaffiliated_old, "Unaffiliated old is a subset of old available"); // Make sure old_evac_committed is unaffiliated if (old_evacuated_committed > 0) { if (unaffiliated_old > old_evacuated_committed) { - size_t giveaway = unaffiliated_old - old_evacuated_committed; - size_t giveaway_regions = giveaway / region_size_bytes; // round down + const size_t giveaway = unaffiliated_old - old_evacuated_committed; + const size_t giveaway_regions = giveaway / region_size_bytes; // round down if (giveaway_regions > 0) { excess_old = MIN2(excess_old, giveaway_regions * region_size_bytes); } else { @@ -463,7 +463,7 @@ void ShenandoahGeneration::adjust_evacuation_budgets(ShenandoahHeap* const heap, } } else if (unaffiliated_old_regions > 0) { // excess_old < unaffiliated old: we can give back MIN(excess_old/region_size_bytes, unaffiliated_old_regions) - size_t excess_regions = excess_old / region_size_bytes; + const size_t excess_regions = excess_old / region_size_bytes; regions_to_xfer = MIN2(excess_regions, unaffiliated_old_regions); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahOldGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahOldGC.cpp index 1724fc2849f76..2662705d0e535 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahOldGC.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahOldGC.cpp @@ -69,12 +69,6 @@ void ShenandoahOldGC::op_final_mark() { heap->set_unload_classes(false); heap->prepare_concurrent_roots(); - // Believe verification following old-gen concurrent mark needs to be different than verification following - // young-gen concurrent mark, so am commenting this out for now: - // if (ShenandoahVerify) { - // heap->verifier()->verify_after_concmark(); - // } - if (VerifyAfterGC) { Universe::verify(); } @@ -146,7 +140,8 @@ bool ShenandoahOldGC::collect(GCCause::Cause cause) { // We do not rebuild_free following increments of old marking because memory has not been reclaimed. However, we may // need to transfer memory to OLD in order to efficiently support the mixed evacuations that might immediately follow. - size_t allocation_runway = heap->young_generation()->heuristics()->bytes_of_allocation_runway_before_gc_trigger(0); + log_info(gc, ergo)("Updating generation sizes at end of old mark, anticipated promotions: %zub", _old_generation->get_promotion_potential()); + const size_t allocation_runway = heap->young_generation()->heuristics()->bytes_of_allocation_runway_before_gc_trigger(0); heap->compute_old_generation_balance(allocation_runway, 0); ShenandoahGenerationalHeap::TransferResult result; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.hpp b/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.hpp index a9be1c8b34be0..49acdcc5aa04d 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.hpp @@ -137,6 +137,7 @@ class ShenandoahOldGeneration : public ShenandoahGeneration { // See description in field declaration void set_region_balance(ssize_t balance) { _region_balance = balance; } ssize_t get_region_balance() const { return _region_balance; } + // See description in field declaration void set_promotion_potential(size_t val) { _promotion_potential = val; }; size_t get_promotion_potential() const { return _promotion_potential; }; From a3e5fb4d50bf8916a155a724c939de8ed051d9f5 Mon Sep 17 00:00:00 2001 From: William Kemper Date: Tue, 19 Aug 2025 15:25:08 -0700 Subject: [PATCH 19/47] Add a method to get the total bytes occupied by objects eligible for tenure --- .../gc/shenandoah/shenandoahAgeCensus.cpp | 12 ++++ .../gc/shenandoah/shenandoahAgeCensus.hpp | 6 ++ .../shenandoah/test_shenandoahAgeCensus.cpp | 72 +++++++++++++------ 3 files changed, 68 insertions(+), 22 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp index a2d717e46b326..f5874ed71b172 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp @@ -237,6 +237,18 @@ bool ShenandoahAgeCensus::is_clear_local() { return true; } +size_t ShenandoahAgeCensus::get_tenurable_bytes(const uint tenuring_threshold) const { + assert(_epoch < MAX_SNAPSHOTS, "Out of bounds"); + size_t total = 0; + const AgeTable* pv = _global_age_table[_epoch]; + for (uint i = 0; i < MAX_COHORTS; i++) { + if (i>= tenuring_threshold) { + total += pv->sizes[i]; + } + } + return total; +} + size_t ShenandoahAgeCensus::get_all_ages(uint snap) { assert(snap < MAX_SNAPSHOTS, "Out of bounds"); size_t pop = 0; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp index 66aac3d49cc80..2859553b49b49 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp @@ -207,6 +207,12 @@ class ShenandoahAgeCensus: public CHeapObj { // Return the most recently computed tenuring threshold uint tenuring_threshold() const { return _tenuring_threshold[_epoch]; } + // Return the total size of the population above the given threshold for the current epoch + size_t get_tenurable_bytes(uint tenuring_threshold) const; + + // As above, but use the current tenuring threshold by default + size_t get_tenurable_bytes() const { return get_tenurable_bytes(tenuring_threshold()); } + // Reset the epoch, clearing accumulated census history // Note: this isn't currently used, but reserved for planned // future usage. diff --git a/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp b/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp index 2b27a22b58a40..7e964b608eb2b 100644 --- a/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp +++ b/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp @@ -28,27 +28,60 @@ class ShenandoahAgeCensusTest : public ::testing::Test { protected: static constexpr size_t MinimumPopulationSize = 4*K; - static void build_mortality_rate_curve(ShenandoahAgeCensus& census, const double mortality_rates[], const size_t cohorts) { - constexpr size_t initial_population = MinimumPopulationSize * 1000; + static constexpr size_t InitialPopulationSize = MinimumPopulationSize * 1000; - size_t population = initial_population; - for (size_t i = 0; i < cohorts; i++) { - population = population * (1.0 - mortality_rates[i]); - census.add(i + 1, 0, 0, population, 0); + size_t _cohorts_count = ShenandoahAgeCensus::MAX_COHORTS; + double _mortality_rates[ShenandoahAgeCensus::MAX_COHORTS]; + size_t _cohort_populations[ShenandoahAgeCensus::MAX_COHORTS]; + + ShenandoahAgeCensusTest() + : _mortality_rates{0.9, 0.7, 0.5, 0.3, 0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0} + { + get_cohort_populations(_mortality_rates, _cohort_populations, _cohorts_count); + } + + void update(ShenandoahAgeCensus& census) const { + for (size_t i = 1; i < _cohorts_count; i++) { + census.add(i, 0, 0, _cohort_populations[i], 0); + } + census.update_census(_cohort_populations[0]); + } + + size_t get_total_population_older_than(const size_t min_cohort_age) const { + size_t total = 0; + for (size_t i = 0; i < _cohorts_count; i++) { + if (i >= min_cohort_age) { + total += _cohort_populations[i]; + } } + return total; + } - census.update_census(initial_population); + static void get_cohort_populations(const double mortality_rates[], size_t cohort_populations[], const size_t cohorts) { + size_t population = InitialPopulationSize; + cohort_populations[0] = population; + for (size_t i = 1; i < cohorts; i++) { + population = population * (1.0 - mortality_rates[i - 1]); + cohort_populations[i] = population; + } } }; TEST_F(ShenandoahAgeCensusTest, initialize) { - ShenandoahAgeCensus census(4); + const ShenandoahAgeCensus census(1); EXPECT_EQ(census.tenuring_threshold(), ShenandoahAgeCensus::MAX_COHORTS); } +TEST_F(ShenandoahAgeCensusTest, get_tenurable_bytes) { + ShenandoahAgeCensus census(1); + update(census); + EXPECT_EQ(get_total_population_older_than(1), census.get_tenurable_bytes(1)); + EXPECT_LT(census.get_tenurable_bytes(2), census.get_tenurable_bytes(1)); +} + TEST_F(ShenandoahAgeCensusTest, ignore_small_populations) { // Small populations are ignored so we do not return early before reaching the youngest cohort. - ShenandoahAgeCensus census(4); + ShenandoahAgeCensus census(1); census.add(1, 0, 0, 32, 0); census.add(1, 0, 0, 32, 0); census.update_census(64); @@ -56,39 +89,34 @@ TEST_F(ShenandoahAgeCensusTest, ignore_small_populations) { } TEST_F(ShenandoahAgeCensusTest, find_high_mortality_rate) { - ShenandoahAgeCensus census(4); - constexpr double mortality_rates[] = { - 0.9, 0.7, 0.5, 0.3, 0.1, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 - }; - size_t mortality_rates_count = sizeof(mortality_rates) / sizeof(double); + ShenandoahAgeCensus census(1); // Initial threshold, no data EXPECT_EQ(16u, census.tenuring_threshold()); // No deaths in previous data, everybody seems to survive, set threshold to 1 (tenure everything). - build_mortality_rate_curve(census, mortality_rates, mortality_rates_count); + update(census); EXPECT_EQ(1u, census.tenuring_threshold()); // mr = 0.7 from 1 -> 2, above mr threshold of 0.1 - build_mortality_rate_curve(census, mortality_rates, mortality_rates_count); + update(census); EXPECT_EQ(2u, census.tenuring_threshold()); // mr = 0.5 from 2 -> 3, above mr threshold of 0.1 - build_mortality_rate_curve(census, mortality_rates, mortality_rates_count); + update(census); EXPECT_EQ(3u, census.tenuring_threshold()); // mr = 0.3 from 3 -> 4, above mr threshold of 0.1 - build_mortality_rate_curve(census, mortality_rates, mortality_rates_count); + update(census); EXPECT_EQ(4u, census.tenuring_threshold()); // mr = 0.1 from 4 -> 5, not above mr threshold of 0.1, stay at 4? - build_mortality_rate_curve(census, mortality_rates, mortality_rates_count); + update(census); EXPECT_EQ(5u, census.tenuring_threshold()); - build_mortality_rate_curve(census, mortality_rates, mortality_rates_count); + update(census); EXPECT_EQ(5u, census.tenuring_threshold()); - build_mortality_rate_curve(census, mortality_rates, mortality_rates_count); + update(census); EXPECT_EQ(5u, census.tenuring_threshold()); } From c4fb8cf8f990dc071fac58b935c8d3396edbe99a Mon Sep 17 00:00:00 2001 From: William Kemper Date: Tue, 19 Aug 2025 16:25:16 -0700 Subject: [PATCH 20/47] Instrumentation to guage potential changes to promotion reserves --- .../share/gc/shenandoah/shenandoahGeneration.cpp | 10 ++++++++++ src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp | 2 ++ 2 files changed, 12 insertions(+) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp index ab0a65a9fef7b..9ddbdd0bae46b 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp @@ -352,6 +352,10 @@ void ShenandoahGeneration::compute_evacuation_budgets(ShenandoahHeap* const heap const size_t consumed_by_advance_promotion = select_aged_regions(old_promo_reserve); assert(consumed_by_advance_promotion <= maximum_old_evacuation_reserve, "Cannot promote more than available old-gen memory"); + ShenandoahAgeCensus* census = ShenandoahGenerationalHeap::cast(heap)->age_census(); + log_info(gc, ergo)("Old gen reserved for live objects in aged regions: %zu, total tenurable population: %zu", + consumed_by_advance_promotion, census->get_tenurable_bytes()); + // Note that unused old_promo_reserve might not be entirely consumed_by_advance_promotion. Do not transfer this // to old_evacuation_reserve because this memory is likely very fragmented, and we do not want to increase the likelihood // of old evacuation failure. @@ -655,6 +659,12 @@ size_t ShenandoahGeneration::select_aged_regions(const size_t old_promotion_rese selected_regions, selected_live, old_consumed, old_promotion_reserve); } + const size_t tenurable_next_cycle = heap->age_census()->get_tenurable_bytes(tenuring_threshold - 1); + const size_t tenurable_this_cycle = heap->age_census()->get_tenurable_bytes(tenuring_threshold); + + log_info(gc, ergo)("Promotion potential: %zu, tenurable next cycle: %zu, tenurable this cycle: %zu, difference: %zu", + promo_potential, tenurable_next_cycle, tenurable_this_cycle, (tenurable_next_cycle - tenurable_this_cycle)); + heap->old_generation()->set_pad_for_promote_in_place(promote_in_place_pad); heap->old_generation()->set_promotion_potential(promo_potential); return old_consumed; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp index f29bcfb6ebf80..8eae2b6a4333f 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp @@ -1277,6 +1277,8 @@ void ShenandoahHeap::concurrent_prepare_for_update_refs() { set_gc_state_concurrent(UPDATE_REFS, true); } + log_info(gc, ergo)("Evacuation complete, promotions expended: %zu", old_generation()->get_promoted_expended()); + // This will propagate the gc state and retire gclabs and plabs for threads that require it. ShenandoahPrepareForUpdateRefsHandshakeClosure prepare_for_update_refs(_gc_state.raw_value()); From ca6ca98d6905bdc3ad5dfc02de34170f030084cc Mon Sep 17 00:00:00 2001 From: William Kemper Date: Wed, 20 Aug 2025 10:43:25 -0700 Subject: [PATCH 21/47] Fix linker error --- .../gc/shenandoah/shenandoahAgeCensus.cpp | 25 ++++++++++--------- .../gc/shenandoah/shenandoahGeneration.cpp | 1 + 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp index f5874ed71b172..6b466a52087c4 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp @@ -183,6 +183,19 @@ void ShenandoahAgeCensus::update_census(size_t age0_pop, AgeTable* pv1, AgeTable } +size_t ShenandoahAgeCensus::get_tenurable_bytes(const uint tenuring_threshold) const { + assert(_epoch < MAX_SNAPSHOTS, "Out of bounds"); + size_t total = 0; + const AgeTable* pv = _global_age_table[_epoch]; + for (uint i = 0; i < MAX_COHORTS; i++) { + if (i>= tenuring_threshold) { + total += pv->sizes[i]; + } + } + return total; +} + + // Reset the epoch for the global age tables, // clearing all history. void ShenandoahAgeCensus::reset_global() { @@ -237,18 +250,6 @@ bool ShenandoahAgeCensus::is_clear_local() { return true; } -size_t ShenandoahAgeCensus::get_tenurable_bytes(const uint tenuring_threshold) const { - assert(_epoch < MAX_SNAPSHOTS, "Out of bounds"); - size_t total = 0; - const AgeTable* pv = _global_age_table[_epoch]; - for (uint i = 0; i < MAX_COHORTS; i++) { - if (i>= tenuring_threshold) { - total += pv->sizes[i]; - } - } - return total; -} - size_t ShenandoahAgeCensus::get_all_ages(uint snap) { assert(snap < MAX_SNAPSHOTS, "Out of bounds"); size_t pop = 0; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp index 9ddbdd0bae46b..80925ccb0d00a 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp @@ -24,6 +24,7 @@ */ #include "gc/shenandoah/heuristics/shenandoahHeuristics.hpp" +#include "gc/shenandoah/shenandoahAgeCensus.hpp" #include "gc/shenandoah/shenandoahCollectionSetPreselector.hpp" #include "gc/shenandoah/shenandoahCollectorPolicy.hpp" #include "gc/shenandoah/shenandoahFreeSet.hpp" From 89a6212ee6f1bb6c709c05229cc8a042da12bb08 Mon Sep 17 00:00:00 2001 From: William Kemper Date: Wed, 20 Aug 2025 12:11:47 -0700 Subject: [PATCH 22/47] How is promotion potential higher than next cycle's tenurable objects size? --- src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp index 80925ccb0d00a..66b70e319ad00 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp @@ -630,6 +630,8 @@ size_t ShenandoahGeneration::select_aged_regions(const size_t old_promotion_rese // Subsequent regions may be selected if they have smaller live data. } + log_info(gc, ergo)("Promotion potential of aged regions with sufficient garbage: %zu", promo_potential); + // Sort in increasing order according to live data bytes. Note that candidates represents the number of regions // that qualify to be promoted by evacuation. size_t old_consumed = 0; From d3a63ec7a7516467eacae7fd0df7dc10dca9a768 Mon Sep 17 00:00:00 2001 From: William Kemper Date: Wed, 20 Aug 2025 13:58:38 -0700 Subject: [PATCH 23/47] Oops, age table sizes are words --- src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp | 2 +- test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp index 6b466a52087c4..63f542d252997 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp @@ -192,7 +192,7 @@ size_t ShenandoahAgeCensus::get_tenurable_bytes(const uint tenuring_threshold) c total += pv->sizes[i]; } } - return total; + return total * HeapWordSize; } diff --git a/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp b/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp index 7e964b608eb2b..efb8c186df824 100644 --- a/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp +++ b/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp @@ -54,7 +54,7 @@ class ShenandoahAgeCensusTest : public ::testing::Test { total += _cohort_populations[i]; } } - return total; + return total * HeapWordSize; } static void get_cohort_populations(const double mortality_rates[], size_t cohort_populations[], const size_t cohorts) { From 32f6b3bef0e648df4be05eb1794a3f5b49c8dcfb Mon Sep 17 00:00:00 2001 From: William Kemper Date: Wed, 20 Aug 2025 14:25:47 -0700 Subject: [PATCH 24/47] Add test that simulates promotion above tenuring age --- .../shenandoah/test_shenandoahAgeCensus.cpp | 96 ++++++++++++++----- 1 file changed, 74 insertions(+), 22 deletions(-) diff --git a/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp b/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp index 2b27a22b58a40..8407b0d237709 100644 --- a/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp +++ b/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp @@ -28,27 +28,61 @@ class ShenandoahAgeCensusTest : public ::testing::Test { protected: static constexpr size_t MinimumPopulationSize = 4*K; - static void build_mortality_rate_curve(ShenandoahAgeCensus& census, const double mortality_rates[], const size_t cohorts) { - constexpr size_t initial_population = MinimumPopulationSize * 1000; + static constexpr size_t InitialPopulationSize = MinimumPopulationSize * 1000; - size_t population = initial_population; - for (size_t i = 0; i < cohorts; i++) { - population = population * (1.0 - mortality_rates[i]); - census.add(i + 1, 0, 0, population, 0); + size_t _cohorts_count = ShenandoahAgeCensus::MAX_COHORTS; + double _mortality_rates[ShenandoahAgeCensus::MAX_COHORTS]; + size_t _cohort_populations[ShenandoahAgeCensus::MAX_COHORTS]; + + ShenandoahAgeCensusTest() + : _mortality_rates{0.9, 0.7, 0.5, 0.3, 0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0} + { + get_cohort_populations(_mortality_rates, _cohort_populations, _cohorts_count); + } + + void update(ShenandoahAgeCensus& census) const { + for (size_t i = 1; i < _cohorts_count; i++) { + census.add(i, 0, 0, _cohort_populations[i], 0); + } + census.update_census(_cohort_populations[0]); + } + + size_t get_total_population_older_than(const size_t min_cohort_age) const { + size_t total = 0; + for (size_t i = 0; i < _cohorts_count; i++) { + if (i >= min_cohort_age) { + total += _cohort_populations[i]; + } } + return total; + } - census.update_census(initial_population); + void promote_all_tenurable(const size_t tenuring_threshold) { + for (size_t i = 0; i < _cohorts_count; i++) { + if (i >= tenuring_threshold) { + _cohort_populations[i] = 0; + } + } + } + + static void get_cohort_populations(const double mortality_rates[], size_t cohort_populations[], const size_t cohorts) { + size_t population = InitialPopulationSize; + cohort_populations[0] = population; + for (size_t i = 1; i < cohorts; i++) { + population = population * (1.0 - mortality_rates[i - 1]); + cohort_populations[i] = population; + } } }; TEST_F(ShenandoahAgeCensusTest, initialize) { - ShenandoahAgeCensus census(4); + const ShenandoahAgeCensus census(1); EXPECT_EQ(census.tenuring_threshold(), ShenandoahAgeCensus::MAX_COHORTS); } TEST_F(ShenandoahAgeCensusTest, ignore_small_populations) { // Small populations are ignored so we do not return early before reaching the youngest cohort. - ShenandoahAgeCensus census(4); + ShenandoahAgeCensus census(1); census.add(1, 0, 0, 32, 0); census.add(1, 0, 0, 32, 0); census.update_census(64); @@ -56,39 +90,57 @@ TEST_F(ShenandoahAgeCensusTest, ignore_small_populations) { } TEST_F(ShenandoahAgeCensusTest, find_high_mortality_rate) { - ShenandoahAgeCensus census(4); - constexpr double mortality_rates[] = { - 0.9, 0.7, 0.5, 0.3, 0.1, 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 - }; - size_t mortality_rates_count = sizeof(mortality_rates) / sizeof(double); + ShenandoahAgeCensus census(1); // Initial threshold, no data EXPECT_EQ(16u, census.tenuring_threshold()); // No deaths in previous data, everybody seems to survive, set threshold to 1 (tenure everything). - build_mortality_rate_curve(census, mortality_rates, mortality_rates_count); + update(census); EXPECT_EQ(1u, census.tenuring_threshold()); // mr = 0.7 from 1 -> 2, above mr threshold of 0.1 - build_mortality_rate_curve(census, mortality_rates, mortality_rates_count); + update(census); EXPECT_EQ(2u, census.tenuring_threshold()); // mr = 0.5 from 2 -> 3, above mr threshold of 0.1 - build_mortality_rate_curve(census, mortality_rates, mortality_rates_count); + update(census); EXPECT_EQ(3u, census.tenuring_threshold()); // mr = 0.3 from 3 -> 4, above mr threshold of 0.1 - build_mortality_rate_curve(census, mortality_rates, mortality_rates_count); + update(census); EXPECT_EQ(4u, census.tenuring_threshold()); // mr = 0.1 from 4 -> 5, not above mr threshold of 0.1, stay at 4? - build_mortality_rate_curve(census, mortality_rates, mortality_rates_count); + update(census); EXPECT_EQ(5u, census.tenuring_threshold()); - build_mortality_rate_curve(census, mortality_rates, mortality_rates_count); + update(census); EXPECT_EQ(5u, census.tenuring_threshold()); - build_mortality_rate_curve(census, mortality_rates, mortality_rates_count); + update(census); EXPECT_EQ(5u, census.tenuring_threshold()); } + +TEST_F(ShenandoahAgeCensusTest, ignore_mortality_caused_by_promotions) { + ShenandoahAgeCensus census(1); + + // Simulate a sequence of censuses with the same mortality rate. Each one will see a + // mortality rate above the tenuring threshold and raise the tenuring threshold by one. + update(census); + update(census); + update(census); + + EXPECT_EQ(3u, census.tenuring_threshold()); + + // Simulate the effect of promoting all objects above the tenuring threshold + // out of the young generation. This will look like a very high (100%) mortality + // rate for these cohorts. However, we do _not_ want to raise the threshold in + // this case because these objects haven't really "died", they have just been + // tenured. + promote_all_tenurable(census.tenuring_threshold()); + update(census); + + // We want this to stay at 3 - the mortality in 4th cohort was caused by expected promotions. + EXPECT_EQ(4u, census.tenuring_threshold()); +} From 642c1a0067ff8343bd2f9a3249e493dcfbadbfd8 Mon Sep 17 00:00:00 2001 From: William Kemper Date: Thu, 21 Aug 2025 15:10:10 -0700 Subject: [PATCH 25/47] Checkpoint, tests pass --- .../gc/shenandoah/shenandoahAgeCensus.cpp | 11 +++--- .../gc/shenandoah/shenandoahAgeCensus.hpp | 5 +++ .../shenandoah/shenandoahGenerationalHeap.cpp | 2 +- .../shenandoah/test_shenandoahAgeCensus.cpp | 38 +++++++++++-------- 4 files changed, 34 insertions(+), 22 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp index ffec1b86c981b..6893823b6dee1 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp @@ -301,9 +301,10 @@ uint ShenandoahAgeCensus::compute_tenuring_threshold() { uint upper_bound = ShenandoahGenerationalMaxTenuringAge; const uint prev_tt = previous_tenuring_threshold(); if (ShenandoahGenerationalCensusIgnoreOlderCohorts && prev_tt > 0) { - // We stay below the computed tenuring threshold for the last cycle plus 1, - // ignoring the mortality rates of any older cohorts. - upper_bound = MIN2(upper_bound, prev_tt + 1); + // We stay below the computed tenuring threshold for the last cycle, + // ignoring the mortality rates of any older cohorts (which may see + // higher mortality rates due to promotions). + upper_bound = MIN2(upper_bound, prev_tt); } upper_bound = MIN2(upper_bound, markWord::max_age); @@ -318,7 +319,7 @@ uint ShenandoahAgeCensus::compute_tenuring_threshold() { const size_t prev_pop = prev_pv->sizes[i-1]; const double mr = mortality_rate(prev_pop, cur_pop); if (prev_pop > ShenandoahGenerationalTenuringCohortPopulationThreshold && - mr > 0 && ((mr - ShenandoahGenerationalTenuringMortalityRateThreshold) > 0.001)) { + mr > 0 && mr > ShenandoahGenerationalTenuringMortalityRateThreshold) { // This is the oldest cohort that has high mortality. // We ignore any cohorts that had a very low population count, or // that have a lower mortality rate than we care to age in young; these @@ -327,7 +328,7 @@ uint ShenandoahAgeCensus::compute_tenuring_threshold() { // so that we do not prematurely promote objects of this age. assert(tenuring_threshold == i+1 || tenuring_threshold == upper_bound, "Error"); assert(tenuring_threshold >= lower_bound && tenuring_threshold <= upper_bound, "Error"); - return tenuring_threshold; + return i + 1; } // Remember that we passed over this cohort, looking for younger cohorts // showing high mortality. We want to tenure cohorts of this age. diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp index 6868b73512502..4bb16f328b1e5 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp @@ -176,6 +176,11 @@ class ShenandoahAgeCensus: public CHeapObj { return _local_age_table[worker_id]; } + // Return true if this age is above the tenuring threshold. + bool is_tenurable(uint age) const { + return age > tenuring_threshold(); + } + // Update the local age table for worker_id by size for // given obj_age, region_age, and region_youth CENSUS_NOISE(void add(uint obj_age, uint region_age, uint region_youth, size_t size, uint worker_id);) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp index d05ae713645af..0aca8f971e3aa 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp @@ -216,7 +216,7 @@ oop ShenandoahGenerationalHeap::evacuate_object(oop p, Thread* thread) { if (mark.has_displaced_mark_helper()) { // We don't want to deal with MT here just to ensure we read the right mark word. // Skip the potential promotion attempt for this one. - } else if (r->age() + mark.age() >= age_census()->tenuring_threshold()) { + } else if (age_census()->is_tenurable(r->age() + mark.age())) { oop result = try_evacuate_object(p, thread, r, OLD_GENERATION); if (result != nullptr) { return result; diff --git a/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp b/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp index 8407b0d237709..c1c239929d0b4 100644 --- a/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp +++ b/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp @@ -40,13 +40,17 @@ class ShenandoahAgeCensusTest : public ::testing::Test { get_cohort_populations(_mortality_rates, _cohort_populations, _cohorts_count); } - void update(ShenandoahAgeCensus& census) const { - for (size_t i = 1; i < _cohorts_count; i++) { + void update(ShenandoahAgeCensus& census, size_t cohorts) const { + for (size_t i = 1; i < cohorts; i++) { census.add(i, 0, 0, _cohort_populations[i], 0); } census.update_census(_cohort_populations[0]); } + void update(ShenandoahAgeCensus& census) const { + update(census, _cohorts_count); + } + size_t get_total_population_older_than(const size_t min_cohort_age) const { size_t total = 0; for (size_t i = 0; i < _cohorts_count; i++) { @@ -59,7 +63,7 @@ class ShenandoahAgeCensusTest : public ::testing::Test { void promote_all_tenurable(const size_t tenuring_threshold) { for (size_t i = 0; i < _cohorts_count; i++) { - if (i >= tenuring_threshold) { + if (i > tenuring_threshold) { _cohort_populations[i] = 0; } } @@ -96,29 +100,29 @@ TEST_F(ShenandoahAgeCensusTest, find_high_mortality_rate) { EXPECT_EQ(16u, census.tenuring_threshold()); // No deaths in previous data, everybody seems to survive, set threshold to 1 (tenure everything). - update(census); + update(census, 1); EXPECT_EQ(1u, census.tenuring_threshold()); // mr = 0.7 from 1 -> 2, above mr threshold of 0.1 - update(census); + update(census, 2); EXPECT_EQ(2u, census.tenuring_threshold()); // mr = 0.5 from 2 -> 3, above mr threshold of 0.1 - update(census); + update(census, 3); EXPECT_EQ(3u, census.tenuring_threshold()); // mr = 0.3 from 3 -> 4, above mr threshold of 0.1 - update(census); + update(census, 4); EXPECT_EQ(4u, census.tenuring_threshold()); // mr = 0.1 from 4 -> 5, not above mr threshold of 0.1, stay at 4? - update(census); + update(census, 5); EXPECT_EQ(5u, census.tenuring_threshold()); - update(census); + update(census, 6); EXPECT_EQ(5u, census.tenuring_threshold()); - update(census); + update(census, 7); EXPECT_EQ(5u, census.tenuring_threshold()); } @@ -127,11 +131,13 @@ TEST_F(ShenandoahAgeCensusTest, ignore_mortality_caused_by_promotions) { // Simulate a sequence of censuses with the same mortality rate. Each one will see a // mortality rate above the tenuring threshold and raise the tenuring threshold by one. - update(census); - update(census); - update(census); + update(census, 1); + update(census, 2); + update(census, 3); + update(census, 4); + update(census, 5); - EXPECT_EQ(3u, census.tenuring_threshold()); + EXPECT_EQ(5u, census.tenuring_threshold()); // Simulate the effect of promoting all objects above the tenuring threshold // out of the young generation. This will look like a very high (100%) mortality @@ -141,6 +147,6 @@ TEST_F(ShenandoahAgeCensusTest, ignore_mortality_caused_by_promotions) { promote_all_tenurable(census.tenuring_threshold()); update(census); - // We want this to stay at 3 - the mortality in 4th cohort was caused by expected promotions. - EXPECT_EQ(4u, census.tenuring_threshold()); + // We want this to stay at 5 - the mortality in 6th cohort was caused by expected promotions. + EXPECT_EQ(5u, census.tenuring_threshold()); } From 82c06f20e17445eaf232f2cb1a67fe8bc9694bf1 Mon Sep 17 00:00:00 2001 From: William Kemper Date: Fri, 22 Aug 2025 10:21:09 -0700 Subject: [PATCH 26/47] Clean up tests --- .../gc/shenandoah/shenandoahAgeCensus.cpp | 2 +- .../shenandoah/test_shenandoahAgeCensus.cpp | 28 ++++++++++--------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp index 6893823b6dee1..c7956d1d2b553 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp @@ -326,7 +326,7 @@ uint ShenandoahAgeCensus::compute_tenuring_threshold() { // cohorts are considered eligible for tenuring when all older // cohorts are. We return the next higher age as the tenuring threshold // so that we do not prematurely promote objects of this age. - assert(tenuring_threshold == i+1 || tenuring_threshold == upper_bound, "Error"); + assert(tenuring_threshold == i + 1 || tenuring_threshold == upper_bound, "Error"); assert(tenuring_threshold >= lower_bound && tenuring_threshold <= upper_bound, "Error"); return i + 1; } diff --git a/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp b/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp index c1c239929d0b4..260ef75eb1d83 100644 --- a/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp +++ b/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp @@ -35,9 +35,9 @@ class ShenandoahAgeCensusTest : public ::testing::Test { size_t _cohort_populations[ShenandoahAgeCensus::MAX_COHORTS]; ShenandoahAgeCensusTest() - : _mortality_rates{0.9, 0.7, 0.5, 0.3, 0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0} + : _mortality_rates{0.9, 0.7, 0.5, 0.3, 0.09, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0} { - get_cohort_populations(_mortality_rates, _cohort_populations, _cohorts_count); + build_cohort_populations(_mortality_rates, _cohort_populations, _cohorts_count); } void update(ShenandoahAgeCensus& census, size_t cohorts) const { @@ -69,12 +69,10 @@ class ShenandoahAgeCensusTest : public ::testing::Test { } } - static void get_cohort_populations(const double mortality_rates[], size_t cohort_populations[], const size_t cohorts) { - size_t population = InitialPopulationSize; - cohort_populations[0] = population; + static void build_cohort_populations(const double mortality_rates[], size_t cohort_populations[], const size_t cohorts) { + cohort_populations[0] = InitialPopulationSize; for (size_t i = 1; i < cohorts; i++) { - population = population * (1.0 - mortality_rates[i - 1]); - cohort_populations[i] = population; + cohort_populations[i] = cohort_populations[i - 1] * (1.0 - mortality_rates[i - 1]); } } }; @@ -99,29 +97,33 @@ TEST_F(ShenandoahAgeCensusTest, find_high_mortality_rate) { // Initial threshold, no data EXPECT_EQ(16u, census.tenuring_threshold()); - // No deaths in previous data, everybody seems to survive, set threshold to 1 (tenure everything). + // Provide population data for 1st cohort. Previous epoch has no population data so our + // algorithm skips over all cohorts, leaving tenuring threshold at 1. update(census, 1); EXPECT_EQ(1u, census.tenuring_threshold()); - // mr = 0.7 from 1 -> 2, above mr threshold of 0.1 + // Mortality rate of 1st cohort at age 1 is 0.9, we don't want to promote here. Move threshold to 2. update(census, 2); EXPECT_EQ(2u, census.tenuring_threshold()); - // mr = 0.5 from 2 -> 3, above mr threshold of 0.1 + // Mortality rate of 1st cohort at age 2 is 0.7, we don't want to promote here. Move threshold to 3. update(census, 3); EXPECT_EQ(3u, census.tenuring_threshold()); - // mr = 0.3 from 3 -> 4, above mr threshold of 0.1 + // Mortality rate of 1st cohort at age 3 is 0.5, we don't want to promote here. Move threshold to 4. update(census, 4); EXPECT_EQ(4u, census.tenuring_threshold()); - // mr = 0.1 from 4 -> 5, not above mr threshold of 0.1, stay at 4? + // Mortality rate of 1st cohort at age 4 is 0.3, we don't want to promote here. Move threshold to 5. update(census, 5); EXPECT_EQ(5u, census.tenuring_threshold()); + // Mortality rate of 1st cohort at age 5 is 0.09, this is less than the mortality rate threshold. It + // is okay to tenure objects older than 5 now. Keep threshold at 5. update(census, 6); EXPECT_EQ(5u, census.tenuring_threshold()); + // Mortality rate at this age is 0. Keep tenuring threshold at 5. update(census, 7); EXPECT_EQ(5u, census.tenuring_threshold()); } @@ -147,6 +149,6 @@ TEST_F(ShenandoahAgeCensusTest, ignore_mortality_caused_by_promotions) { promote_all_tenurable(census.tenuring_threshold()); update(census); - // We want this to stay at 5 - the mortality in 6th cohort was caused by expected promotions. + // We want this to stay at 5 - the mortality in 1st cohort at age 6 was caused by expected promotions. EXPECT_EQ(5u, census.tenuring_threshold()); } From 64c68395f22d4c1f9a4ec0ea00f59d13cbdf1f12 Mon Sep 17 00:00:00 2001 From: William Kemper Date: Fri, 22 Aug 2025 11:20:42 -0700 Subject: [PATCH 27/47] Revert unintended change --- src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp index c7956d1d2b553..af34c38d2af4c 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp @@ -319,7 +319,7 @@ uint ShenandoahAgeCensus::compute_tenuring_threshold() { const size_t prev_pop = prev_pv->sizes[i-1]; const double mr = mortality_rate(prev_pop, cur_pop); if (prev_pop > ShenandoahGenerationalTenuringCohortPopulationThreshold && - mr > 0 && mr > ShenandoahGenerationalTenuringMortalityRateThreshold) { + mr > ShenandoahGenerationalTenuringMortalityRateThreshold) { // This is the oldest cohort that has high mortality. // We ignore any cohorts that had a very low population count, or // that have a lower mortality rate than we care to age in young; these From b3109ede2e74487dd59a7354d6ae927424c88392 Mon Sep 17 00:00:00 2001 From: William Kemper Date: Fri, 22 Aug 2025 12:54:56 -0700 Subject: [PATCH 28/47] Use age census to inform size of old generation --- src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp index 66b70e319ad00..cd7600a456159 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp @@ -665,11 +665,11 @@ size_t ShenandoahGeneration::select_aged_regions(const size_t old_promotion_rese const size_t tenurable_next_cycle = heap->age_census()->get_tenurable_bytes(tenuring_threshold - 1); const size_t tenurable_this_cycle = heap->age_census()->get_tenurable_bytes(tenuring_threshold); - log_info(gc, ergo)("Promotion potential: %zu, tenurable next cycle: %zu, tenurable this cycle: %zu, difference: %zu", - promo_potential, tenurable_next_cycle, tenurable_this_cycle, (tenurable_next_cycle - tenurable_this_cycle)); + log_info(gc, ergo)("Promotion potential: %zu, tenurable next cycle: %zu, tenurable this cycle: %zu, selected for promotion: %zu", + promo_potential, tenurable_next_cycle, tenurable_this_cycle, old_consumed); heap->old_generation()->set_pad_for_promote_in_place(promote_in_place_pad); - heap->old_generation()->set_promotion_potential(promo_potential); + heap->old_generation()->set_promotion_potential(tenurable_next_cycle); return old_consumed; } From 8b2fb41a0d4e70196619503e2ecf9e60ef488606 Mon Sep 17 00:00:00 2001 From: William Kemper Date: Fri, 22 Aug 2025 13:57:53 -0700 Subject: [PATCH 29/47] Fix release build --- .../share/gc/shenandoah/shenandoahAgeCensus.hpp | 4 ++-- .../gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp | 11 ++++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp index 4bb16f328b1e5..7a2517a516c8e 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp @@ -91,7 +91,7 @@ struct ShenandoahNoiseStats { // // In addition, this class also maintains per worker population vectors into which // census for the current minor GC is accumulated (during marking or, optionally, during -// evacuation). These are cleared after each marking (resectively, evacuation) cycle, +// evacuation). These are cleared after each marking (respectively, evacuation) cycle, // once the per-worker data is consolidated into the appropriate population vector // per minor collection. The _local_age_table is thus C x N, for N GC workers. class ShenandoahAgeCensus: public CHeapObj { @@ -172,7 +172,7 @@ class ShenandoahAgeCensus: public CHeapObj { // Return the local age table (population vector) for worker_id. // Only used in the case of (ShenandoahGenerationalAdaptiveTenuring && !ShenandoahGenerationalCensusAtEvac) - AgeTable* get_local_age_table(uint worker_id) { + AgeTable* get_local_age_table(uint worker_id) const { return _local_age_table[worker_id]; } diff --git a/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp b/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp index 260ef75eb1d83..68090899095e9 100644 --- a/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp +++ b/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp @@ -40,9 +40,14 @@ class ShenandoahAgeCensusTest : public ::testing::Test { build_cohort_populations(_mortality_rates, _cohort_populations, _cohorts_count); } + static void add_population(ShenandoahAgeCensus& census, uint age, size_t population_words) { + CENSUS_NOISE(census.add(age, 0, 0, population_words, 0)); + NO_CENSUS_NOISE(census.add(age, 0, population_words, 0)); + } + void update(ShenandoahAgeCensus& census, size_t cohorts) const { for (size_t i = 1; i < cohorts; i++) { - census.add(i, 0, 0, _cohort_populations[i], 0); + add_population(census, i, _cohort_populations[i]); } census.update_census(_cohort_populations[0]); } @@ -85,8 +90,8 @@ TEST_F(ShenandoahAgeCensusTest, initialize) { TEST_F(ShenandoahAgeCensusTest, ignore_small_populations) { // Small populations are ignored so we do not return early before reaching the youngest cohort. ShenandoahAgeCensus census(1); - census.add(1, 0, 0, 32, 0); - census.add(1, 0, 0, 32, 0); + add_population(census,1, 32); + add_population(census,1, 32); census.update_census(64); EXPECT_EQ(1u, census.tenuring_threshold()); } From 225c6ede575733797a4c5a2b0677cbaa0e097356 Mon Sep 17 00:00:00 2001 From: William Kemper Date: Fri, 22 Aug 2025 14:15:47 -0700 Subject: [PATCH 30/47] Fix age table printing (again) --- .../gc/shenandoah/shenandoahAgeCensus.cpp | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp index 6b09996d05b59..fcd6b96cdfe97 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp @@ -387,22 +387,22 @@ void ShenandoahAgeCensus::print() { for (uint i = 1; i < MAX_COHORTS; i++) { const size_t prev_pop = prev_pv->sizes[i-1]; // (i-1) OK because i >= 1 const size_t cur_pop = cur_pv->sizes[i]; - double mr = mortality_rate(prev_pop, cur_pop); - - if ( i >= tt) { - total_tenurable += cur_pop; - if (i == tt) { - // Underline the cohort for tenuring threshold (if < MAX_COHORTS) - log_info(gc, age)("----------------------------------------------------------------------------"); - } - } + const double mr = mortality_rate(prev_pop, cur_pop); // Suppress printing when everything is zero if (prev_pop + cur_pop > 0) { - log_info(gc, age) - (" - age %3u: prev %10zu bytes, curr %10zu bytes, mortality %.2f ", - i, prev_pop*oopSize, cur_pop*oopSize, mr); + log_info(gc, age) (" - age %3u: prev %10zu bytes, curr %10zu bytes, mortality %.2f ", i, prev_pop*oopSize, cur_pop*oopSize, mr); } + + if (i == tt) { + // Underline the cohort for tenuring threshold (if < MAX_COHORTS) + log_info(gc, age)("----------------------------------------------------------------------------"); + } + + if ( i > tt) { + total_tenurable += cur_pop; + } + total += cur_pop; } From c663e2fc6654ad736b841f521f873a6005a14566 Mon Sep 17 00:00:00 2001 From: William Kemper Date: Fri, 22 Aug 2025 14:17:19 -0700 Subject: [PATCH 31/47] Track and print promotion failure stats --- src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp | 4 +++- .../share/gc/shenandoah/shenandoahOldGeneration.cpp | 9 ++++++++- .../share/gc/shenandoah/shenandoahOldGeneration.hpp | 13 +++++++++++-- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp index 8eae2b6a4333f..132b2ed6120cb 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp @@ -1277,7 +1277,9 @@ void ShenandoahHeap::concurrent_prepare_for_update_refs() { set_gc_state_concurrent(UPDATE_REFS, true); } - log_info(gc, ergo)("Evacuation complete, promotions expended: %zu", old_generation()->get_promoted_expended()); + log_info(gc, ergo)("Evacuation complete, promotions expended: %zu, failed count: %zu, failed bytes: %zu", + old_generation()->get_promoted_expended(), old_generation()->get_promotion_failed_count(), + old_generation()->get_promotion_failed_words() * HeapWordSize); // This will propagate the gc state and retire gclabs and plabs for threads that require it. ShenandoahPrepareForUpdateRefsHandshakeClosure prepare_for_update_refs(_gc_state.raw_value()); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.cpp b/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.cpp index fbf86f7abc566..8a01bd478d072 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.cpp @@ -205,6 +205,8 @@ ShenandoahOldGeneration::ShenandoahOldGeneration(uint max_queues, size_t max_cap _promoted_expended(0), _promotion_potential(0), _pad_for_promote_in_place(0), + _promotion_failure_count(0), + _promotion_failure_words(0), _promotable_humongous_regions(0), _promotable_regular_regions(0), _is_parsable(true), @@ -241,7 +243,9 @@ void ShenandoahOldGeneration::augment_promoted_reserve(size_t increment) { void ShenandoahOldGeneration::reset_promoted_expended() { shenandoah_assert_heaplocked_or_safepoint(); - Atomic::store(&_promoted_expended, (size_t) 0); + Atomic::store(&_promoted_expended, 0UL); + Atomic::store(&_promotion_failure_count, 0UL); + Atomic::store(&_promotion_failure_words, 0UL); } size_t ShenandoahOldGeneration::expend_promoted(size_t increment) { @@ -683,6 +687,9 @@ void ShenandoahOldGeneration::handle_failed_promotion(Thread* thread, size_t siz const size_t gc_id = heap->control_thread()->get_gc_id(); + Atomic::inc(&_promotion_failure_count); + Atomic::add(&_promotion_failure_words, size); + if ((gc_id != last_report_epoch) || (epoch_report_count++ < MaxReportsPerEpoch)) { size_t promotion_expended; size_t promotion_reserve; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.hpp b/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.hpp index 49acdcc5aa04d..334b4422b17c0 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.hpp @@ -75,6 +75,11 @@ class ShenandoahOldGeneration : public ShenandoahGeneration { // objects). This field records the total amount of padding used for such regions. size_t _pad_for_promote_in_place; + // Keep track of the size of promotions that failed. Perhaps we should use this to increase + // the size of the old generation for the next collection cycle. + size_t _promotion_failure_count; + size_t _promotion_failure_words; + // During construction of the collection set, we keep track of regions that are eligible // for promotion in place. These fields track the count of those humongous and regular regions. // This data is used to force the evacuation phase even when the collection set is otherwise @@ -119,6 +124,10 @@ class ShenandoahOldGeneration : public ShenandoahGeneration { // This is used on the allocation path to gate promotions that would exceed the reserve size_t get_promoted_expended() const; + // Return the count and size (in words) of failed promotions since the last reset + size_t get_promotion_failed_count() const { return _promotion_failure_count; } + size_t get_promotion_failed_words() const { return _promotion_failure_words; } + // Test if there is enough memory reserved for this promotion bool can_promote(size_t requested_bytes) const { size_t promotion_avail = get_promoted_reserve(); @@ -139,8 +148,8 @@ class ShenandoahOldGeneration : public ShenandoahGeneration { ssize_t get_region_balance() const { return _region_balance; } // See description in field declaration - void set_promotion_potential(size_t val) { _promotion_potential = val; }; - size_t get_promotion_potential() const { return _promotion_potential; }; + void set_promotion_potential(size_t val) { _promotion_potential = val; } + size_t get_promotion_potential() const { return _promotion_potential; } // See description in field declaration void set_pad_for_promote_in_place(size_t pad) { _pad_for_promote_in_place = pad; } From fb5e0fc79101ddc22222c2e54cf48bc65e1e7fee Mon Sep 17 00:00:00 2001 From: William Kemper Date: Fri, 22 Aug 2025 15:27:12 -0700 Subject: [PATCH 32/47] Use age census to size promotion reserve for current cycle --- src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp index cd7600a456159..d21021370a2fa 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp @@ -274,6 +274,7 @@ void ShenandoahGeneration::compute_evacuation_budgets(ShenandoahHeap* const heap // maximum_young_evacuation_reserve is upper bound on memory to be evacuated out of young const size_t maximum_young_evacuation_reserve = (young_generation->max_capacity() * ShenandoahEvacReserve) / 100; const size_t young_evacuation_reserve = MIN2(maximum_young_evacuation_reserve, young_generation->available_with_reserve()); + log_info(gc, ergo)("max_young_evac_reserve: %zu", maximum_young_evacuation_reserve); // maximum_old_evacuation_reserve is an upper bound on memory evacuated from old and evacuated to old (promoted), // clamped by the old generation space available. @@ -296,7 +297,7 @@ void ShenandoahGeneration::compute_evacuation_budgets(ShenandoahHeap* const heap const size_t maximum_old_evacuation_reserve = (ShenandoahOldEvacRatioPercent == 100) ? old_available : MIN2((maximum_young_evacuation_reserve * ShenandoahOldEvacRatioPercent) / (100 - ShenandoahOldEvacRatioPercent), old_available); - + log_info(gc, ergo)("max_old_evac_reserve: %zu", maximum_old_evacuation_reserve); // Second priority is to reclaim garbage out of old-gen if there are old-gen collection candidates. Third priority // is to promote as much as we have room to promote. However, if old-gen memory is in short supply, this means young @@ -657,7 +658,7 @@ size_t ShenandoahGeneration::select_aged_regions(const size_t old_promotion_rese // We keep going even if one region is excluded from selection because we need to accumulate all eligible // regions that are not preselected into promo_potential } - log_debug(gc)("Preselected %zu regions containing %zu live bytes," + log_info(gc, ergo)("Preselected %zu regions containing %zu live bytes," " consuming: %zu of budgeted: %zu", selected_regions, selected_live, old_consumed, old_promotion_reserve); } @@ -670,7 +671,8 @@ size_t ShenandoahGeneration::select_aged_regions(const size_t old_promotion_rese heap->old_generation()->set_pad_for_promote_in_place(promote_in_place_pad); heap->old_generation()->set_promotion_potential(tenurable_next_cycle); - return old_consumed; + old_consumed = MAX2(old_consumed, tenurable_this_cycle); + return MIN2(old_consumed, old_promotion_reserve); } void ShenandoahGeneration::prepare_regions_and_collection_set(bool concurrent) { From e1cdba79244e5da1a4aaf3598374ece566a26ecc Mon Sep 17 00:00:00 2001 From: William Kemper Date: Fri, 22 Aug 2025 16:08:48 -0700 Subject: [PATCH 33/47] Adjust promotion reserve based only on old regions that have been added to collection set --- .../gc/shenandoah/shenandoahGeneration.cpp | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp index d21021370a2fa..d340f4bfa337f 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp @@ -410,8 +410,13 @@ void ShenandoahGeneration::adjust_evacuation_budgets(ShenandoahHeap* const heap, old_generation->set_evacuation_reserve(old_evacuation_reserve); } - const size_t young_advance_promoted = collection_set->get_young_bytes_to_be_promoted(); - size_t young_advance_promoted_reserve_used = (size_t) (ShenandoahPromoEvacWaste * double(young_advance_promoted)); + // This is only the young bytes in aged regions. It represents a _floor_ on the amount of data we actually have to promote + // out of the collection set. In reality, it may be much higher. We previously attempted to reserved enough to hold + // all objects above the tenuring threshold. Let's stick with that. The risk is we promote _too_ much, but we have + // faith in the tenuring algorithm. + // TODO: Keep this in, but consider using established promotion reserve instead of what is in aged regions added to collection set. + // const size_t young_advance_promoted = collection_set->get_young_bytes_to_be_promoted(); + // size_t young_advance_promoted_reserve_used = (size_t) (ShenandoahPromoEvacWaste * double(young_advance_promoted)); const size_t young_evacuated = collection_set->get_young_bytes_reserved_for_evacuation(); const size_t young_evacuated_reserve_used = (size_t) (ShenandoahEvacWaste * double(young_evacuated)); @@ -424,17 +429,11 @@ void ShenandoahGeneration::adjust_evacuation_budgets(ShenandoahHeap* const heap, // Now that we've established the collection set, we know how much memory is really required by old-gen for evacuation // and promotion reserves. Try shrinking OLD now in case that gives us a bit more runway for mutator allocations during // evac and update phases. - size_t old_consumed = old_evacuated_committed + young_advance_promoted_reserve_used; + size_t old_consumed = old_evacuated_committed; if (old_available < old_consumed) { + // TODO: Set promotion reserve to zero? log_info(gc, ergo)("Old has consumed more than available, truncate young promo reserve"); - // This can happen due to round-off errors when adding the results of truncated integer arithmetic. - // We've already truncated old_evacuated_committed. Truncate young_advance_promoted_reserve_used here. - assert(young_advance_promoted_reserve_used <= (33 * (old_available - old_evacuated_committed)) / 32, - "Round-off errors should be less than 3.125%%, committed: %zu, reserved: %zu", - young_advance_promoted_reserve_used, old_available - old_evacuated_committed); - young_advance_promoted_reserve_used = old_available - old_evacuated_committed; - old_consumed = old_evacuated_committed + young_advance_promoted_reserve_used; } assert(old_available >= old_consumed, "Cannot consume (%zu) more than is available (%zu)", old_consumed, old_available); @@ -447,7 +446,7 @@ void ShenandoahGeneration::adjust_evacuation_budgets(ShenandoahHeap* const heap, // Make sure old_evac_committed is unaffiliated if (old_evacuated_committed > 0) { if (unaffiliated_old > old_evacuated_committed) { - const size_t giveaway = unaffiliated_old - old_evacuated_committed; + const size_t giveaway = unaffiliated_old - old_evacuated_committed - ShenandoahHeap::heap()->old_generation()->get_promoted_reserve(); const size_t giveaway_regions = giveaway / region_size_bytes; // round down if (giveaway_regions > 0) { excess_old = MIN2(excess_old, giveaway_regions * region_size_bytes); @@ -485,9 +484,9 @@ void ShenandoahGeneration::adjust_evacuation_budgets(ShenandoahHeap* const heap, // Add in the excess_old memory to hold unanticipated promotions, if any. If there are more unanticipated // promotions than fit in reserved memory, they will be deferred until a future GC pass. - const size_t total_promotion_reserve = young_advance_promoted_reserve_used + excess_old; - log_info(gc, ergo)("Changing promotion reserve from %zu to %zu (young advance: %zu, old excess: %zu)", - old_generation->get_promoted_reserve(), total_promotion_reserve, young_advance_promoted_reserve_used, excess_old); + const size_t total_promotion_reserve = excess_old; + log_info(gc, ergo)("Changing promotion reserve from %zu to %zu (%zu, old excess: %zu)", + old_generation->get_promoted_reserve(), total_promotion_reserve, excess_old); old_generation->set_promoted_reserve(total_promotion_reserve); old_generation->reset_promoted_expended(); } From d306a51e1124eb5aa874ea85198502bbac1ef33e Mon Sep 17 00:00:00 2001 From: William Kemper Date: Fri, 22 Aug 2025 16:37:28 -0700 Subject: [PATCH 34/47] Why do we keep giving away all our old reserve? --- src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp index d340f4bfa337f..fe47cfc37eaa0 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp @@ -473,6 +473,8 @@ void ShenandoahGeneration::adjust_evacuation_budgets(ShenandoahHeap* const heap, } if (regions_to_xfer > 0) { + log_info(gc,ergo)("Giving away %zu old regions (old_available, %zu, old_evac: %zu, unaffiliated_old: %zu)", + regions_to_xfer, old_available, old_evacuated_committed, unaffiliated_old); bool result = ShenandoahGenerationalHeap::cast(heap)->generation_sizer()->transfer_to_young(regions_to_xfer); assert(excess_old >= regions_to_xfer * region_size_bytes, "Cannot transfer (%zu, %zu) more than excess old (%zu)", @@ -485,7 +487,7 @@ void ShenandoahGeneration::adjust_evacuation_budgets(ShenandoahHeap* const heap, // Add in the excess_old memory to hold unanticipated promotions, if any. If there are more unanticipated // promotions than fit in reserved memory, they will be deferred until a future GC pass. const size_t total_promotion_reserve = excess_old; - log_info(gc, ergo)("Changing promotion reserve from %zu to %zu (%zu, old excess: %zu)", + log_info(gc, ergo)("Changing promotion reserve from %zu to %zu (old excess: %zu)", old_generation->get_promoted_reserve(), total_promotion_reserve, excess_old); old_generation->set_promoted_reserve(total_promotion_reserve); old_generation->reset_promoted_expended(); From 25c488e9b25fb0b8be56342d922ed8c972a51728 Mon Sep 17 00:00:00 2001 From: William Kemper Date: Fri, 22 Aug 2025 17:01:56 -0700 Subject: [PATCH 35/47] Count promotion reserve in old consumed --- src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp index fe47cfc37eaa0..cafdb122a614d 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp @@ -417,6 +417,7 @@ void ShenandoahGeneration::adjust_evacuation_budgets(ShenandoahHeap* const heap, // TODO: Keep this in, but consider using established promotion reserve instead of what is in aged regions added to collection set. // const size_t young_advance_promoted = collection_set->get_young_bytes_to_be_promoted(); // size_t young_advance_promoted_reserve_used = (size_t) (ShenandoahPromoEvacWaste * double(young_advance_promoted)); + size_t promoted_reserve = ShenandoahHeap::heap()->old_generation()->get_promoted_reserve(); const size_t young_evacuated = collection_set->get_young_bytes_reserved_for_evacuation(); const size_t young_evacuated_reserve_used = (size_t) (ShenandoahEvacWaste * double(young_evacuated)); @@ -429,7 +430,7 @@ void ShenandoahGeneration::adjust_evacuation_budgets(ShenandoahHeap* const heap, // Now that we've established the collection set, we know how much memory is really required by old-gen for evacuation // and promotion reserves. Try shrinking OLD now in case that gives us a bit more runway for mutator allocations during // evac and update phases. - size_t old_consumed = old_evacuated_committed; + size_t old_consumed = old_evacuated_committed + promoted_reserve; if (old_available < old_consumed) { // TODO: Set promotion reserve to zero? @@ -446,7 +447,7 @@ void ShenandoahGeneration::adjust_evacuation_budgets(ShenandoahHeap* const heap, // Make sure old_evac_committed is unaffiliated if (old_evacuated_committed > 0) { if (unaffiliated_old > old_evacuated_committed) { - const size_t giveaway = unaffiliated_old - old_evacuated_committed - ShenandoahHeap::heap()->old_generation()->get_promoted_reserve(); + const size_t giveaway = unaffiliated_old - old_evacuated_committed - promoted_reserve; const size_t giveaway_regions = giveaway / region_size_bytes; // round down if (giveaway_regions > 0) { excess_old = MIN2(excess_old, giveaway_regions * region_size_bytes); From b9e16d26140d630af982bed2a61b53e182c210d7 Mon Sep 17 00:00:00 2001 From: William Kemper Date: Mon, 25 Aug 2025 10:17:58 -0700 Subject: [PATCH 36/47] Fix windows build --- test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp b/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp index 68090899095e9..c53d0a155544c 100644 --- a/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp +++ b/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp @@ -40,13 +40,13 @@ class ShenandoahAgeCensusTest : public ::testing::Test { build_cohort_populations(_mortality_rates, _cohort_populations, _cohorts_count); } - static void add_population(ShenandoahAgeCensus& census, uint age, size_t population_words) { + static void add_population(ShenandoahAgeCensus& census, const uint age, const size_t population_words) { CENSUS_NOISE(census.add(age, 0, 0, population_words, 0)); NO_CENSUS_NOISE(census.add(age, 0, population_words, 0)); } void update(ShenandoahAgeCensus& census, size_t cohorts) const { - for (size_t i = 1; i < cohorts; i++) { + for (uint i = 1; i < cohorts; i++) { add_population(census, i, _cohort_populations[i]); } census.update_census(_cohort_populations[0]); From 268bddf418236e22172d57e0db08b052ca01573e Mon Sep 17 00:00:00 2001 From: William Kemper Date: Mon, 25 Aug 2025 13:53:31 -0700 Subject: [PATCH 37/47] Keep promotion reserve (we already accounted for this when transferring excess old regions to young generation) --- src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp index cafdb122a614d..8f3c3d148d9cd 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp @@ -406,6 +406,7 @@ void ShenandoahGeneration::adjust_evacuation_budgets(ShenandoahHeap* const heap, // Leave old_evac_reserve as previously configured } else if (old_evacuated_committed < old_evacuation_reserve) { // This happens if the old-gen collection consumes less than full budget. + log_info(gc, ergo)("Shrinking old evac reserve to match old_evac_commited: %zu", old_evacuated_committed); old_evacuation_reserve = old_evacuated_committed; old_generation->set_evacuation_reserve(old_evacuation_reserve); } @@ -417,7 +418,7 @@ void ShenandoahGeneration::adjust_evacuation_budgets(ShenandoahHeap* const heap, // TODO: Keep this in, but consider using established promotion reserve instead of what is in aged regions added to collection set. // const size_t young_advance_promoted = collection_set->get_young_bytes_to_be_promoted(); // size_t young_advance_promoted_reserve_used = (size_t) (ShenandoahPromoEvacWaste * double(young_advance_promoted)); - size_t promoted_reserve = ShenandoahHeap::heap()->old_generation()->get_promoted_reserve(); + const size_t promoted_reserve = ShenandoahHeap::heap()->old_generation()->get_promoted_reserve(); const size_t young_evacuated = collection_set->get_young_bytes_reserved_for_evacuation(); const size_t young_evacuated_reserve_used = (size_t) (ShenandoahEvacWaste * double(young_evacuated)); @@ -443,6 +444,7 @@ void ShenandoahGeneration::adjust_evacuation_budgets(ShenandoahHeap* const heap, const size_t unaffiliated_old_regions = old_generation->free_unaffiliated_regions(); const size_t unaffiliated_old = unaffiliated_old_regions * region_size_bytes; assert(old_available >= unaffiliated_old, "Unaffiliated old is a subset of old available"); + log_info(gc, ergo)("excess_old is: %zu, unaffiliated_old_regions is: %zu", excess_old, unaffiliated_old_regions); // Make sure old_evac_committed is unaffiliated if (old_evacuated_committed > 0) { @@ -451,6 +453,7 @@ void ShenandoahGeneration::adjust_evacuation_budgets(ShenandoahHeap* const heap, const size_t giveaway_regions = giveaway / region_size_bytes; // round down if (giveaway_regions > 0) { excess_old = MIN2(excess_old, giveaway_regions * region_size_bytes); + log_info(gc, ergo)("Changed excess_old to: %zu", excess_old); } else { excess_old = 0; } @@ -488,9 +491,9 @@ void ShenandoahGeneration::adjust_evacuation_budgets(ShenandoahHeap* const heap, // Add in the excess_old memory to hold unanticipated promotions, if any. If there are more unanticipated // promotions than fit in reserved memory, they will be deferred until a future GC pass. const size_t total_promotion_reserve = excess_old; - log_info(gc, ergo)("Changing promotion reserve from %zu to %zu (old excess: %zu)", + log_info(gc, ergo)("Not changing promotion reserve from %zu to %zu (old excess: %zu)", old_generation->get_promoted_reserve(), total_promotion_reserve, excess_old); - old_generation->set_promoted_reserve(total_promotion_reserve); + // old_generation->set_promoted_reserve(total_promotion_reserve); old_generation->reset_promoted_expended(); } From 2bd2e41450589ee27f65147827ebff44092b8017 Mon Sep 17 00:00:00 2001 From: William Kemper Date: Mon, 25 Aug 2025 14:21:17 -0700 Subject: [PATCH 38/47] More instrumentation --- .../share/gc/shenandoah/shenandoahGeneration.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp index 8f3c3d148d9cd..4925727cbbc6c 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp @@ -293,11 +293,10 @@ void ShenandoahGeneration::compute_evacuation_budgets(ShenandoahHeap* const heap // We have to be careful in the event that SOEP is set to 100 by the user. assert(ShenandoahOldEvacRatioPercent <= 100, "Error"); + const size_t ratio_of_old_in_collection_set = (maximum_young_evacuation_reserve * ShenandoahOldEvacRatioPercent) / (100 - ShenandoahOldEvacRatioPercent); const size_t old_available = old_generation->available(); - const size_t maximum_old_evacuation_reserve = (ShenandoahOldEvacRatioPercent == 100) ? - old_available : MIN2((maximum_young_evacuation_reserve * ShenandoahOldEvacRatioPercent) / (100 - ShenandoahOldEvacRatioPercent), - old_available); - log_info(gc, ergo)("max_old_evac_reserve: %zu", maximum_old_evacuation_reserve); + const size_t maximum_old_evacuation_reserve = (ShenandoahOldEvacRatioPercent == 100) ? old_available : MIN2(ratio_of_old_in_collection_set, old_available); + log_info(gc, ergo)("max_old_evac_reserve: %zu, old_available: %zu", maximum_old_evacuation_reserve, old_available); // Second priority is to reclaim garbage out of old-gen if there are old-gen collection candidates. Third priority // is to promote as much as we have room to promote. However, if old-gen memory is in short supply, this means young @@ -354,8 +353,8 @@ void ShenandoahGeneration::compute_evacuation_budgets(ShenandoahHeap* const heap const size_t consumed_by_advance_promotion = select_aged_regions(old_promo_reserve); assert(consumed_by_advance_promotion <= maximum_old_evacuation_reserve, "Cannot promote more than available old-gen memory"); - ShenandoahAgeCensus* census = ShenandoahGenerationalHeap::cast(heap)->age_census(); - log_info(gc, ergo)("Old gen reserved for live objects in aged regions: %zu, total tenurable population: %zu", + const ShenandoahAgeCensus* census = ShenandoahGenerationalHeap::cast(heap)->age_census(); + log_info(gc, ergo)("Old gen reserved for anticipated promotions: %zu, total tenurable population: %zu", consumed_by_advance_promotion, census->get_tenurable_bytes()); // Note that unused old_promo_reserve might not be entirely consumed_by_advance_promotion. Do not transfer this From 3037ca8a2ba3684085934f0961ba60d0653513c9 Mon Sep 17 00:00:00 2001 From: William Kemper Date: Thu, 11 Sep 2025 11:55:30 -0700 Subject: [PATCH 39/47] Tweak comment --- src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.hpp b/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.hpp index 334b4422b17c0..b0680e2d02f76 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.hpp @@ -75,7 +75,7 @@ class ShenandoahOldGeneration : public ShenandoahGeneration { // objects). This field records the total amount of padding used for such regions. size_t _pad_for_promote_in_place; - // Keep track of the size of promotions that failed. Perhaps we should use this to increase + // Keep track of the number and size of promotions that failed. Perhaps we should use this to increase // the size of the old generation for the next collection cycle. size_t _promotion_failure_count; size_t _promotion_failure_words; From 38cda58790e5b0d963964643fe1d7a7e918693a9 Mon Sep 17 00:00:00 2001 From: William Kemper Date: Thu, 25 Sep 2025 14:45:19 -0700 Subject: [PATCH 40/47] Merge fallout --- src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp | 4 ++-- src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.hpp | 2 ++ src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp index 3fa8987a47a30..62784bda68114 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp @@ -184,9 +184,9 @@ void ShenandoahAgeCensus::update_census(size_t age0_pop) { size_t ShenandoahAgeCensus::get_tenurable_bytes(const uint tenuring_threshold) const { assert(_epoch < MAX_SNAPSHOTS, "Out of bounds"); size_t total = 0; - const AgeTable* pv = _global_age_table[_epoch]; + const AgeTable* pv = _global_age_tables[_epoch]; for (uint i = 0; i < MAX_COHORTS; i++) { - if (i > tenuring_threshold) { + if (i >= tenuring_threshold) { total += pv->sizes[i]; } } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.hpp b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.hpp index 2c5990df7c62b..d4a590a3d89a6 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.hpp @@ -105,6 +105,8 @@ class ShenandoahCollectionSet : public CHeapObj { // Prints a detailed accounting of all regions in the collection set when gc+cset=debug void print_on(outputStream* out) const; + + // Prints a summary of the collection set when gc+ergo=info void summarize(size_t total_garbage, size_t immediate_garbage, size_t immediate_regions) const; // Returns the amount of live bytes in young regions in the collection set. It is not known how many of these bytes will be promoted. diff --git a/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp b/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp index adeaff5890095..8bd59beb93b7a 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp @@ -410,7 +410,7 @@ \ product(bool, ShenandoahEvacTracking, false, DIAGNOSTIC, \ "Collect additional metrics about evacuations. Enabling this " \ - "track how many objects and how many bytes were evacuated, and " \ + "tracks how many objects and how many bytes were evacuated, and " \ "how many were abandoned. The information will be categorized " \ "by thread type (worker or mutator) and evacuation type (young, " \ "old, or promotion.") \ From 5809aa52cbd0eb4555452ca482e4e25a717b34a0 Mon Sep 17 00:00:00 2001 From: William Kemper Date: Tue, 30 Sep 2025 17:28:17 -0700 Subject: [PATCH 41/47] More accurate method names for cset fields, consider promotion reserves when computing excess old regions --- .../heuristics/shenandoahOldHeuristics.cpp | 8 +-- .../gc/shenandoah/shenandoahCollectionSet.cpp | 6 +- .../gc/shenandoah/shenandoahCollectionSet.hpp | 8 +-- .../shenandoahCollectionSet.inline.hpp | 6 +- .../gc/shenandoah/shenandoahGeneration.cpp | 58 ++++++++----------- .../share/gc/shenandoah/shenandoahHeap.cpp | 6 +- .../shenandoah/shenandoahThreadLocalData.hpp | 3 - .../share/gc/shenandoah/shenandoahTrace.cpp | 6 +- 8 files changed, 43 insertions(+), 58 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.cpp index 5776dec0a4d0a..915262a666739 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.cpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.cpp @@ -168,8 +168,8 @@ bool ShenandoahOldHeuristics::prime_collection_set(ShenandoahCollectionSet* coll // allocation out of unfragmented memory. Unfragmented memory does not need to account for loss of free. if (live_data_for_evacuation > unfragmented_available) { // There is no room to evacuate this region or any that come after it in within the candidates array. - log_info(gc, ergo)("Not enough unfragmented memory (%zu) to hold evacuees (%zu) from region: (%zu)", - unfragmented_available, live_data_for_evacuation, r->index()); + log_debug(gc, cset)("Not enough unfragmented memory (%zu) to hold evacuees (%zu) from region: (%zu)", + unfragmented_available, live_data_for_evacuation, r->index()); break; } else { unfragmented_available -= live_data_for_evacuation; @@ -188,8 +188,8 @@ bool ShenandoahOldHeuristics::prime_collection_set(ShenandoahCollectionSet* coll } if (evacuation_need > unfragmented_available) { // There is no room to evacuate this region or any that come after it in within the candidates array. - log_info(gc, ergo)("Not enough memory (%zu) to hold evacuees (%zu) from region: (%zu)", - unfragmented_available, live_data_for_evacuation, r->index()); + log_debug(gc, cset)("Not enough memory (%zu) to hold evacuees (%zu) from region: (%zu)", + unfragmented_available, live_data_for_evacuation, r->index()); break; } else { unfragmented_available -= evacuation_need; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.cpp b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.cpp index 745d45ace1e25..23af115dad16a 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.cpp @@ -225,9 +225,9 @@ void ShenandoahCollectionSet::summarize(size_t total_garbage, size_t immediate_g count()); if (garbage() > 0) { - const size_t young_evac_bytes = get_young_bytes_reserved_for_evacuation(); - const size_t promote_evac_bytes = get_young_bytes_to_be_promoted(); - const size_t old_evac_bytes = get_old_bytes_reserved_for_evacuation(); + const size_t young_evac_bytes = get_live_bytes_in_young_regions(); + const size_t promote_evac_bytes = get_live_bytes_in_tenurable_regions(); + const size_t old_evac_bytes = get_live_bytes_in_old_regions(); const size_t total_evac_bytes = young_evac_bytes + promote_evac_bytes + old_evac_bytes; ls.print_cr("Evacuation Targets: " "YOUNG: " PROPERFMT ", " "PROMOTE: " PROPERFMT ", " "OLD: " PROPERFMT ", " "TOTAL: " PROPERFMT, diff --git a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.hpp b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.hpp index d4a590a3d89a6..3880ba9933a4f 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.hpp @@ -110,13 +110,13 @@ class ShenandoahCollectionSet : public CHeapObj { void summarize(size_t total_garbage, size_t immediate_garbage, size_t immediate_regions) const; // Returns the amount of live bytes in young regions in the collection set. It is not known how many of these bytes will be promoted. - inline size_t get_young_bytes_reserved_for_evacuation() const; + inline size_t get_live_bytes_in_young_regions() const; // Returns the amount of live bytes in old regions in the collection set. - inline size_t get_old_bytes_reserved_for_evacuation() const; + inline size_t get_live_bytes_in_old_regions() const; // Returns the amount of live bytes in young regions with an age above the tenuring threshold. - inline size_t get_young_bytes_to_be_promoted() const; + inline size_t get_live_bytes_in_tenurable_regions() const; // Returns the amount of free bytes in young regions in the collection set. size_t get_young_available_bytes_collected() const { return _young_available_bytes_collected; } @@ -125,7 +125,7 @@ class ShenandoahCollectionSet : public CHeapObj { inline size_t get_old_garbage() const; bool is_preselected(size_t region_idx) { - assert(_preselected_regions != nullptr, "Missing etsablish after abandon"); + assert(_preselected_regions != nullptr, "Missing establish after abandon"); return _preselected_regions[region_idx]; } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.inline.hpp index 4adcec4fbb552..f1ed852a2d917 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.inline.hpp @@ -54,15 +54,15 @@ bool ShenandoahCollectionSet::is_in_loc(void* p) const { return _biased_cset_map[index] == 1; } -size_t ShenandoahCollectionSet::get_old_bytes_reserved_for_evacuation() const { +size_t ShenandoahCollectionSet::get_live_bytes_in_old_regions() const { return _old_bytes_to_evacuate; } -size_t ShenandoahCollectionSet::get_young_bytes_reserved_for_evacuation() const { +size_t ShenandoahCollectionSet::get_live_bytes_in_young_regions() const { return _young_bytes_to_evacuate - _young_bytes_to_promote; } -size_t ShenandoahCollectionSet::get_young_bytes_to_be_promoted() const { +size_t ShenandoahCollectionSet::get_live_bytes_in_tenurable_regions() const { return _young_bytes_to_promote; } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp index e4bfbc2c3d1fb..3efb900f51828 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp @@ -273,7 +273,6 @@ void ShenandoahGeneration::compute_evacuation_budgets(ShenandoahHeap* const heap // maximum_young_evacuation_reserve is upper bound on memory to be evacuated out of young const size_t maximum_young_evacuation_reserve = (young_generation->max_capacity() * ShenandoahEvacReserve) / 100; const size_t young_evacuation_reserve = MIN2(maximum_young_evacuation_reserve, young_generation->available_with_reserve()); - log_info(gc, ergo)("max_young_evac_reserve: %zu", maximum_young_evacuation_reserve); // maximum_old_evacuation_reserve is an upper bound on memory evacuated from old and evacuated to old (promoted), // clamped by the old generation space available. @@ -295,7 +294,8 @@ void ShenandoahGeneration::compute_evacuation_budgets(ShenandoahHeap* const heap const size_t ratio_of_old_in_collection_set = (maximum_young_evacuation_reserve * ShenandoahOldEvacRatioPercent) / (100 - ShenandoahOldEvacRatioPercent); const size_t old_available = old_generation->available(); const size_t maximum_old_evacuation_reserve = (ShenandoahOldEvacRatioPercent == 100) ? old_available : MIN2(ratio_of_old_in_collection_set, old_available); - log_info(gc, ergo)("max_old_evac_reserve: %zu, old_available: %zu", maximum_old_evacuation_reserve, old_available); + log_debug(gc, cset)("max_young_evac_reserver: %zu, max_old_evac_reserve: %zu, old_available: %zu", + maximum_young_evacuation_reserve, maximum_old_evacuation_reserve, old_available); // Second priority is to reclaim garbage out of old-gen if there are old-gen collection candidates. Third priority // is to promote as much as we have room to promote. However, if old-gen memory is in short supply, this means young @@ -353,8 +353,8 @@ void ShenandoahGeneration::compute_evacuation_budgets(ShenandoahHeap* const heap assert(consumed_by_advance_promotion <= maximum_old_evacuation_reserve, "Cannot promote more than available old-gen memory"); const ShenandoahAgeCensus* census = ShenandoahGenerationalHeap::cast(heap)->age_census(); - log_info(gc, ergo)("Old gen reserved for anticipated promotions: %zu, total tenurable population: %zu", - consumed_by_advance_promotion, census->get_tenurable_bytes()); + log_debug(gc, cset)("Old gen reserved for anticipated promotions: %zu, total tenurable population: %zu", + consumed_by_advance_promotion, census->get_tenurable_bytes()); // Note that unused old_promo_reserve might not be entirely consumed_by_advance_promotion. Do not transfer this // to old_evacuation_reserve because this memory is likely very fragmented, and we do not want to increase the likelihood @@ -391,7 +391,7 @@ void ShenandoahGeneration::adjust_evacuation_budgets(ShenandoahHeap* const heap, ShenandoahOldGeneration* const old_generation = heap->old_generation(); ShenandoahYoungGeneration* const young_generation = heap->young_generation(); - const size_t old_evacuated = collection_set->get_old_bytes_reserved_for_evacuation(); + 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_evacuation_reserve = old_generation->get_evacuation_reserve(); @@ -409,35 +409,20 @@ void ShenandoahGeneration::adjust_evacuation_budgets(ShenandoahHeap* const heap, old_generation->set_evacuation_reserve(old_evacuation_reserve); } - // This is only the young bytes in aged regions. It represents a _floor_ on the amount of data we actually have to promote - // out of the collection set. In reality, it may be much higher. We previously attempted to reserved enough to hold - // all objects above the tenuring threshold. Let's stick with that. The risk is we promote _too_ much, but we have - // faith in the tenuring algorithm. - // TODO: Keep this in, but consider using established promotion reserve instead of what is in aged regions added to collection set. - // const size_t young_advance_promoted = collection_set->get_young_bytes_to_be_promoted(); - // size_t young_advance_promoted_reserve_used = (size_t) (ShenandoahPromoEvacWaste * double(young_advance_promoted)); - const size_t promoted_reserve = ShenandoahHeap::heap()->old_generation()->get_promoted_reserve(); - - const size_t young_evacuated = collection_set->get_young_bytes_reserved_for_evacuation(); + const size_t young_evacuated = collection_set->get_live_bytes_in_young_regions(); const size_t young_evacuated_reserve_used = (size_t) (ShenandoahEvacWaste * double(young_evacuated)); - const size_t total_young_available = young_generation->available_with_reserve(); assert(young_evacuated_reserve_used <= total_young_available, "Cannot evacuate more than is available in young"); young_generation->set_evacuation_reserve(young_evacuated_reserve_used); - const size_t old_available = old_generation->available(); // Now that we've established the collection set, we know how much memory is really required by old-gen for evacuation // and promotion reserves. Try shrinking OLD now in case that gives us a bit more runway for mutator allocations during // evac and update phases. - size_t old_consumed = old_evacuated_committed + promoted_reserve; - - if (old_available < old_consumed) { - // TODO: Set promotion reserve to zero? - log_info(gc, ergo)("Old has consumed more than available, truncate young promo reserve"); - } + const size_t old_available = old_generation->available(); + const size_t promoted_reserve = old_generation->get_promoted_reserve(); + const size_t old_consumed = old_evacuated_committed + promoted_reserve; assert(old_available >= old_consumed, "Cannot consume (%zu) more than is available (%zu)", old_consumed, old_available); - // TODO: We want to reserve more of old for unknown promotions (i.e., tenurable objects in untenurable regions) size_t excess_old = old_available - old_consumed; const size_t unaffiliated_old_regions = old_generation->free_unaffiliated_regions(); const size_t unaffiliated_old = unaffiliated_old_regions * region_size_bytes; @@ -445,13 +430,13 @@ void ShenandoahGeneration::adjust_evacuation_budgets(ShenandoahHeap* const heap, log_info(gc, ergo)("excess_old is: %zu, unaffiliated_old_regions is: %zu", excess_old, unaffiliated_old_regions); // Make sure old_evac_committed is unaffiliated - if (old_evacuated_committed > 0) { - if (unaffiliated_old > old_evacuated_committed) { - const size_t giveaway = unaffiliated_old - old_evacuated_committed - promoted_reserve; + if (old_consumed > 0) { + if (unaffiliated_old > old_consumed) { + const size_t giveaway = unaffiliated_old - old_consumed; const size_t giveaway_regions = giveaway / region_size_bytes; // round down if (giveaway_regions > 0) { excess_old = MIN2(excess_old, giveaway_regions * region_size_bytes); - log_info(gc, ergo)("Changed excess_old to: %zu", excess_old); + log_debug(gc, cset)("Changed excess_old to: %zu", excess_old); } else { excess_old = 0; } @@ -475,8 +460,8 @@ void ShenandoahGeneration::adjust_evacuation_budgets(ShenandoahHeap* const heap, } if (regions_to_xfer > 0) { - log_info(gc,ergo)("Giving away %zu old regions (old_available, %zu, old_evac: %zu, unaffiliated_old: %zu)", - regions_to_xfer, old_available, old_evacuated_committed, unaffiliated_old); + log_debug(gc, cset)("Giving away %zu old regions (old_available, %zu, old_evac: %zu, unaffiliated_old: %zu)", + regions_to_xfer, old_available, old_evacuated_committed, unaffiliated_old); bool result = ShenandoahGenerationalHeap::cast(heap)->generation_sizer()->transfer_to_young(regions_to_xfer); assert(excess_old >= regions_to_xfer * region_size_bytes, "Cannot transfer (%zu, %zu) more than excess old (%zu)", @@ -572,7 +557,7 @@ size_t ShenandoahGeneration::select_aged_regions(const size_t old_promotion_rese } if (heap->is_tenurable(r)) { if ((r->garbage() < old_garbage_threshold) && (r->used() > pip_used_threshold)) { - // We prefer to promote this region in place because is has a small amount of garbage and a large usage. + // We prefer to promote this region in place because it has a small amount of garbage and a large usage. HeapWord* tams = ctx->top_at_mark_start(r); HeapWord* original_top = r->top(); if (!heap->is_concurrent_old_mark_in_progress() && tams == original_top) { @@ -644,8 +629,8 @@ size_t ShenandoahGeneration::select_aged_regions(const size_t old_promotion_rese QuickSort::sort(sorted_regions, candidates, compare_by_aged_live); for (size_t i = 0; i < candidates; i++) { ShenandoahHeapRegion* const region = sorted_regions[i]._region; - size_t region_live_data = sorted_regions[i]._live_data; - size_t promotion_need = (size_t) (region_live_data * ShenandoahPromoEvacWaste); + const size_t region_live_data = sorted_regions[i]._live_data; + const size_t promotion_need = (size_t) (region_live_data * ShenandoahPromoEvacWaste); if (old_consumed + promotion_need <= old_promotion_reserve) { old_consumed += promotion_need; candidate_regions_for_promotion_by_copy[region->index()] = true; @@ -674,8 +659,11 @@ size_t ShenandoahGeneration::select_aged_regions(const size_t old_promotion_rese heap->old_generation()->set_pad_for_promote_in_place(promote_in_place_pad); heap->old_generation()->set_promotion_potential(tenurable_next_cycle); - old_consumed = MAX2(old_consumed, tenurable_this_cycle); - return MIN2(old_consumed, old_promotion_reserve); + + assert(old_consumed <= old_promotion_reserve, "Consumed more than we reserved"); + assert(old_consumed <= tenurable_this_cycle, "Promotions from tenurable regions exceeds all tenurable objects"); + + return MIN2(tenurable_this_cycle, old_promotion_reserve); } void ShenandoahGeneration::prepare_regions_and_collection_set(bool concurrent) { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp index 872a790c02d7d..9900a1e33d092 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp @@ -1276,9 +1276,9 @@ void ShenandoahHeap::concurrent_prepare_for_update_refs() { set_gc_state_concurrent(UPDATE_REFS, true); } - log_info(gc, ergo)("Evacuation complete, promotions expended: %zu, failed count: %zu, failed bytes: %zu", - old_generation()->get_promoted_expended(), old_generation()->get_promotion_failed_count(), - old_generation()->get_promotion_failed_words() * HeapWordSize); + log_info(gc, cset)("Evacuation complete, promotions expended: %zu, failed count: %zu, failed bytes: %zu", + old_generation()->get_promoted_expended(), old_generation()->get_promotion_failed_count(), + old_generation()->get_promotion_failed_words() * HeapWordSize); // This will propagate the gc state and retire gclabs and plabs for threads that require it. ShenandoahPrepareForUpdateRefsHandshakeClosure prepare_for_update_refs(_gc_state.raw_value()); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.hpp b/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.hpp index 571eebf80bce1..f54a65b078507 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.hpp @@ -38,7 +38,6 @@ #include "gc/shenandoah/shenandoahGenerationalHeap.hpp" #include "gc/shenandoah/shenandoahSATBMarkQueueSet.hpp" #include "runtime/javaThread.hpp" -#include "runtime/osThread.hpp" #include "utilities/debug.hpp" #include "utilities/sizes.hpp" @@ -196,12 +195,10 @@ class ShenandoahThreadLocalData { } static void enable_plab_promotions(Thread* thread) { - log_develop_trace(gc, plab)("Enable PLAB promotions for thread: %d (java? %s)", thread->osthread()->thread_id(), BOOL_TO_STR(thread->is_Java_thread())); data(thread)->_plab_allows_promotion = true; } static void disable_plab_promotions(Thread* thread) { - log_develop_trace(gc, plab)("Disable PLAB promotions for thread: %d", thread->osthread()->thread_id()); data(thread)->_plab_allows_promotion = false; } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahTrace.cpp b/src/hotspot/share/gc/shenandoah/shenandoahTrace.cpp index a786f8ae216b3..7ae4135706aee 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahTrace.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahTrace.cpp @@ -37,9 +37,9 @@ void ShenandoahTracer::report_evacuation_info(const ShenandoahCollectionSet* cse e.set_cSetRegions(cset->count()); e.set_cSetUsedBefore(cset->used()); e.set_cSetUsedAfter(cset->live()); - e.set_collectedOld(cset->get_old_bytes_reserved_for_evacuation()); - e.set_collectedPromoted(cset->get_young_bytes_to_be_promoted()); - e.set_collectedYoung(cset->get_young_bytes_reserved_for_evacuation()); + e.set_collectedOld(cset->get_live_bytes_in_old_regions()); + e.set_collectedPromoted(cset->get_live_bytes_in_tenurable_regions()); + e.set_collectedYoung(cset->get_live_bytes_in_young_regions()); e.set_regionsPromotedHumongous(regions_promoted_humongous); e.set_regionsPromotedRegular(regions_promoted_regular); e.set_regularPromotedGarbage(regular_promoted_garbage); From 35e04177cc4bb16d6b67835839d0799dddbcc954 Mon Sep 17 00:00:00 2001 From: William Kemper Date: Wed, 1 Oct 2025 11:39:00 -0700 Subject: [PATCH 42/47] Only use old generation in generational mode --- .../share/gc/shenandoah/shenandoahGenerationalHeap.cpp | 4 ++++ src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp index f9e9a9ce53109..16114d8a4bf91 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp @@ -1109,6 +1109,10 @@ void ShenandoahGenerationalHeap::complete_concurrent_cycle() { entry_global_coalesce_and_fill(); } + log_info(gc, cset)("Concurrent cycle complete, promotions expended: %zu, failed count: %zu, failed bytes: %zu", + old_generation()->get_promoted_expended(), old_generation()->get_promotion_failed_count(), + old_generation()->get_promotion_failed_words() * HeapWordSize); + TransferResult result; { ShenandoahHeapLocker locker(lock()); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp index 9900a1e33d092..1ee7af0b3b819 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp @@ -1276,10 +1276,6 @@ void ShenandoahHeap::concurrent_prepare_for_update_refs() { set_gc_state_concurrent(UPDATE_REFS, true); } - log_info(gc, cset)("Evacuation complete, promotions expended: %zu, failed count: %zu, failed bytes: %zu", - old_generation()->get_promoted_expended(), old_generation()->get_promotion_failed_count(), - old_generation()->get_promotion_failed_words() * HeapWordSize); - // This will propagate the gc state and retire gclabs and plabs for threads that require it. ShenandoahPrepareForUpdateRefsHandshakeClosure prepare_for_update_refs(_gc_state.raw_value()); From 1f757d172247b10dbb30a874d78821f60af23760 Mon Sep 17 00:00:00 2001 From: William Kemper Date: Wed, 1 Oct 2025 13:47:40 -0700 Subject: [PATCH 43/47] Fix wrong asserts --- .../share/gc/shenandoah/shenandoahGeneration.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp index 3efb900f51828..0b0859baede9f 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp @@ -460,12 +460,12 @@ void ShenandoahGeneration::adjust_evacuation_budgets(ShenandoahHeap* const heap, } if (regions_to_xfer > 0) { - log_debug(gc, cset)("Giving away %zu old regions (old_available, %zu, old_evac: %zu, unaffiliated_old: %zu)", - regions_to_xfer, old_available, old_evacuated_committed, unaffiliated_old); - bool result = ShenandoahGenerationalHeap::cast(heap)->generation_sizer()->transfer_to_young(regions_to_xfer); assert(excess_old >= regions_to_xfer * region_size_bytes, "Cannot transfer (%zu, %zu) more than excess old (%zu)", regions_to_xfer, region_size_bytes, excess_old); + log_debug(gc, cset)("Giving away %zu old regions (old_available, %zu, old_evac: %zu, unaffiliated_old: %zu)", + regions_to_xfer, old_available, old_evacuated_committed, unaffiliated_old); + bool result = ShenandoahGenerationalHeap::cast(heap)->generation_sizer()->transfer_to_young(regions_to_xfer); excess_old -= regions_to_xfer * region_size_bytes; log_debug(gc, ergo)("%s transferred %zu excess regions to young before start of evacuation", result? "Successfully": "Unsuccessfully", regions_to_xfer); @@ -473,9 +473,9 @@ void ShenandoahGeneration::adjust_evacuation_budgets(ShenandoahHeap* const heap, // Add in the excess_old memory to hold unanticipated promotions, if any. If there are more unanticipated // promotions than fit in reserved memory, they will be deferred until a future GC pass. - const size_t total_promotion_reserve = excess_old; + const size_t total_promotion_reserve = old_consumed + excess_old; log_info(gc, ergo)("Not changing promotion reserve from %zu to %zu (old excess: %zu)", - old_generation->get_promoted_reserve(), total_promotion_reserve, excess_old); + old_generation->get_promoted_reserve(), total_promotion_reserve, excess_old); // old_generation->set_promoted_reserve(total_promotion_reserve); old_generation->reset_promoted_expended(); } @@ -660,9 +660,10 @@ size_t ShenandoahGeneration::select_aged_regions(const size_t old_promotion_rese heap->old_generation()->set_pad_for_promote_in_place(promote_in_place_pad); heap->old_generation()->set_promotion_potential(tenurable_next_cycle); - assert(old_consumed <= old_promotion_reserve, "Consumed more than we reserved"); - assert(old_consumed <= tenurable_this_cycle, "Promotions from tenurable regions exceeds all tenurable objects"); + assert(old_consumed <= old_promotion_reserve, "Consumed more (%zu) than we reserved (%zu)", old_consumed, old_promotion_reserve); + // old_consumed may exceed tenurable_this_cycle because it has been scaled by ShenandoahPromoEvacWaste. + old_consumed = MAX2(old_consumed, tenurable_this_cycle); return MIN2(tenurable_this_cycle, old_promotion_reserve); } From fd9619dd344d2d564d89925b69ca46cf63168f95 Mon Sep 17 00:00:00 2001 From: William Kemper Date: Thu, 2 Oct 2025 15:26:20 -0700 Subject: [PATCH 44/47] Little cleanup --- .../heuristics/shenandoahOldHeuristics.cpp | 8 +++++--- .../gc/shenandoah/shenandoahAgeCensus.cpp | 19 ++++--------------- .../gc/shenandoah/shenandoahGeneration.cpp | 16 +++------------- .../shenandoah/shenandoahGenerationalHeap.cpp | 12 ++++++------ .../share/gc/shenandoah/shenandoahOldGC.cpp | 3 +-- 5 files changed, 19 insertions(+), 39 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.cpp index 915262a666739..e963bcc35bb47 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.cpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.cpp @@ -102,11 +102,13 @@ bool ShenandoahOldHeuristics::prime_collection_set(ShenandoahCollectionSet* coll const size_t old_evacuation_reserve = _old_generation->get_evacuation_reserve(); const size_t old_evacuation_budget = (size_t) ((double) old_evacuation_reserve / ShenandoahOldEvacWaste); size_t unfragmented_available = _old_generation->free_unaffiliated_regions() * ShenandoahHeapRegion::region_size_bytes(); - size_t fragmented_available = 0; - size_t excess_fragmented_available = 0; + size_t fragmented_available; + size_t excess_fragmented_available; if (unfragmented_available > old_evacuation_budget) { unfragmented_available = old_evacuation_budget; + fragmented_available = 0; + excess_fragmented_available = 0; } else { assert(_old_generation->available() >= old_evacuation_budget, "Cannot budget more than is available"); fragmented_available = _old_generation->available() - unfragmented_available; @@ -188,7 +190,7 @@ bool ShenandoahOldHeuristics::prime_collection_set(ShenandoahCollectionSet* coll } if (evacuation_need > unfragmented_available) { // There is no room to evacuate this region or any that come after it in within the candidates array. - log_debug(gc, cset)("Not enough memory (%zu) to hold evacuees (%zu) from region: (%zu)", + log_debug(gc, cset)("Not enough unfragmented memory (%zu) to hold evacuees (%zu) from region: (%zu)", unfragmented_available, live_data_for_evacuation, r->index()); break; } else { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp index 62784bda68114..c1a0521c58135 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp @@ -386,33 +386,22 @@ void ShenandoahAgeCensus::print() { const uint tt = tenuring_threshold(); - size_t total_tenurable = 0; size_t total= 0; for (uint i = 1; i < MAX_COHORTS; i++) { const size_t prev_pop = prev_pv->sizes[i-1]; // (i-1) OK because i >= 1 const size_t cur_pop = cur_pv->sizes[i]; const double mr = mortality_rate(prev_pop, cur_pop); - // Suppress printing when everything is zero if (prev_pop + cur_pop > 0) { ls.print_cr(" - age %3u: prev %10zu bytes, curr %10zu bytes, mortality %.2f ", i, prev_pop * oopSize, cur_pop * oopSize, mr); } - - if (i >= tt) { - if (i == tt) { - // Underline the cohort for tenuring threshold (if < MAX_COHORTS) - ls.print_cr("----------------------------------------------------------------------------"); - } - total_tenurable += cur_pop; - } - total += cur_pop; + if (i == tt) { + // Underline the cohort for tenuring threshold (if < MAX_COHORTS) + ls.print_cr("----------------------------------------------------------------------------"); + } } - - log_info(gc, age)("%.3f of population meets tenuring threshold (%u). Total: (%zu), Tenurable: (%zu)", - double(total_tenurable) / double(MAX2(total, 1UL)), tt, total, total_tenurable); - CENSUS_NOISE(_global_noise[cur_epoch].print(ls, total);) } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp index 0b0859baede9f..b104d45f79d0f 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp @@ -352,10 +352,6 @@ void ShenandoahGeneration::compute_evacuation_budgets(ShenandoahHeap* const heap const size_t consumed_by_advance_promotion = select_aged_regions(old_promo_reserve); assert(consumed_by_advance_promotion <= maximum_old_evacuation_reserve, "Cannot promote more than available old-gen memory"); - const ShenandoahAgeCensus* census = ShenandoahGenerationalHeap::cast(heap)->age_census(); - log_debug(gc, cset)("Old gen reserved for anticipated promotions: %zu, total tenurable population: %zu", - consumed_by_advance_promotion, census->get_tenurable_bytes()); - // Note that unused old_promo_reserve might not be entirely consumed_by_advance_promotion. Do not transfer this // to old_evacuation_reserve because this memory is likely very fragmented, and we do not want to increase the likelihood // of old evacuation failure. @@ -404,7 +400,7 @@ void ShenandoahGeneration::adjust_evacuation_budgets(ShenandoahHeap* const heap, // Leave old_evac_reserve as previously configured } else if (old_evacuated_committed < old_evacuation_reserve) { // This happens if the old-gen collection consumes less than full budget. - log_info(gc, ergo)("Shrinking old evac reserve to match old_evac_commited: %zu", old_evacuated_committed); + log_debug(gc, cset)("Shrinking old evac reserve to match old_evac_commited: %zu", old_evacuated_committed); old_evacuation_reserve = old_evacuated_committed; old_generation->set_evacuation_reserve(old_evacuation_reserve); } @@ -427,7 +423,7 @@ void ShenandoahGeneration::adjust_evacuation_budgets(ShenandoahHeap* const heap, const size_t unaffiliated_old_regions = old_generation->free_unaffiliated_regions(); const size_t unaffiliated_old = unaffiliated_old_regions * region_size_bytes; assert(old_available >= unaffiliated_old, "Unaffiliated old is a subset of old available"); - log_info(gc, ergo)("excess_old is: %zu, unaffiliated_old_regions is: %zu", excess_old, unaffiliated_old_regions); + log_debug(gc, cset)("excess_old is: %zu, unaffiliated_old_regions is: %zu", excess_old, unaffiliated_old_regions); // Make sure old_evac_committed is unaffiliated if (old_consumed > 0) { @@ -463,7 +459,7 @@ void ShenandoahGeneration::adjust_evacuation_budgets(ShenandoahHeap* const heap, assert(excess_old >= regions_to_xfer * region_size_bytes, "Cannot transfer (%zu, %zu) more than excess old (%zu)", regions_to_xfer, region_size_bytes, excess_old); - log_debug(gc, cset)("Giving away %zu old regions (old_available, %zu, old_evac: %zu, unaffiliated_old: %zu)", + log_debug(gc, ergo)("Giving away %zu old regions (old_available, %zu, old_evac: %zu, unaffiliated_old: %zu)", regions_to_xfer, old_available, old_evacuated_committed, unaffiliated_old); bool result = ShenandoahGenerationalHeap::cast(heap)->generation_sizer()->transfer_to_young(regions_to_xfer); excess_old -= regions_to_xfer * region_size_bytes; @@ -471,12 +467,6 @@ void ShenandoahGeneration::adjust_evacuation_budgets(ShenandoahHeap* const heap, result? "Successfully": "Unsuccessfully", regions_to_xfer); } - // Add in the excess_old memory to hold unanticipated promotions, if any. If there are more unanticipated - // promotions than fit in reserved memory, they will be deferred until a future GC pass. - const size_t total_promotion_reserve = old_consumed + excess_old; - log_info(gc, ergo)("Not changing promotion reserve from %zu to %zu (old excess: %zu)", - old_generation->get_promoted_reserve(), total_promotion_reserve, excess_old); - // old_generation->set_promoted_reserve(total_promotion_reserve); old_generation->reset_promoted_expended(); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp index 16114d8a4bf91..f1d2ba4c83801 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp @@ -291,14 +291,14 @@ oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, Shena if (copy == nullptr) { // If we failed to allocate in LAB, we'll try a shared allocation. - if (!is_promotion || !has_plab || (size > PLAB::min_size())) { + if (!is_promotion || !has_plab || (size > PLAB::max_size())) { ShenandoahAllocRequest req = ShenandoahAllocRequest::for_shared_gc(size, target_gen, is_promotion); copy = allocate_memory(req); alloc_from_lab = false; if (is_promotion && copy != nullptr) { log_debug(gc, plab)("Made a shared promotion of size: %zu, actual PLAB size for thread: %zu, min PLAB: %zu, max PLAB: %zu", - size * HeapWordSize, ShenandoahThreadLocalData::get_plab_actual_size(thread) * HeapWordSize, - PLAB::min_size() * HeapWordSize, plab_max_size() * HeapWordSize); + size * HeapWordSize, ShenandoahThreadLocalData::get_plab_actual_size(thread) * HeapWordSize, + PLAB::min_size() * HeapWordSize, plab_max_size() * HeapWordSize); } } // else, we leave copy equal to nullptr, signaling a promotion failure below if appropriate. @@ -1109,9 +1109,9 @@ void ShenandoahGenerationalHeap::complete_concurrent_cycle() { entry_global_coalesce_and_fill(); } - log_info(gc, cset)("Concurrent cycle complete, promotions expended: %zu, failed count: %zu, failed bytes: %zu", - old_generation()->get_promoted_expended(), old_generation()->get_promotion_failed_count(), - old_generation()->get_promotion_failed_words() * HeapWordSize); + log_info(gc, cset)("Concurrent cycle complete, promotions reserved: %zu, promotions expended: %zu, failed count: %zu, failed bytes: %zu", + old_generation()->get_promoted_reserve(), old_generation()->get_promoted_expended(), + old_generation()->get_promotion_failed_count(), old_generation()->get_promotion_failed_words() * HeapWordSize); TransferResult result; { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahOldGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahOldGC.cpp index 2662705d0e535..707c2690b5818 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahOldGC.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahOldGC.cpp @@ -140,8 +140,7 @@ bool ShenandoahOldGC::collect(GCCause::Cause cause) { // We do not rebuild_free following increments of old marking because memory has not been reclaimed. However, we may // need to transfer memory to OLD in order to efficiently support the mixed evacuations that might immediately follow. - log_info(gc, ergo)("Updating generation sizes at end of old mark, anticipated promotions: %zub", _old_generation->get_promotion_potential()); - const size_t allocation_runway = heap->young_generation()->heuristics()->bytes_of_allocation_runway_before_gc_trigger(0); + size_t allocation_runway = heap->young_generation()->heuristics()->bytes_of_allocation_runway_before_gc_trigger(0); heap->compute_old_generation_balance(allocation_runway, 0); ShenandoahGenerationalHeap::TransferResult result; From 8fca0b680d22e25f150a76d13dcf127343c804f9 Mon Sep 17 00:00:00 2001 From: William Kemper Date: Mon, 6 Oct 2025 07:47:52 -0700 Subject: [PATCH 45/47] Fix windows build --- src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.cpp b/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.cpp index d748f3672afff..176dec5e79082 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.cpp @@ -242,9 +242,9 @@ void ShenandoahOldGeneration::augment_promoted_reserve(size_t increment) { void ShenandoahOldGeneration::reset_promoted_expended() { shenandoah_assert_heaplocked_or_safepoint(); - AtomicAccess::store(&_promoted_expended, 0UL); - AtomicAccess::store(&_promotion_failure_count, 0UL); - AtomicAccess::store(&_promotion_failure_words, 0UL); + AtomicAccess::store(&_promoted_expended, static_cast(0)); + AtomicAccess::store(&_promotion_failure_count, static_cast(0)); + AtomicAccess::store(&_promotion_failure_words, static_cast(0)); } size_t ShenandoahOldGeneration::expend_promoted(size_t increment) { From 09926eb26b2c533d1e3e0b6e86eb5cd53322c566 Mon Sep 17 00:00:00 2001 From: William Kemper Date: Mon, 6 Oct 2025 08:31:09 -0700 Subject: [PATCH 46/47] Fix windows build more --- src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp index b104d45f79d0f..b7671960a9846 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp @@ -640,7 +640,7 @@ size_t ShenandoahGeneration::select_aged_regions(const size_t old_promotion_rese selected_regions, selected_live, old_consumed, old_promotion_reserve); } - const size_t tenuring_threshold = heap->age_census()->tenuring_threshold(); + const uint tenuring_threshold = heap->age_census()->tenuring_threshold(); const size_t tenurable_next_cycle = heap->age_census()->get_tenurable_bytes(tenuring_threshold - 1); const size_t tenurable_this_cycle = heap->age_census()->get_tenurable_bytes(tenuring_threshold); From b4d1cf9020d7c7dd7e6ce3e860cdcadd1a498c5d Mon Sep 17 00:00:00 2001 From: William Kemper Date: Tue, 7 Oct 2025 15:25:58 -0700 Subject: [PATCH 47/47] Review feedback, bug fixes --- .../gc/shenandoah/shenandoahCollectionSet.cpp | 2 +- .../gc/shenandoah/shenandoahCollectionSet.hpp | 4 +- .../shenandoahCollectionSet.inline.hpp | 2 +- .../gc/shenandoah/shenandoahGeneration.cpp | 54 +++++++++++-------- .../shenandoah/shenandoahGenerationalHeap.cpp | 8 +-- .../share/gc/shenandoah/shenandoahTrace.cpp | 2 +- 6 files changed, 40 insertions(+), 32 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.cpp b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.cpp index 23af115dad16a..e58a7f4079608 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.cpp @@ -225,7 +225,7 @@ void ShenandoahCollectionSet::summarize(size_t total_garbage, size_t immediate_g count()); if (garbage() > 0) { - const size_t young_evac_bytes = get_live_bytes_in_young_regions(); + const size_t young_evac_bytes = get_live_bytes_in_untenurable_regions(); const size_t promote_evac_bytes = get_live_bytes_in_tenurable_regions(); const size_t old_evac_bytes = get_live_bytes_in_old_regions(); const size_t total_evac_bytes = young_evac_bytes + promote_evac_bytes + old_evac_bytes; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.hpp b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.hpp index 3880ba9933a4f..415573195f537 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.hpp @@ -109,8 +109,8 @@ class ShenandoahCollectionSet : public CHeapObj { // Prints a summary of the collection set when gc+ergo=info void summarize(size_t total_garbage, size_t immediate_garbage, size_t immediate_regions) const; - // Returns the amount of live bytes in young regions in the collection set. It is not known how many of these bytes will be promoted. - inline size_t get_live_bytes_in_young_regions() const; + // Returns the amount of live bytes in young regions with an age below the tenuring threshold. + inline size_t get_live_bytes_in_untenurable_regions() const; // Returns the amount of live bytes in old regions in the collection set. inline size_t get_live_bytes_in_old_regions() const; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.inline.hpp index f1ed852a2d917..3ff5f2f81d70b 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.inline.hpp @@ -58,7 +58,7 @@ size_t ShenandoahCollectionSet::get_live_bytes_in_old_regions() const { return _old_bytes_to_evacuate; } -size_t ShenandoahCollectionSet::get_live_bytes_in_young_regions() const { +size_t ShenandoahCollectionSet::get_live_bytes_in_untenurable_regions() const { return _young_bytes_to_evacuate - _young_bytes_to_promote; } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp index b7671960a9846..f5f8ee1c584b7 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp @@ -252,6 +252,28 @@ void ShenandoahGeneration::parallel_heap_region_iterate_free(ShenandoahHeapRegio ShenandoahHeap::heap()->parallel_heap_region_iterate(cl); } +// Here's the algebra. +// Let SOEP = ShenandoahOldEvacRatioPercent, +// OE = old evac, +// YE = young evac, and +// TE = total evac = OE + YE +// By definition: +// SOEP/100 = OE/TE +// = OE/(OE+YE) +// => SOEP/(100-SOEP) = OE/((OE+YE)-OE) // componendo-dividendo: If a/b = c/d, then a/(b-a) = c/(d-c) +// = OE/YE +// => OE = YE*SOEP/(100-SOEP) +size_t get_maximum_old_evacuation_reserve(size_t maximum_young_evacuation_reserve, size_t old_available) { + // We have to be careful in the event that SOEP is set to 100 by the user. + assert(ShenandoahOldEvacRatioPercent <= 100, "Error"); + if (ShenandoahOldEvacRatioPercent == 100) { + return old_available; + } + + const size_t ratio_of_old_in_collection_set = (maximum_young_evacuation_reserve * ShenandoahOldEvacRatioPercent) / (100 - ShenandoahOldEvacRatioPercent); + return MIN2(ratio_of_old_in_collection_set, old_available); +} + void ShenandoahGeneration::compute_evacuation_budgets(ShenandoahHeap* const heap) { shenandoah_assert_generational(); @@ -276,24 +298,10 @@ void ShenandoahGeneration::compute_evacuation_budgets(ShenandoahHeap* const heap // maximum_old_evacuation_reserve is an upper bound on memory evacuated from old and evacuated to old (promoted), // clamped by the old generation space available. - // - // Here's the algebra. - // Let SOEP = ShenandoahOldEvacRatioPercent, - // OE = old evac, - // YE = young evac, and - // TE = total evac = OE + YE - // By definition: - // SOEP/100 = OE/TE - // = OE/(OE+YE) - // => SOEP/(100-SOEP) = OE/((OE+YE)-OE) // componendo-dividendo: If a/b = c/d, then a/(b-a) = c/(d-c) - // = OE/YE - // => OE = YE*SOEP/(100-SOEP) - - // We have to be careful in the event that SOEP is set to 100 by the user. - assert(ShenandoahOldEvacRatioPercent <= 100, "Error"); - const size_t ratio_of_old_in_collection_set = (maximum_young_evacuation_reserve * ShenandoahOldEvacRatioPercent) / (100 - ShenandoahOldEvacRatioPercent); const size_t old_available = old_generation->available(); - const size_t maximum_old_evacuation_reserve = (ShenandoahOldEvacRatioPercent == 100) ? old_available : MIN2(ratio_of_old_in_collection_set, old_available); + const size_t maximum_old_evacuation_reserve = get_maximum_old_evacuation_reserve(maximum_young_evacuation_reserve, old_available); + + log_debug(gc, cset)("max_young_evac_reserver: %zu, max_old_evac_reserve: %zu, old_available: %zu", maximum_young_evacuation_reserve, maximum_old_evacuation_reserve, old_available); @@ -351,6 +359,7 @@ void ShenandoahGeneration::compute_evacuation_budgets(ShenandoahHeap* const heap // and identify regions that will promote in place. These use the tenuring threshold. const size_t consumed_by_advance_promotion = select_aged_regions(old_promo_reserve); assert(consumed_by_advance_promotion <= maximum_old_evacuation_reserve, "Cannot promote more than available old-gen memory"); + assert(consumed_by_advance_promotion <= old_promo_reserve, "Cannot promote more than was reserved"); // Note that unused old_promo_reserve might not be entirely consumed_by_advance_promotion. Do not transfer this // to old_evacuation_reserve because this memory is likely very fragmented, and we do not want to increase the likelihood @@ -405,11 +414,10 @@ void ShenandoahGeneration::adjust_evacuation_budgets(ShenandoahHeap* const heap, old_generation->set_evacuation_reserve(old_evacuation_reserve); } - const size_t young_evacuated = collection_set->get_live_bytes_in_young_regions(); - const size_t young_evacuated_reserve_used = (size_t) (ShenandoahEvacWaste * double(young_evacuated)); - const size_t total_young_available = young_generation->available_with_reserve(); - assert(young_evacuated_reserve_used <= total_young_available, "Cannot evacuate more than is available in young"); - young_generation->set_evacuation_reserve(young_evacuated_reserve_used); + const size_t young_evacuated = collection_set->get_live_bytes_in_untenurable_regions(); + const size_t young_evacuated_commited = (size_t) (ShenandoahEvacWaste * double(young_evacuated)); + assert(young_evacuated_commited <= young_generation->available_with_reserve(), "Cannot evacuate more than is available in young"); + young_generation->set_evacuation_reserve(young_evacuated_commited); // Now that we've established the collection set, we know how much memory is really required by old-gen for evacuation // and promotion reserves. Try shrinking OLD now in case that gives us a bit more runway for mutator allocations during @@ -654,7 +662,7 @@ size_t ShenandoahGeneration::select_aged_regions(const size_t old_promotion_rese // old_consumed may exceed tenurable_this_cycle because it has been scaled by ShenandoahPromoEvacWaste. old_consumed = MAX2(old_consumed, tenurable_this_cycle); - return MIN2(tenurable_this_cycle, old_promotion_reserve); + return MIN2(old_consumed, old_promotion_reserve); } void ShenandoahGeneration::prepare_regions_and_collection_set(bool concurrent) { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp index f1d2ba4c83801..f679b3df0b536 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp @@ -110,7 +110,6 @@ void ShenandoahGenerationalHeap::initialize_heuristics() { _generation_sizer.heap_size_changed(max_capacity()); size_t initial_capacity_young = _generation_sizer.max_young_size(); size_t max_capacity_young = _generation_sizer.max_young_size(); - size_t initial_capacity_old = max_capacity() - max_capacity_young; size_t max_capacity_old = max_capacity() - initial_capacity_young; _young_generation = new ShenandoahYoungGeneration(max_workers(), max_capacity_young); @@ -302,9 +301,10 @@ oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, Shena } } // else, we leave copy equal to nullptr, signaling a promotion failure below if appropriate. - // We choose not to promote objects smaller than PLAB::min_size() by way of shared allocations, as this is too - // costly. Instead, we'll simply "evacuate" to young-gen memory (using a GCLAB) and will promote in a future - // evacuation pass. This condition is denoted by: is_promotion && has_plab && (size <= PLAB::min_size()) + // We choose not to promote objects smaller than PLAB::max_size() by way of shared allocations, as this is too + // costly (such objects should use the PLAB). Instead, we'll simply "evacuate" to young-gen memory (using a GCLAB) + // and will promote in a future evacuation pass. This condition is denoted by: is_promotion && has_plab && (size + // <= PLAB::max_size()) } #ifdef ASSERT } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahTrace.cpp b/src/hotspot/share/gc/shenandoah/shenandoahTrace.cpp index 7ae4135706aee..bbb44348355b6 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahTrace.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahTrace.cpp @@ -39,7 +39,7 @@ void ShenandoahTracer::report_evacuation_info(const ShenandoahCollectionSet* cse e.set_cSetUsedAfter(cset->live()); e.set_collectedOld(cset->get_live_bytes_in_old_regions()); e.set_collectedPromoted(cset->get_live_bytes_in_tenurable_regions()); - e.set_collectedYoung(cset->get_live_bytes_in_young_regions()); + e.set_collectedYoung(cset->get_live_bytes_in_untenurable_regions()); e.set_regionsPromotedHumongous(regions_promoted_humongous); e.set_regionsPromotedRegular(regions_promoted_regular); e.set_regularPromotedGarbage(regular_promoted_garbage);