Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
8493d54
Filter out young pointers from thread local SATB buffers if young is …
earthling-amzn Oct 16, 2025
ac36718
Temporarily increase log level
earthling-amzn Oct 16, 2025
bdbd8e3
Remove instrumentation
earthling-amzn Oct 17, 2025
039a08d
Comment out all calls to transfer old pointers out of SATB
earthling-amzn Oct 20, 2025
bbf207c
Need to flush thread local SATB buffers before young marking
earthling-amzn Oct 21, 2025
1301df4
Avoid checking gc-state every time we filter a SATB buffer
earthling-amzn Oct 21, 2025
eb65003
Stop processing completed SATB buffers, they should no longer have ba…
earthling-amzn Oct 21, 2025
05aa120
Manage satb filter at safepoint exit
earthling-amzn Oct 21, 2025
004a954
Remove unused code
earthling-amzn Oct 22, 2025
03a4dad
Flush old satb after update references
earthling-amzn Oct 23, 2025
e4f4f8a
Try piggybacking satb flush on update roots
earthling-amzn Oct 23, 2025
584b532
Oops, move inline definition out of ifdef ASSERT
earthling-amzn Oct 23, 2025
bafd55d
Fix assertion
earthling-amzn Oct 23, 2025
feeeaaf
Merge remote-tracking branch 'jdk/master' into piggyback-satb-flush-o…
earthling-amzn Oct 24, 2025
f47e976
Cleanup and comments
earthling-amzn Oct 24, 2025
72d89d4
Only flush satb once during degenerated cycle
earthling-amzn Oct 24, 2025
48d4941
Remove duplicate satb flush closure
earthling-amzn Oct 24, 2025
ec58b72
Fix typo in comment
earthling-amzn Oct 24, 2025
ef21a81
Flush SATB buffers upon entering degenerated cycle when old marking i…
earthling-amzn Oct 28, 2025
01f0f97
Merge remote-tracking branch 'jdk/master' into piggyback-satb-flush-o…
earthling-amzn Oct 30, 2025
4bd602d
Merge remote-tracking branch 'jdk/master' into piggyback-satb-flush-o…
earthling-amzn Nov 3, 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
12 changes: 11 additions & 1 deletion src/hotspot/share/gc/shenandoah/shenandoahClosures.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
#ifndef SHARE_GC_SHENANDOAH_SHENANDOAHCLOSURES_HPP
#define SHARE_GC_SHENANDOAH_SHENANDOAHCLOSURES_HPP

#include "code/nmethod.hpp"
#include "gc/shared/stringdedup/stringDedup.hpp"
#include "gc/shenandoah/shenandoahGenerationType.hpp"
#include "gc/shenandoah/shenandoahTaskqueue.hpp"
Expand Down Expand Up @@ -230,6 +229,17 @@ class ShenandoahConcUpdateRefsClosure : public ShenandoahUpdateRefsSuperClosure
};


class ShenandoahFlushSATB : public ThreadClosure {
private:
SATBMarkQueueSet& _satb_qset;

public:
explicit ShenandoahFlushSATB(SATBMarkQueueSet& satb_qset) : _satb_qset(satb_qset) {}

inline void do_thread(Thread* thread) override;
};


//
// ========= Utilities
//
Expand Down
4 changes: 4 additions & 0 deletions src/hotspot/share/gc/shenandoah/shenandoahClosures.inline.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,10 @@ inline void ShenandoahConcUpdateRefsClosure::work(T* p) {
_heap->conc_update_with_forwarded(p);
}

inline void ShenandoahFlushSATB::do_thread(Thread* thread) {
// Transfer any partial buffer to the qset for completed buffer processing.
_satb_qset.flush_queue(ShenandoahThreadLocalData::satb_mark_queue(thread));
}

//
// ========= Utilities
Expand Down
92 changes: 47 additions & 45 deletions src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -279,21 +279,6 @@ bool ShenandoahConcurrentGC::complete_abbreviated_cycle() {
heap->update_region_ages(_generation->complete_marking_context());
}

if (!heap->is_concurrent_old_mark_in_progress()) {
heap->concurrent_final_roots();
} else {
// Since the cycle was shortened for having enough immediate garbage, this will be
// the last phase before concurrent marking of old resumes. We must be sure
// that old mark threads don't see any pointers to garbage in the SATB queues. Even
// though nothing was evacuated, overwriting unreachable weak roots with null may still
// put pointers to regions that become trash in the SATB queues. The following will
// piggyback flushing the thread local SATB queues on the same handshake that propagates
// the gc state change.
ShenandoahSATBMarkQueueSet& satb_queues = ShenandoahBarrierSet::satb_mark_queue_set();
ShenandoahFlushSATBHandshakeClosure complete_thread_local_satb_buffers(satb_queues);
heap->concurrent_final_roots(&complete_thread_local_satb_buffers);
heap->old_generation()->concurrent_transfer_pointers_from_satb();
}
return true;
}

Expand Down Expand Up @@ -684,16 +669,10 @@ void ShenandoahConcurrentGC::op_init_mark() {
assert(!heap->has_forwarded_objects(), "No forwarded objects on this path");

if (heap->mode()->is_generational()) {

if (_generation->is_global()) {
heap->old_generation()->cancel_gc();
} else if (heap->is_concurrent_old_mark_in_progress()) {
// Purge the SATB buffers, transferring any valid, old pointers to the
// old generation mark queue. Any pointers in a young region will be
// abandoned.
ShenandoahGCPhase phase(ShenandoahPhaseTimings::init_transfer_satb);
heap->old_generation()->transfer_pointers_from_satb();
}

{
// After we swap card table below, the write-table is all clean, and the read table holds
// cards dirty prior to the start of GC. Young and bootstrap collection will update
Expand Down Expand Up @@ -1131,7 +1110,7 @@ class ShenandoahUpdateThreadHandshakeClosure : public HandshakeClosure {
ShenandoahNonConcUpdateRefsClosure _cl;
public:
ShenandoahUpdateThreadHandshakeClosure();
void do_thread(Thread* thread);
void do_thread(Thread* thread) override;
};

ShenandoahUpdateThreadHandshakeClosure::ShenandoahUpdateThreadHandshakeClosure() :
Expand All @@ -1146,9 +1125,49 @@ void ShenandoahUpdateThreadHandshakeClosure::do_thread(Thread* thread) {
}
}

class ShenandoahUpdateThreadRootsAndFlushOldSatbBuffers final : public HandshakeClosure {
// When Shenandoah is marking the old generation, it is possible for the SATB barrier
// to pick up overwritten pointers that point into a cset region. If these pointers
// are accessed by mark threads, they will crash. Once update refs has completed, it is
// no longer possible for a mutator thread to overwrite a pointer into a cset region.
//
// Therefore, at the end of update refs, we use this closure to update the thread roots
// and 'complete' all the thread local SATB buffers. Completing these will filter out
// anything that has already been marked or anything that points to a region which is
// not old. We do not need to worry about ABA situations where a region may become old
// after the pointer is enqueued but before it is filtered. There are only two ways a
// region may become old:
// 1. The region is promoted in place. This is safe because such regions will never
// be in the collection set. If this happens, the pointer will be preserved, essentially
// becoming part of the old snapshot.
// 2. The region is allocated during evacuation of old. This is also not a concern because
// we haven't yet finished marking old so no mixed evacuations will happen.
Copy link
Contributor

@kdnilsen kdnilsen Oct 24, 2025

Choose a reason for hiding this comment

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

Might be worth mentioning that there may be some additional analysis and code required when we use the forwarding table to recycle cset regions during evacuation and/or updating. If one of these regions becomes old before the SATB buffers have been flushed, then a young cset pointer that lingers in a SATB buffer will "all of a sudden" look like a valid old pointer and will not be purged from the SATB buffer. When we subsequently scan the "object" referenced by this obsolete pointer, we are likely to find "garbage memory", possibly resulting in a crash.

I am thinking that an initial fix might be to do this flushing at init-update-refs instead of at final update refs, and to not recycle cset regions until evacuation is all done. Is there a different handshake there that we might piggyback on?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm, that's a good point about recycling regions concurrently. I don't think we can flush before init-update-refs because forwarded pointers still exist and the SATB barrier doesn't try to resolve them. That is to say, "bad" pointers can be created throughout the update-refs phase.

Even in the (hypothetical) scenario with a forwarding table, a region would only become old through an in-place-promotion (in which case, it will never have been recycled) or we will be doing a mixed evacuation. If we are running a mixed evacuation, we will already have finished marking old.

ShenandoahUpdateThreadHandshakeClosure _update_roots;
ShenandoahFlushSATB _flush_all_satb;

public:
ShenandoahUpdateThreadRootsAndFlushOldSatbBuffers() :
HandshakeClosure("Shenandoah Update Thread Roots and Flush SATB"),
_flush_all_satb(ShenandoahBarrierSet::satb_mark_queue_set()) {
assert(ShenandoahBarrierSet::satb_mark_queue_set().get_filter_out_young(),
"Should be filtering pointers outside of old during old marking");
}

void do_thread(Thread* thread) override {
_update_roots.do_thread(thread);
_flush_all_satb.do_thread(thread);
}
};

void ShenandoahConcurrentGC::op_update_thread_roots() {
ShenandoahUpdateThreadHandshakeClosure cl;
Handshake::execute(&cl);
ShenandoahHeap* const heap = ShenandoahHeap::heap();
if (heap->is_concurrent_old_mark_in_progress()) {
ShenandoahUpdateThreadRootsAndFlushOldSatbBuffers cl;
Handshake::execute(&cl);
} else {
ShenandoahUpdateThreadHandshakeClosure cl;
Handshake::execute(&cl);
}
}

void ShenandoahConcurrentGC::op_final_update_refs() {
Expand Down Expand Up @@ -1177,23 +1196,6 @@ void ShenandoahConcurrentGC::op_final_update_refs() {
heap->set_has_forwarded_objects(false);

if (heap->mode()->is_generational() && heap->is_concurrent_old_mark_in_progress()) {
// When the SATB barrier is left on to support concurrent old gen mark, it may pick up writes to
// objects in the collection set. After those objects are evacuated, the pointers in the
// SATB are no longer safe. Once we have finished update references, we are guaranteed that
// no more writes to the collection set are possible.
//
// This will transfer any old pointers in _active_ regions from the SATB to the old gen
// mark queues. All other pointers will be discarded. This would also discard any pointers
// in old regions that were included in a mixed evacuation. We aren't using the SATB filter
// methods here because we cannot control when they execute. If the SATB filter runs _after_
// a region has been recycled, we will not be able to detect the bad pointer.
//
// We are not concerned about skipping this step in abbreviated cycles because regions
// with no live objects cannot have been written to and so cannot have entries in the SATB
// buffers.
ShenandoahGCPhase phase(ShenandoahPhaseTimings::final_update_refs_transfer_satb);
heap->old_generation()->transfer_pointers_from_satb();

// Aging_cycle is only relevant during evacuation cycle for individual objects and during final mark for
// entire regions. Both of these relevant operations occur before final update refs.
ShenandoahGenerationalHeap::heap()->set_aging_cycle(false);
Expand Down Expand Up @@ -1228,13 +1230,13 @@ bool ShenandoahConcurrentGC::entry_final_roots() {
ShenandoahWorkerPolicy::calc_workers_for_conc_evac(),
msg);

if (!heap->mode()->is_generational()) {
heap->concurrent_final_roots();
} else {
if (heap->mode()->is_generational()) {
if (!complete_abbreviated_cycle()) {
return false;
}
}

heap->concurrent_final_roots();
return true;
}

Expand Down
16 changes: 1 addition & 15 deletions src/hotspot/share/gc/shenandoah/shenandoahConcurrentMark.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,20 +66,6 @@ class ShenandoahConcurrentMarkingTask : public WorkerTask {
}
};

class ShenandoahSATBAndRemarkThreadsClosure : public ThreadClosure {
private:
SATBMarkQueueSet& _satb_qset;

public:
explicit ShenandoahSATBAndRemarkThreadsClosure(SATBMarkQueueSet& satb_qset) :
_satb_qset(satb_qset) {}

void do_thread(Thread* thread) override {
// Transfer any partial buffer to the qset for completed buffer processing.
_satb_qset.flush_queue(ShenandoahThreadLocalData::satb_mark_queue(thread));
}
};

template <ShenandoahGenerationType GENERATION>
class ShenandoahFinalMarkingTask : public WorkerTask {
private:
Expand Down Expand Up @@ -109,7 +95,7 @@ class ShenandoahFinalMarkingTask : public WorkerTask {
while (satb_mq_set.apply_closure_to_completed_buffer(&cl)) {}
assert(!heap->has_forwarded_objects(), "Not expected");

ShenandoahSATBAndRemarkThreadsClosure tc(satb_mq_set);
ShenandoahFlushSATB tc(satb_mq_set);
Threads::possibly_parallel_threads_do(true /* is_par */, &tc);
}
_cm->mark_loop(worker_id, _terminator, GENERATION, false /*not cancellable*/,
Expand Down
23 changes: 9 additions & 14 deletions src/hotspot/share/gc/shenandoah/shenandoahDegeneratedGC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +101,16 @@ void ShenandoahDegenGC::op_degenerated() {
heap->old_generation()->card_scan()->mark_write_table_as_clean();
}

#ifdef ASSERT
if (heap->mode()->is_generational()) {
ShenandoahOldGeneration* old_generation = heap->old_generation();
const ShenandoahOldGeneration* old_generation = heap->old_generation();
if (!heap->is_concurrent_old_mark_in_progress()) {
// If we are not marking the old generation, there should be nothing in the old mark queues
assert(old_generation->task_queues()->is_empty(), "Old gen task queues should be empty");
} else {
// This is still necessary for degenerated cycles because the degeneration point may occur
// after final mark of the young generation. See ShenandoahConcurrentGC::op_final_update_refs for
// a more detailed explanation.
old_generation->transfer_pointers_from_satb();
}

if (_generation->is_global()) {
Expand All @@ -118,7 +122,6 @@ void ShenandoahDegenGC::op_degenerated() {
"Old generation cannot be in state: %s", old_generation->state_name());
}
}
#endif

ShenandoahMetricsSnapshot metrics(heap->free_set());

Expand Down Expand Up @@ -166,15 +169,6 @@ void ShenandoahDegenGC::op_degenerated() {
_generation->cancel_marking();
}

if (heap->is_concurrent_mark_in_progress()) {
// If either old or young marking is in progress, the SATB barrier will be enabled.
// The SATB buffer may hold a mix of old and young pointers. The old pointers need to be
// transferred to the old generation mark queues and the young pointers are NOT part
// of this snapshot, so they must be dropped here. It is safe to drop them here because
// we will rescan the roots on this safepoint.
heap->old_generation()->transfer_pointers_from_satb();
}

if (_degen_point == ShenandoahDegenPoint::_degenerated_roots) {
// We only need this if the concurrent cycle has already swapped the card tables.
// Marking will use the 'read' table, but interesting pointers may have been
Expand All @@ -193,8 +187,9 @@ void ShenandoahDegenGC::op_degenerated() {
case _degenerated_mark:
// No fallthrough. Continue mark, handed over from concurrent mark if
// concurrent mark has yet completed
if (_degen_point == ShenandoahDegenPoint::_degenerated_mark &&
heap->is_concurrent_mark_in_progress()) {
if (_degen_point == ShenandoahDegenPoint::_degenerated_mark && heap->is_concurrent_mark_in_progress()) {
assert(!ShenandoahBarrierSet::satb_mark_queue_set().get_filter_out_young(),
"Should not be filtering out young pointers when concurrent mark degenerates");
op_finish_mark();
}
assert(!heap->cancelled_gc(), "STW mark can not OOM");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1040,12 +1040,6 @@ void ShenandoahGenerationalHeap::final_update_refs_update_region_states() {

void ShenandoahGenerationalHeap::complete_degenerated_cycle() {
shenandoah_assert_heaplocked_or_safepoint();
if (is_concurrent_old_mark_in_progress()) {
// This is still necessary for degenerated cycles because the degeneration point may occur
// after final mark of the young generation. See ShenandoahConcurrentGC::op_final_update_refs for
// a more detailed explanation.
old_generation()->transfer_pointers_from_satb();
}
// In case degeneration interrupted concurrent evacuation or update references, we need to clean up
// transient state. Otherwise, these actions have no effect.
reset_generation_reserves();
Expand Down
4 changes: 4 additions & 0 deletions src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2028,6 +2028,10 @@ void ShenandoahHeap::prepare_update_heap_references() {
void ShenandoahHeap::propagate_gc_state_to_all_threads() {
assert(ShenandoahSafepoint::is_at_shenandoah_safepoint(), "Must be at Shenandoah safepoint");
if (_gc_state_changed) {
// If we are only marking old, we do not need to process young pointers
ShenandoahBarrierSet::satb_mark_queue_set().set_filter_out_young(
is_concurrent_old_mark_in_progress() && !is_concurrent_young_mark_in_progress()
);
ShenandoahGCStatePropagatorHandshakeClosure propagator(_gc_state.raw_value());
Threads::threads_do(&propagator);
_gc_state_changed = false;
Expand Down
Loading