8380958: GenShen: Regulator thread may observe inconsistent old generation state

Reviewed-by: kdnilsen, xpeng
This commit is contained in:
William Kemper 2026-04-03 20:10:26 +00:00
parent 410b3e816a
commit e254526f4a
5 changed files with 33 additions and 60 deletions

View File

@ -576,7 +576,7 @@ void ShenandoahOldHeuristics::prepare_for_old_collections() {
} else if (has_coalesce_and_fill_candidates()) {
_old_generation->transition_to(ShenandoahOldGeneration::FILLING);
} else {
_old_generation->transition_to(ShenandoahOldGeneration::WAITING_FOR_BOOTSTRAP);
_old_generation->transition_to(ShenandoahOldGeneration::IDLE);
}
}

View File

@ -55,7 +55,7 @@ bool ShenandoahDegenGC::collect(GCCause::Cause cause) {
vmop_degenerated();
ShenandoahHeap* heap = ShenandoahHeap::heap();
if (heap->mode()->is_generational()) {
bool is_bootstrap_gc = heap->old_generation()->is_bootstrapping();
bool is_bootstrap_gc = heap->young_generation()->is_bootstrap_cycle();
heap->mmu_tracker()->record_degenerated(GCId::current(), is_bootstrap_gc);
const char* msg = is_bootstrap_gc? "At end of Degenerated Bootstrap Old GC": "At end of Degenerated Young GC";
heap->log_heap_status(msg);

View File

@ -422,12 +422,11 @@ void ShenandoahGenerationalControlThread::service_concurrent_old_cycle(const She
}
// Coalescing threads completed and nothing was cancelled. it is safe to transition from this state.
old_generation->transition_to(ShenandoahOldGeneration::WAITING_FOR_BOOTSTRAP);
old_generation->transition_to(ShenandoahOldGeneration::IDLE);
return;
}
case ShenandoahOldGeneration::WAITING_FOR_BOOTSTRAP:
old_generation->transition_to(ShenandoahOldGeneration::BOOTSTRAPPING);
case ShenandoahOldGeneration::BOOTSTRAPPING: {
case ShenandoahOldGeneration::IDLE:
old_generation->transition_to(ShenandoahOldGeneration::MARKING);
// Configure the young generation's concurrent mark to put objects in
// old regions into the concurrent mark queues associated with the old
// generation. The young cycle will run as normal except that rather than
@ -450,8 +449,6 @@ void ShenandoahGenerationalControlThread::service_concurrent_old_cycle(const She
// and init mark for the concurrent mark. All of that work will have been
// done by the bootstrapping young cycle.
set_gc_mode(servicing_old);
old_generation->transition_to(ShenandoahOldGeneration::MARKING);
}
case ShenandoahOldGeneration::MARKING: {
ShenandoahGCSession session(request.cause, old_generation);
bool marking_complete = resume_concurrent_old_cycle(old_generation, request.cause);
@ -644,12 +641,6 @@ void ShenandoahGenerationalControlThread::service_stw_degenerated_cycle(const Sh
if (request.generation->is_global()) {
assert(_heap->old_generation()->task_queues()->is_empty(), "Unexpected old generation marking tasks");
assert(_heap->global_generation()->task_queues()->is_empty(), "Unexpected global generation marking tasks");
} else {
assert(request.generation->is_young(), "Expected degenerated young cycle, if not global.");
ShenandoahOldGeneration* old = _heap->old_generation();
if (old->is_bootstrapping()) {
old->transition_to(ShenandoahOldGeneration::MARKING);
}
}
}
@ -681,7 +672,7 @@ bool ShenandoahGenerationalControlThread::request_concurrent_gc(ShenandoahGenera
// Cancel the old GC and wait for the control thread to start servicing the new request.
log_info(gc)("Preempting old generation mark to allow %s GC", generation->name());
while (gc_mode() == servicing_old) {
ShenandoahHeap::heap()->cancel_gc(GCCause::_shenandoah_concurrent_gc);
_heap->cancel_gc(GCCause::_shenandoah_concurrent_gc);
notify_control_thread(ml, GCCause::_shenandoah_concurrent_gc, generation);
ml.wait();
}

View File

@ -114,7 +114,7 @@ ShenandoahOldGeneration::ShenandoahOldGeneration(uint max_queues)
_promotable_regular_regions(0),
_is_parsable(true),
_card_scan(nullptr),
_state(WAITING_FOR_BOOTSTRAP),
_state(IDLE),
_growth_percent_before_collection(INITIAL_GROWTH_PERCENT_BEFORE_COLLECTION)
{
assert(type() == ShenandoahGenerationType::OLD, "OO sanity");
@ -339,7 +339,7 @@ void ShenandoahOldGeneration::cancel_gc() {
shenandoah_assert_safepoint();
if (is_idle()) {
#ifdef ASSERT
validate_waiting_for_bootstrap();
validate_idle();
#endif
} else {
log_info(gc)("Terminating old gc cycle.");
@ -350,7 +350,7 @@ void ShenandoahOldGeneration::cancel_gc() {
// Remove old generation access to young generation mark queues
ShenandoahHeap::heap()->young_generation()->set_old_gen_task_queues(nullptr);
// Transition to IDLE now.
transition_to(ShenandoahOldGeneration::WAITING_FOR_BOOTSTRAP);
transition_to(ShenandoahOldGeneration::IDLE);
}
}
@ -477,9 +477,8 @@ void ShenandoahOldGeneration::prepare_regions_and_collection_set(bool concurrent
const char* ShenandoahOldGeneration::state_name(State state) {
switch (state) {
case WAITING_FOR_BOOTSTRAP: return "Waiting for Bootstrap";
case IDLE: return "Idle";
case FILLING: return "Coalescing";
case BOOTSTRAPPING: return "Bootstrapping";
case MARKING: return "Marking";
case EVACUATING: return "Evacuating";
case EVACUATING_AFTER_GLOBAL: return "Evacuating (G)";
@ -517,7 +516,7 @@ void ShenandoahOldGeneration::transition_to(State new_state) {
// the old generation in the respective states (EVACUATING or FILLING). After a Full GC,
// the mark bitmaps are all reset, all regions are parsable and the mark context will
// not be "complete". After a Full GC, remembered set scans will _not_ use the mark bitmap
// and we expect the old generation to be waiting for bootstrap.
// and we expect the old generation to be idle.
//
// +-----------------+
// +------------> | FILLING | <---+
@ -526,19 +525,12 @@ void ShenandoahOldGeneration::transition_to(State new_state) {
// | | | |
// | | | Filling Complete | <-> A global collection may
// | | v | move the old generation
// | | +-----------------+ | directly from waiting for
// +-- |-- |--------> | WAITING | | bootstrap to filling or
// | | | +---- | FOR BOOTSTRAP | ----+ evacuating. It may also
// | | | | +-----------------+ move from filling to waiting
// | | | | | for bootstrap.
// | | | | | Reset Bitmap
// | | | | v
// | | | | +-----------------+ +----------------------+
// | | | | | BOOTSTRAP | <-> | YOUNG GC |
// | | | | | | | (RSet Parses Region) |
// | | | | +-----------------+ +----------------------+
// | | +-----------------+ | directly from idle to
// +-- |-- |--------> | IDLE | | filling or evacuating.
// | | | +---- | | ----+ It may also move from
// | | | | +-----------------+ filling to idle.
// | | | | |
// | | | | | Old Marking
// | | | | | Reset Bitmap + Start Marking
// | | | | v
// | | | | +-----------------+ +----------------------+
// | | | | | MARKING | <-> | YOUNG GC |
@ -564,29 +556,23 @@ void ShenandoahOldGeneration::validate_transition(State new_state) {
ShenandoahGenerationalHeap* heap = ShenandoahGenerationalHeap::heap();
switch (new_state) {
case FILLING:
assert(_state != BOOTSTRAPPING, "Cannot begin making old regions parsable after bootstrapping");
assert(is_mark_complete(), "Cannot begin filling without first completing marking, state is '%s'", state_name(_state));
assert(_old_heuristics->has_coalesce_and_fill_candidates(), "Cannot begin filling without something to fill.");
break;
case WAITING_FOR_BOOTSTRAP:
case IDLE:
// GC cancellation can send us back here from any state.
validate_waiting_for_bootstrap();
break;
case BOOTSTRAPPING:
assert(_state == WAITING_FOR_BOOTSTRAP, "Cannot reset bitmap without making old regions parsable, state is '%s'", state_name(_state));
assert(_old_heuristics->unprocessed_old_collection_candidates() == 0, "Cannot bootstrap with mixed collection candidates");
assert(!heap->is_prepare_for_old_mark_in_progress(), "Cannot still be making old regions parsable.");
validate_idle();
break;
case MARKING:
assert(_state == BOOTSTRAPPING, "Must have finished bootstrapping before marking, state is '%s'", state_name(_state));
assert(heap->young_generation()->old_gen_task_queues() != nullptr, "Young generation needs old mark queues.");
assert(heap->is_concurrent_old_mark_in_progress(), "Should be marking old now.");
assert(_state == IDLE, "Must be idle before marking, state is '%s'", state_name(_state));
assert(_old_heuristics->unprocessed_old_collection_candidates() == 0, "Cannot start marking with mixed collection candidates");
assert(!heap->is_prepare_for_old_mark_in_progress(), "Cannot still be making old regions parsable.");
break;
case EVACUATING_AFTER_GLOBAL:
assert(_state == EVACUATING, "Must have been evacuating, state is '%s'", state_name(_state));
break;
case EVACUATING:
assert(_state == WAITING_FOR_BOOTSTRAP || _state == MARKING, "Cannot have old collection candidates without first marking, state is '%s'", state_name(_state));
assert(_state == IDLE || _state == MARKING, "Cannot have old collection candidates without first marking, state is '%s'", state_name(_state));
assert(_old_heuristics->unprocessed_old_collection_candidates() > 0, "Must have collection candidates here.");
break;
default:
@ -594,10 +580,10 @@ void ShenandoahOldGeneration::validate_transition(State new_state) {
}
}
bool ShenandoahOldGeneration::validate_waiting_for_bootstrap() {
bool ShenandoahOldGeneration::validate_idle() {
ShenandoahHeap* heap = ShenandoahHeap::heap();
assert(!heap->is_concurrent_old_mark_in_progress(), "Cannot become ready for bootstrap during old mark.");
assert(heap->young_generation()->old_gen_task_queues() == nullptr, "Cannot become ready for bootstrap when still setup for bootstrapping.");
assert(!heap->is_concurrent_old_mark_in_progress(), "Cannot be idle during old mark.");
assert(heap->young_generation()->old_gen_task_queues() == nullptr, "Cannot be idle when still setup for bootstrapping.");
assert(!is_concurrent_mark_in_progress(), "Cannot be marking in IDLE");
assert(!heap->young_generation()->is_bootstrap_cycle(), "Cannot have old mark queues if IDLE");
assert(!_old_heuristics->has_coalesce_and_fill_candidates(), "Cannot have coalesce and fill candidates in IDLE");
@ -733,7 +719,7 @@ void ShenandoahOldGeneration::set_parsable(bool parsable) {
// that we would unload classes and make everything parsable. But, we know
// that now so we can override this state.
abandon_collection_candidates();
transition_to(ShenandoahOldGeneration::WAITING_FOR_BOOTSTRAP);
transition_to(ShenandoahOldGeneration::IDLE);
break;
default:
// We can get here during a full GC. The full GC will cancel anything
@ -750,7 +736,7 @@ void ShenandoahOldGeneration::complete_mixed_evacuations() {
assert(is_doing_mixed_evacuations(), "Mixed evacuations should be in progress");
if (!_old_heuristics->has_coalesce_and_fill_candidates()) {
// No candidate regions to coalesce and fill
transition_to(ShenandoahOldGeneration::WAITING_FOR_BOOTSTRAP);
transition_to(ShenandoahOldGeneration::IDLE);
return;
}
@ -764,7 +750,7 @@ void ShenandoahOldGeneration::complete_mixed_evacuations() {
// more to do.
assert(state() == ShenandoahOldGeneration::EVACUATING_AFTER_GLOBAL, "Should be evacuating after a global cycle");
abandon_collection_candidates();
transition_to(ShenandoahOldGeneration::WAITING_FOR_BOOTSTRAP);
transition_to(ShenandoahOldGeneration::IDLE);
}
void ShenandoahOldGeneration::abandon_mixed_evacuations() {
@ -774,7 +760,7 @@ void ShenandoahOldGeneration::abandon_mixed_evacuations() {
break;
case ShenandoahOldGeneration::EVACUATING_AFTER_GLOBAL:
abandon_collection_candidates();
transition_to(ShenandoahOldGeneration::WAITING_FOR_BOOTSTRAP);
transition_to(ShenandoahOldGeneration::IDLE);
break;
default:
log_warning(gc)("Abandon mixed evacuations in unexpected state: %s", state_name(state()));

View File

@ -256,11 +256,7 @@ public:
}
bool is_idle() const {
return state() == WAITING_FOR_BOOTSTRAP;
}
bool is_bootstrapping() const {
return state() == BOOTSTRAPPING;
return state() == IDLE;
}
// Amount of live memory (bytes) in regions waiting for mixed collections
@ -271,11 +267,11 @@ public:
public:
enum State {
FILLING, WAITING_FOR_BOOTSTRAP, BOOTSTRAPPING, MARKING, EVACUATING, EVACUATING_AFTER_GLOBAL
FILLING, IDLE, MARKING, EVACUATING, EVACUATING_AFTER_GLOBAL
};
#ifdef ASSERT
bool validate_waiting_for_bootstrap();
bool validate_idle();
#endif
private:
@ -318,7 +314,7 @@ public:
size_t usage_trigger_threshold() const;
bool can_start_gc() {
return _state == WAITING_FOR_BOOTSTRAP;
return _state == IDLE;
}
static const char* state_name(State state);