Skip to content

8357471: GenShen: Share collector reserves between young and old #25357

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 38 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
6c90312
Share reserves between old and young
kdnilsen Feb 17, 2025
43c1842
Cleaner separation when asserts enabled
kdnilsen Feb 17, 2025
c097513
Force transfer of old region young at updaterefs
kdnilsen Feb 19, 2025
980110b
Remove debug instrumentation
kdnilsen Feb 19, 2025
7ae16e1
Add some debug instrumentation
kdnilsen Feb 22, 2025
ac39797
Recycle old region on demands adjusts free bounds
kdnilsen Feb 22, 2025
b6ef323
Cancel pending GC trigger at start of old gc
kdnilsen Feb 27, 2025
a8e40e1
Fix white space
kdnilsen Feb 27, 2025
1911e04
Reduce likelihood of promotion by shared allocs
kdnilsen Mar 8, 2025
7ce1120
Merge tag 'jdk-25+13' into share-collector-reserves
kdnilsen Mar 17, 2025
2412337
Add support for adaptive OldEvacRatio
kdnilsen Mar 17, 2025
6ddcc4d
Merge tag 'jdk-25+14' into share-collector-reserves
kdnilsen Mar 17, 2025
437c71f
Fix typo in preprocessor directive
kdnilsen Mar 19, 2025
65eb7b9
Merge remote-tracking branch 'jdk/master' into share-collector-reserves
kdnilsen Mar 20, 2025
77ae0c1
Repair damage resulting from upstream merge
kdnilsen Mar 21, 2025
fa28658
Add asserts and diagnostics to help debug failures
kdnilsen Mar 24, 2025
7e97937
Global cycles need to record cycle start time
kdnilsen Mar 26, 2025
b489f62
Revert "Add support for adaptive OldEvacRatio"
kdnilsen Mar 26, 2025
2aa534a
Acquire heaplock before adjusting interval for old
kdnilsen Apr 7, 2025
85f03c3
Revert "Acquire heaplock before adjusting interval for old"
kdnilsen Apr 7, 2025
143515d
Improve empty region accounting in FreeSet
kdnilsen Apr 8, 2025
be05b38
Fixup bugs introduced by most recent commit
kdnilsen Apr 8, 2025
da883d7
Merge remote-tracking branch 'jdk/master' into share-collector-reserves
kdnilsen May 17, 2025
eda09d5
Revert change inadvertantly applied during merge
kdnilsen May 18, 2025
577b0cb
Refactor to check ShenFreeSet::avaiable() outside global heaplock
kdnilsen May 18, 2025
3cf1b87
Improve empty region accounting in FreeSet
kdnilsen Apr 8, 2025
684b9c2
Fixup bugs introduced by most recent commit
kdnilsen Apr 8, 2025
9a2b972
Merge branch 'share-collector-reserves' of https://github.com/kdnilse…
kdnilsen May 18, 2025
ce897cd
compute_old_generation_balance needs available computations under lock
kdnilsen May 18, 2025
6abbd8b
make old gc more aggresive
kdnilsen May 19, 2025
242bce5
Change fullgc phase5 return type
kdnilsen May 19, 2025
49abfca
Merge branch 'share-collector-reserves' of https://github.com/kdnilse…
kdnilsen May 20, 2025
f0afae0
Merge remote-tracking branch 'jdk/master' into share-collector-reserves
kdnilsen May 20, 2025
3d55a64
Fix whitespace
kdnilsen May 21, 2025
babc04e
Keep gc cycle times with heuristics for the relevant generation
kdnilsen May 23, 2025
ae8c83c
respond to reviewer feedback
kdnilsen May 23, 2025
8ec6ea0
Fix assert_bounds() assertions when old_trash_not_in_bounds
kdnilsen Jun 9, 2025
aea2b92
fix whitespace
kdnilsen Jun 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -175,12 +175,24 @@ void ShenandoahGenerationalHeuristics::choose_collection_set(ShenandoahCollectio
bool doing_promote_in_place = (humongous_regions_promoted + regular_regions_promoted_in_place > 0);
if (doing_promote_in_place || (preselected_candidates > 0) || (immediate_percent <= ShenandoahImmediateThreshold)) {
// Only young collections need to prime the collection set.
bool need_to_finalize_mixed = false;
if (_generation->is_young()) {
heap->old_generation()->heuristics()->prime_collection_set(collection_set);
need_to_finalize_mixed = heap->old_generation()->heuristics()->prime_collection_set(collection_set);
}

// Call the subclasses to add young-gen regions into the collection set.
choose_collection_set_from_regiondata(collection_set, candidates, cand_idx, immediate_garbage + free);

if (_generation->is_young()) {
// Especially when young-gen trigger is expedited in order to finish mixed evacuations, there may not be
// enough consolidated garbage to make effective use of young-gen evacuation reserve. If there is still
// young-gen reserve available following selection of the young-gen collection set, see if we can use
// this memory to expand the old-gen evacuation collection set.
need_to_finalize_mixed |= heap->old_generation()->heuristics()->top_off_collection_set();
if (need_to_finalize_mixed) {
heap->old_generation()->heuristics()->finalize_mixed_evacs();
}
}
}

if (collection_set->has_old_regions()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,93 +53,134 @@ void ShenandoahGlobalHeuristics::choose_global_collection_set(ShenandoahCollecti
size_t cur_young_garbage) const {
auto heap = ShenandoahGenerationalHeap::heap();
size_t region_size_bytes = ShenandoahHeapRegion::region_size_bytes();
size_t capacity = heap->young_generation()->max_capacity();
size_t young_capacity = heap->young_generation()->max_capacity();
size_t old_capacity = heap->old_generation()->max_capacity();
size_t garbage_threshold = region_size_bytes * ShenandoahGarbageThreshold / 100;
size_t ignore_threshold = region_size_bytes * ShenandoahIgnoreGarbageThreshold / 100;
const uint tenuring_threshold = heap->age_census()->tenuring_threshold();

size_t young_evac_reserve = heap->young_generation()->get_evacuation_reserve();
size_t old_evac_reserve = heap->old_generation()->get_evacuation_reserve();
size_t max_young_cset = (size_t) (young_evac_reserve / ShenandoahEvacWaste);
size_t young_cur_cset = 0;
size_t max_old_cset = (size_t) (old_evac_reserve / ShenandoahOldEvacWaste);
size_t old_cur_cset = 0;

// Figure out how many unaffiliated young regions are dedicated to mutator and to evacuator. Allow the young
// collector's unaffiliated regions to be transferred to old-gen if old-gen has more easily reclaimed garbage
// than young-gen. At the end of this cycle, any excess regions remaining in old-gen will be transferred back
// to young. Do not transfer the mutator's unaffiliated regions to old-gen. Those must remain available
// to the mutator as it needs to be able to consume this memory during concurrent GC.

size_t unaffiliated_young_regions = heap->young_generation()->free_unaffiliated_regions();
size_t unaffiliated_young_memory = unaffiliated_young_regions * region_size_bytes;

if (unaffiliated_young_memory > max_young_cset) {
size_t unaffiliated_mutator_memory = unaffiliated_young_memory - max_young_cset;
unaffiliated_young_memory -= unaffiliated_mutator_memory;
unaffiliated_young_regions = unaffiliated_young_memory / region_size_bytes; // round down
unaffiliated_young_memory = unaffiliated_young_regions * region_size_bytes;
size_t unaffiliated_old_regions = heap->old_generation()->free_unaffiliated_regions();
size_t unaffiliated_old_memory = unaffiliated_old_regions * region_size_bytes;

// Figure out how many unaffiliated regions are dedicated to Collector and OldCollector reserves. Let these
// be shuffled between young and old generations in order to expedite evacuation of whichever regions have the
// most garbage, regardless of whether these garbage-first regions reside in young or old generation.
// Excess reserves will be transferred back to the mutator after collection set has been chosen. At the end
// of evacuation, any reserves not consumed by evacuation will also be transferred to the mutator free set.
size_t shared_reserve_regions = 0;
if (young_evac_reserve > unaffiliated_young_memory) {
young_evac_reserve -= unaffiliated_young_memory;
shared_reserve_regions += unaffiliated_young_memory / region_size_bytes;
} else {
size_t delta_regions = young_evac_reserve / region_size_bytes;
shared_reserve_regions += delta_regions;
young_evac_reserve -= delta_regions * region_size_bytes;
}
if (old_evac_reserve > unaffiliated_old_memory) {
old_evac_reserve -= unaffiliated_old_memory;
shared_reserve_regions += unaffiliated_old_memory / region_size_bytes;
} else {
size_t delta_regions = old_evac_reserve / region_size_bytes;
shared_reserve_regions += delta_regions;
old_evac_reserve -= delta_regions * region_size_bytes;
}

// We'll affiliate these unaffiliated regions with either old or young, depending on need.
max_young_cset -= unaffiliated_young_memory;
size_t shared_reserves = shared_reserve_regions * region_size_bytes;
size_t committed_from_shared_reserves = 0;
size_t max_young_cset = (size_t) (young_evac_reserve / ShenandoahEvacWaste);
size_t young_cur_cset = 0;
size_t max_old_cset = (size_t) (old_evac_reserve / ShenandoahOldEvacWaste);
size_t old_cur_cset = 0;

// Keep track of how many regions we plan to transfer from young to old.
size_t regions_transferred_to_old = 0;
size_t promo_bytes = 0;
size_t old_evac_bytes = 0;
size_t young_evac_bytes = 0;

size_t free_target = (capacity * ShenandoahMinFreeThreshold) / 100 + max_young_cset;
size_t max_total_cset = (max_young_cset + max_old_cset +
(size_t) (shared_reserve_regions * region_size_bytes) / ShenandoahOldEvacWaste);
size_t free_target = ((young_capacity + old_capacity) * ShenandoahMinFreeThreshold) / 100 + max_total_cset;
size_t min_garbage = (free_target > actual_free) ? (free_target - actual_free) : 0;

log_info(gc, ergo)("Adaptive CSet Selection for GLOBAL. Max Young Evacuation: %zu"
"%s, Max Old Evacuation: %zu%s, Actual Free: %zu%s.",
"%s, Max Old Evacuation: %zu%s, Discretionary additional evacuation: %zu%s, Actual Free: %zu%s.",
byte_size_in_proper_unit(max_young_cset), proper_unit_for_byte_size(max_young_cset),
byte_size_in_proper_unit(max_old_cset), proper_unit_for_byte_size(max_old_cset),
byte_size_in_proper_unit(shared_reserves), proper_unit_for_byte_size(shared_reserves),
byte_size_in_proper_unit(actual_free), proper_unit_for_byte_size(actual_free));

size_t cur_garbage = cur_young_garbage;
for (size_t idx = 0; idx < size; idx++) {
ShenandoahHeapRegion* r = data[idx].get_region();
assert(!cset->is_preselected(r->index()), "There should be no preselected regions during GLOBAL GC");
bool add_region = false;
size_t region_garbage = r->garbage();
size_t new_garbage = cur_garbage + region_garbage;
bool add_regardless = (region_garbage > ignore_threshold) && (new_garbage < min_garbage);
if (r->is_old() || (r->age() >= tenuring_threshold)) {
size_t new_cset = old_cur_cset + r->get_live_data_bytes();
if ((r->garbage() > garbage_threshold)) {
while ((new_cset > max_old_cset) && (unaffiliated_young_regions > 0)) {
unaffiliated_young_regions--;
regions_transferred_to_old++;
if (add_regardless || (region_garbage > garbage_threshold)) {
size_t live_bytes = r->get_live_data_bytes();
size_t new_cset = old_cur_cset + r->get_live_data_bytes();
// May need multiple reserve regions to evacuate a single region, depending on live data bytes and ShenandoahOldEvacWaste
size_t orig_max_old_cset = max_old_cset;
size_t proposed_old_region_consumption = 0;
while ((new_cset > max_old_cset) && (committed_from_shared_reserves < shared_reserves)) {
committed_from_shared_reserves += region_size_bytes;
proposed_old_region_consumption++;
max_old_cset += region_size_bytes / ShenandoahOldEvacWaste;
}
}
if ((new_cset <= max_old_cset) && (r->garbage() > garbage_threshold)) {
add_region = true;
old_cur_cset = new_cset;
// We already know: add_regardless || region_garbage > garbage_threshold
if (new_cset <= max_old_cset) {
add_region = true;
old_cur_cset = new_cset;
cur_garbage = new_garbage;
if (r->is_old()) {
old_evac_bytes += live_bytes;
} else {
promo_bytes += live_bytes;
}
} else {
// We failed to sufficiently expand old, so unwind proposed expansion
max_old_cset = orig_max_old_cset;
committed_from_shared_reserves -= proposed_old_region_consumption * region_size_bytes;
}
}
} else {
assert(r->is_young() && (r->age() < tenuring_threshold), "DeMorgan's law (assuming r->is_affiliated)");
size_t new_cset = young_cur_cset + r->get_live_data_bytes();
size_t region_garbage = r->garbage();
size_t new_garbage = cur_young_garbage + region_garbage;
bool add_regardless = (region_garbage > ignore_threshold) && (new_garbage < min_garbage);

if (add_regardless || (r->garbage() > garbage_threshold)) {
while ((new_cset > max_young_cset) && (unaffiliated_young_regions > 0)) {
unaffiliated_young_regions--;
if (add_regardless || (region_garbage > garbage_threshold)) {
size_t live_bytes = r->get_live_data_bytes();
size_t new_cset = young_cur_cset + live_bytes;
// May need multiple reserve regions to evacuate a single region, depending on live data bytes and ShenandoahEvacWaste
size_t orig_max_young_cset = max_young_cset;
size_t proposed_young_region_consumption = 0;
while ((new_cset > max_young_cset) && (committed_from_shared_reserves < shared_reserves)) {
committed_from_shared_reserves += region_size_bytes;
proposed_young_region_consumption++;
max_young_cset += region_size_bytes / ShenandoahEvacWaste;
}
}
if ((new_cset <= max_young_cset) && (add_regardless || (region_garbage > garbage_threshold))) {
add_region = true;
young_cur_cset = new_cset;
cur_young_garbage = new_garbage;
// We already know: add_regardless || region_garbage > garbage_threshold
if (new_cset <= max_young_cset) {
add_region = true;
young_cur_cset = new_cset;
cur_garbage = new_garbage;
young_evac_bytes += live_bytes;
} else {
// We failed to sufficiently expand young, so unwind proposed expansion
max_young_cset = orig_max_young_cset;
committed_from_shared_reserves -= proposed_young_region_consumption * region_size_bytes;
}
}
}
if (add_region) {
cset->add_region(r);
}
}

if (regions_transferred_to_old > 0) {
heap->generation_sizer()->force_transfer_to_old(regions_transferred_to_old);
heap->young_generation()->set_evacuation_reserve(young_evac_reserve - regions_transferred_to_old * region_size_bytes);
heap->old_generation()->set_evacuation_reserve(old_evac_reserve + regions_transferred_to_old * region_size_bytes);
}
heap->young_generation()->set_evacuation_reserve((size_t) (young_evac_bytes * ShenandoahEvacWaste));
Copy link
Member

@ysramakrishna ysramakrishna May 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we are using the amount to be evacuated out of young (suitably marked up to account for waste) from the collection set of a specific cycle to predict the same for the next cycle? And similarly for the promotion bytes.

This seems reasonable, but how does that compare with using the live data identified in the most recent marking cycle instead? I can imagine that the former is more accurate under steady state assumptions and the latter is an overestimate to the extent that not all live data will be evacuated because it's in mostly live, i.e. densely live regions. However, it would be interesting to see how they compare and which tracks reality better. Since this is in the nature of a prediction/estimate, once can consider a control algorithm that tries to move the estimate closer based on minimizing some historical deviation between marked vs evacuated.

This need not be done here, but can be considered a future enhancement/experiment.

heap->old_generation()->set_evacuation_reserve((size_t) (old_evac_bytes * ShenandoahOldEvacWaste));
heap->old_generation()->set_promoted_reserve((size_t) (promo_bytes * ShenandoahPromoEvacWaste));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that the census from the most recent mark provides both these bits of information, but doesn't account for other criteria (i.e. liveness denseness) that go into the exclusion of certain regions (and their objects) from the collection set. Therein lies the rub, but armed with historical numbers of each and reality, one might be able to predict this well (may be).

}
Loading