mirror of
https://github.com/openjdk/jdk.git
synced 2026-05-31 07:48:49 +00:00
8377142: Jtreg test gc/shenandoah/oom/TestThreadFailure.java triggers assert(young_reserve + reserve_for_mixed + reserve_for_promo <= old_available + young_available) failed
Reviewed-by: wkemper
This commit is contained in:
parent
871730aa9b
commit
526228ca3f
@ -605,7 +605,8 @@ void ShenandoahGenerationalHeap::compute_old_generation_balance(size_t mutator_x
|
||||
ShenandoahOldGeneration* old_gen = old_generation();
|
||||
size_t old_capacity = old_gen->max_capacity();
|
||||
size_t old_usage = old_gen->used(); // includes humongous waste
|
||||
size_t old_available = ((old_capacity >= old_usage)? old_capacity - old_usage: 0) + old_trashed_regions * region_size_bytes;
|
||||
size_t old_currently_available =
|
||||
((old_capacity >= old_usage)? old_capacity - old_usage: 0) + old_trashed_regions * region_size_bytes;
|
||||
|
||||
ShenandoahYoungGeneration* young_gen = young_generation();
|
||||
size_t young_capacity = young_gen->max_capacity();
|
||||
@ -621,7 +622,8 @@ void ShenandoahGenerationalHeap::compute_old_generation_balance(size_t mutator_x
|
||||
size_t young_reserve = (young_generation()->max_capacity() * ShenandoahEvacReserve) / 100;
|
||||
|
||||
// If ShenandoahOldEvacPercent equals 100, max_old_reserve is limited only by mutator_xfer_limit and young_reserve
|
||||
const size_t bound_on_old_reserve = ((old_available + mutator_xfer_limit + young_reserve) * ShenandoahOldEvacPercent) / 100;
|
||||
const size_t bound_on_old_reserve =
|
||||
((old_currently_available + mutator_xfer_limit + young_reserve) * ShenandoahOldEvacPercent) / 100;
|
||||
size_t proposed_max_old = ((ShenandoahOldEvacPercent == 100)?
|
||||
bound_on_old_reserve:
|
||||
MIN2((young_reserve * ShenandoahOldEvacPercent) / (100 - ShenandoahOldEvacPercent),
|
||||
@ -631,68 +633,105 @@ void ShenandoahGenerationalHeap::compute_old_generation_balance(size_t mutator_x
|
||||
}
|
||||
|
||||
// Decide how much old space we should reserve for a mixed collection
|
||||
size_t reserve_for_mixed = 0;
|
||||
size_t proposed_reserve_for_mixed = 0;
|
||||
const size_t old_fragmented_available =
|
||||
old_available - (old_generation()->free_unaffiliated_regions() + old_trashed_regions) * region_size_bytes;
|
||||
old_currently_available - (old_generation()->free_unaffiliated_regions() + old_trashed_regions) * region_size_bytes;
|
||||
|
||||
if (old_fragmented_available > proposed_max_old) {
|
||||
// After we've promoted regions in place, there may be an abundance of old-fragmented available memory,
|
||||
// even more than the desired percentage for old reserve. We cannot transfer these fragmented regions back
|
||||
// to young. Instead we make the best of the situation by using this fragmented memory for both promotions
|
||||
// and evacuations.
|
||||
// In this case, the old_fragmented_available is greater than the desired amount of evacuation to old.
|
||||
// We'll use all of this memory to hold results of old evacuation, and we'll give back to the young generation
|
||||
// any old regions that are not fragmented.
|
||||
//
|
||||
// This scenario may happen after we have promoted many regions in place, and each of these regions had non-zero
|
||||
// unused memory, so there is now an abundance of old-fragmented available memory, even more than the desired
|
||||
// percentage for old reserve. We cannot transfer these fragmented regions back to young. Instead we make the
|
||||
// best of the situation by using this fragmented memory for both promotions and evacuations.
|
||||
|
||||
proposed_max_old = old_fragmented_available;
|
||||
}
|
||||
size_t reserve_for_promo = old_fragmented_available;
|
||||
// Otherwise: old_fragmented_available <= proposed_max_old. Do not shrink proposed_max_old from the original computation.
|
||||
|
||||
// Though we initially set proposed_reserve_for_promo to equal the entirety of old fragmented available, we have the
|
||||
// opportunity below to shift some of this memory into the proposed_reserve_for_mixed.
|
||||
size_t proposed_reserve_for_promo = old_fragmented_available;
|
||||
const size_t max_old_reserve = proposed_max_old;
|
||||
|
||||
const size_t mixed_candidate_live_memory = old_generation()->unprocessed_collection_candidates_live_memory();
|
||||
const bool doing_mixed = (mixed_candidate_live_memory > 0);
|
||||
if (doing_mixed) {
|
||||
// We want this much memory to be unfragmented in order to reliably evacuate old. This is conservative because we
|
||||
// may not evacuate the entirety of unprocessed candidates in a single mixed evacuation.
|
||||
// In the ideal, all of the memory reserved for mixed evacuation would be unfragmented, but we don't enforce
|
||||
// this. Note that the initial value of max_evac_need is conservative because we may not evacuate all of the
|
||||
// remaining mixed evacuation candidates in a single cycle.
|
||||
const size_t max_evac_need = (size_t) (mixed_candidate_live_memory * ShenandoahOldEvacWaste);
|
||||
assert(old_available >= old_generation()->free_unaffiliated_regions() * region_size_bytes,
|
||||
assert(old_currently_available >= old_generation()->free_unaffiliated_regions() * region_size_bytes,
|
||||
"Unaffiliated available must be less than total available");
|
||||
|
||||
// We prefer to evacuate all of mixed into unfragmented memory, and will expand old in order to do so, unless
|
||||
// we already have too much fragmented available memory in old.
|
||||
reserve_for_mixed = max_evac_need;
|
||||
if (reserve_for_mixed + reserve_for_promo > max_old_reserve) {
|
||||
// In this case, we'll allow old-evac to target some of the fragmented old memory.
|
||||
size_t excess_reserves = (reserve_for_mixed + reserve_for_promo) - max_old_reserve;
|
||||
if (reserve_for_promo > excess_reserves) {
|
||||
reserve_for_promo -= excess_reserves;
|
||||
proposed_reserve_for_mixed = max_evac_need;
|
||||
if (proposed_reserve_for_mixed + proposed_reserve_for_promo > max_old_reserve) {
|
||||
// We're trying to reserve more memory than is available. So we need to shrink our reserves.
|
||||
size_t excess_reserves = (proposed_reserve_for_mixed + proposed_reserve_for_promo) - max_old_reserve;
|
||||
// We need to shrink reserves by excess_reserves. We prefer to shrink by reducing promotion, giving priority to mixed
|
||||
// evacuation. If the promotion reserve is larger than the amount we need to shrink by, do all the shrinkage there.
|
||||
if (proposed_reserve_for_promo > excess_reserves) {
|
||||
proposed_reserve_for_promo -= excess_reserves;
|
||||
} else {
|
||||
excess_reserves -= reserve_for_promo;
|
||||
reserve_for_promo = 0;
|
||||
reserve_for_mixed -= excess_reserves;
|
||||
// Otherwise, we'll shrink promotion reserve to zero and we'll shrink the mixed-evac reserve by the remaining excess.
|
||||
excess_reserves -= proposed_reserve_for_promo;
|
||||
proposed_reserve_for_promo = 0;
|
||||
proposed_reserve_for_mixed -= excess_reserves;
|
||||
}
|
||||
}
|
||||
}
|
||||
assert(proposed_reserve_for_mixed + proposed_reserve_for_promo <= max_old_reserve,
|
||||
"Reserve for mixed (%zu) plus reserve for promotions (%zu) must be less than maximum old reserve (%zu)",
|
||||
proposed_reserve_for_mixed, proposed_reserve_for_promo, max_old_reserve);
|
||||
|
||||
// Decide how much additional space we should reserve for promotions from young. We give priority to mixed evacations
|
||||
// over promotions.
|
||||
const size_t promo_load = old_generation()->get_promotion_potential();
|
||||
const bool doing_promotions = promo_load > 0;
|
||||
if (doing_promotions) {
|
||||
// We've already set aside all of the fragmented available memory within old-gen to represent old objects
|
||||
// to be promoted from young generation. promo_load represents the memory that we anticipate to be promoted
|
||||
// from regions that have reached tenure age. In the ideal, we will always use fragmented old-gen memory
|
||||
// to hold individually promoted objects and will use unfragmented old-gen memory to represent the old-gen
|
||||
// evacuation workloa.
|
||||
|
||||
// We're promoting and have an estimate of memory to be promoted from aged regions
|
||||
assert(max_old_reserve >= (reserve_for_mixed + reserve_for_promo), "Sanity");
|
||||
const size_t available_for_additional_promotions = max_old_reserve - (reserve_for_mixed + reserve_for_promo);
|
||||
size_t promo_need = (size_t)(promo_load * ShenandoahPromoEvacWaste);
|
||||
if (promo_need > reserve_for_promo) {
|
||||
reserve_for_promo += MIN2(promo_need - reserve_for_promo, available_for_additional_promotions);
|
||||
// promo_load represents the combined total of live memory within regions that have reached tenure age. The true
|
||||
// promotion potential is larger than this, because individual objects within regions that have not yet reached tenure
|
||||
// age may be promotable. On the other hand, some of the objects that we intend to promote in the next GC cycle may
|
||||
// die before they are next marked. In the future, the promo_load will include the total size of tenurable objects
|
||||
// residing in regions that have not yet reached tenure age.
|
||||
|
||||
if (doing_promotions) {
|
||||
// We are always doing promotions, even when old_generation->get_promotion_potential() returns 0. As currently implemented,
|
||||
// get_promotion_potential() only knows the total live memory contained within young-generation regions whose age is
|
||||
// tenurable. It does not know whether that memory will still be live at the end of the next mark cycle, and it doesn't
|
||||
// know how much memory is contained within objects whose individual ages are tenurable, which reside in regions with
|
||||
// non-tenurable age. We use this, as adjusted by ShenandoahPromoEvacWaste, as an approximation of the total amount of
|
||||
// memory to be promoted. In the near future, we expect to implement a change that will allow get_promotion_potential()
|
||||
// to account also for the total memory contained within individual objects that are tenure-ready even when they do
|
||||
// not reside in aged regions. This will represent a conservative over approximation of promotable memory because
|
||||
// some of these objects may die before the next GC cycle executes.
|
||||
|
||||
// Be careful not to ask for too much promotion reserves. We have observed jtreg test failures under which a greedy
|
||||
// promotion reserve causes a humongous allocation which is awaiting a full GC to fail (specifically
|
||||
// gc/TestAllocHumongousFragment.java). This happens if too much of the memory reclaimed by the full GC
|
||||
// is immediately reserved so that it cannot be allocated by the waiting mutator. It's not clear that this
|
||||
// particular test is representative of the needs of typical GenShen users. It is really a test of high frequency
|
||||
// Full GCs under heap fragmentation stress.
|
||||
|
||||
size_t promo_need = (size_t) (promo_load * ShenandoahPromoEvacWaste);
|
||||
if (promo_need > proposed_reserve_for_promo) {
|
||||
const size_t available_for_additional_promotions =
|
||||
max_old_reserve - (proposed_reserve_for_mixed + proposed_reserve_for_promo);
|
||||
if (proposed_reserve_for_promo + available_for_additional_promotions >= promo_need) {
|
||||
proposed_reserve_for_promo = promo_need;
|
||||
} else {
|
||||
proposed_reserve_for_promo += available_for_additional_promotions;
|
||||
}
|
||||
}
|
||||
// We've already reserved all the memory required for the promo_load, and possibly more. The excess
|
||||
// can be consumed by objects promoted from regions that have not yet reached tenure age.
|
||||
}
|
||||
// else, leave proposed_reserve_for_promo as is. By default, it is initialized to represent old_fragmented_available.
|
||||
|
||||
// This is the total old we want to reserve (initialized to the ideal reserve)
|
||||
size_t old_reserve = reserve_for_mixed + reserve_for_promo;
|
||||
size_t proposed_old_reserve = proposed_reserve_for_mixed + proposed_reserve_for_promo;
|
||||
|
||||
// We now check if the old generation is running a surplus or a deficit.
|
||||
size_t old_region_deficit = 0;
|
||||
@ -702,68 +741,70 @@ void ShenandoahGenerationalHeap::compute_old_generation_balance(size_t mutator_x
|
||||
// align the mutator_xfer_limit on region size
|
||||
mutator_xfer_limit = mutator_region_xfer_limit * region_size_bytes;
|
||||
|
||||
if (old_available >= old_reserve) {
|
||||
if (old_currently_available >= proposed_old_reserve) {
|
||||
// We are running a surplus, so the old region surplus can go to young
|
||||
const size_t old_surplus = old_available - old_reserve;
|
||||
const size_t old_surplus = old_currently_available - proposed_old_reserve;
|
||||
old_region_surplus = old_surplus / region_size_bytes;
|
||||
const size_t unaffiliated_old_regions = old_generation()->free_unaffiliated_regions() + old_trashed_regions;
|
||||
old_region_surplus = MIN2(old_region_surplus, unaffiliated_old_regions);
|
||||
old_generation()->set_region_balance(checked_cast<ssize_t>(old_region_surplus));
|
||||
} else if (old_available + mutator_xfer_limit >= old_reserve) {
|
||||
// Mutator's xfer limit is sufficient to satisfy our need: transfer all memory from there
|
||||
size_t old_deficit = old_reserve - old_available;
|
||||
old_currently_available -= old_region_surplus * region_size_bytes;
|
||||
young_available += old_region_surplus * region_size_bytes;
|
||||
} else if (old_currently_available + mutator_xfer_limit >= proposed_old_reserve) {
|
||||
// We know that old_currently_available < proposed_old_reserve because above test failed. Expand old_currently_available.
|
||||
// Mutator's xfer limit is sufficient to satisfy our need: transfer all memory from there.
|
||||
size_t old_deficit = proposed_old_reserve - old_currently_available;
|
||||
old_region_deficit = (old_deficit + region_size_bytes - 1) / region_size_bytes;
|
||||
old_generation()->set_region_balance(0 - checked_cast<ssize_t>(old_region_deficit));
|
||||
old_currently_available += old_region_deficit * region_size_bytes;
|
||||
young_available -= old_region_deficit * region_size_bytes;
|
||||
} else {
|
||||
// We'll try to xfer from both mutator excess and from young collector reserve
|
||||
size_t available_reserves = old_available + young_reserve + mutator_xfer_limit;
|
||||
size_t old_entitlement = (available_reserves * ShenandoahOldEvacPercent) / 100;
|
||||
// We know that (old_currently_available < proposed_old_reserve) and
|
||||
// (old_currently_available + mutator_xfer_limit < proposed_old_reserve) because above tests failed.
|
||||
// We need to shrink proposed_old_reserves.
|
||||
|
||||
// Round old_entitlement down to nearest multiple of regions to be transferred to old
|
||||
size_t entitled_xfer = old_entitlement - old_available;
|
||||
entitled_xfer = region_size_bytes * (entitled_xfer / region_size_bytes);
|
||||
size_t unaffiliated_young_regions = young_generation()->free_unaffiliated_regions();
|
||||
size_t unaffiliated_young_memory = unaffiliated_young_regions * region_size_bytes;
|
||||
if (entitled_xfer > unaffiliated_young_memory) {
|
||||
entitled_xfer = unaffiliated_young_memory;
|
||||
}
|
||||
old_entitlement = old_available + entitled_xfer;
|
||||
if (old_entitlement < old_reserve) {
|
||||
// There's not enough memory to satisfy our desire. Scale back our old-gen intentions.
|
||||
size_t budget_overrun = old_reserve - old_entitlement;;
|
||||
if (reserve_for_promo > budget_overrun) {
|
||||
reserve_for_promo -= budget_overrun;
|
||||
old_reserve -= budget_overrun;
|
||||
} else {
|
||||
budget_overrun -= reserve_for_promo;
|
||||
reserve_for_promo = 0;
|
||||
reserve_for_mixed = (reserve_for_mixed > budget_overrun)? reserve_for_mixed - budget_overrun: 0;
|
||||
old_reserve = reserve_for_promo + reserve_for_mixed;
|
||||
}
|
||||
}
|
||||
// We could potentially shrink young_reserves in order to further expand proposed_old_reserves. Let's not bother. The
|
||||
// important thing is that we keep a total amount of memory in reserve in preparation for the next GC cycle. At
|
||||
// the time we choose the next collection set, we'll have an opportunity to shift some of these young reserves
|
||||
// into old reserves if that makes sense.
|
||||
|
||||
// Because of adjustments above, old_reserve may be smaller now than it was when we tested the branch
|
||||
// condition above: "(old_available + mutator_xfer_limit >= old_reserve)
|
||||
// Therefore, we do NOT know that: mutator_xfer_limit < old_reserve - old_available
|
||||
|
||||
size_t old_deficit = old_reserve - old_available;
|
||||
old_region_deficit = (old_deficit + region_size_bytes - 1) / region_size_bytes;
|
||||
|
||||
// Shrink young_reserve to account for loan to old reserve
|
||||
const size_t reserve_xfer_regions = old_region_deficit - mutator_region_xfer_limit;
|
||||
young_reserve -= reserve_xfer_regions * region_size_bytes;
|
||||
// Start by taking all of mutator_xfer_limit into old_currently_available.
|
||||
size_t old_region_deficit = mutator_region_xfer_limit;
|
||||
old_generation()->set_region_balance(0 - checked_cast<ssize_t>(old_region_deficit));
|
||||
old_currently_available += old_region_deficit * region_size_bytes;
|
||||
young_available -= old_region_deficit * region_size_bytes;
|
||||
|
||||
assert(old_currently_available < proposed_old_reserve,
|
||||
"Old currently available (%zu) must be less than old reserve (%zu)", old_currently_available, proposed_old_reserve);
|
||||
|
||||
// There's not enough memory to satisfy our desire. Scale back our old-gen intentions. We prefer to satisfy
|
||||
// the budget_overrun entirely from the promotion reserve, if that is large enough. Otherwise, we'll satisfy
|
||||
// the overrun from a combination of promotion and mixed-evacuation reserves.
|
||||
size_t budget_overrun = proposed_old_reserve - old_currently_available;
|
||||
if (proposed_reserve_for_promo > budget_overrun) {
|
||||
proposed_reserve_for_promo -= budget_overrun;
|
||||
// Dead code:
|
||||
// proposed_old_reserve -= budget_overrun;
|
||||
} else {
|
||||
budget_overrun -= proposed_reserve_for_promo;
|
||||
proposed_reserve_for_promo = 0;
|
||||
proposed_reserve_for_mixed = (proposed_reserve_for_mixed > budget_overrun)? proposed_reserve_for_mixed - budget_overrun: 0;
|
||||
// Dead code:
|
||||
// Note: proposed_reserve_for_promo is 0 and proposed_reserve_for_mixed may equal 0.
|
||||
// proposed_old_reserve = proposed_reserve_for_mixed;
|
||||
}
|
||||
}
|
||||
|
||||
assert(old_region_deficit == 0 || old_region_surplus == 0, "Only surplus or deficit, never both");
|
||||
assert(young_reserve + reserve_for_mixed + reserve_for_promo <= old_available + young_available,
|
||||
assert(old_region_deficit == 0 || old_region_surplus == 0,
|
||||
"Only surplus (%zu) or deficit (%zu), never both", old_region_surplus, old_region_deficit);
|
||||
assert(young_reserve + proposed_reserve_for_mixed + proposed_reserve_for_promo <= old_currently_available + young_available,
|
||||
"Cannot reserve more memory than is available: %zu + %zu + %zu <= %zu + %zu",
|
||||
young_reserve, reserve_for_mixed, reserve_for_promo, old_available, young_available);
|
||||
young_reserve, proposed_reserve_for_mixed, proposed_reserve_for_promo, old_currently_available, young_available);
|
||||
|
||||
// deficit/surplus adjustments to generation sizes will precede rebuild
|
||||
young_generation()->set_evacuation_reserve(young_reserve);
|
||||
old_generation()->set_evacuation_reserve(reserve_for_mixed);
|
||||
old_generation()->set_promoted_reserve(reserve_for_promo);
|
||||
old_generation()->set_evacuation_reserve(proposed_reserve_for_mixed);
|
||||
old_generation()->set_promoted_reserve(proposed_reserve_for_promo);
|
||||
}
|
||||
|
||||
void ShenandoahGenerationalHeap::coalesce_and_fill_old_regions(bool concurrent) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user