8387790: Shenandoah: Remove degenerated cycles - #31797
Conversation
… from updating references Otherwise, assertions about objects being forwarded (even self-forwarded) may not hold.
Also, check for displaced mark words when making assertions about objects being self-forwarded.
…stroys lock bits)
…ions with failed evacuations
|
|
||
| ShenandoahHeapRegion* from_region = heap_region_containing(p); | ||
| assert(!from_region->is_humongous(), "never evacuate humongous objects"); | ||
| if (has_self_forwarded_objects() && from_region->has_self_forwards()) { |
There was a problem hiding this comment.
Currently, once a thread self-forwards an object, no other threads (not even a worker) will attempt further evacuations from the region. We could defer setting this flag until after a worker has tried to evacuate all it can, but as this region will only be partially recycled anyway, it is unclear if more aggressive evacuation would be worth it.
|
Under "additional behavior changes":
|
|
We have:
I will elaborate on 2. |
kdnilsen
left a comment
There was a problem hiding this comment.
Thanks for this very thorough simplification of GenShen. I like the approach. I have a few questions embedded within my comments.
| _cycle_start = os::elapsedTime(); | ||
| cancel_trigger_request(); | ||
| log_debug(gc, ergo)("Declined trigger count at start: %zu", _declined_trigger_count.load_relaxed()); | ||
| } |
There was a problem hiding this comment.
Note to self: In tip, we do not penalize if a concurrent GC "degenerates" through no fault of the triggering heuristic. We used a count of how many times we declined to trigger as an indication that the triggering heuristic might be responsible for degeneration. If it declined to trigger less than a small threshold value, then we say it was not at fault, and we do not apply a penalty.
I wonder if there's an equivalent notion now that we do not have degenerated cycles. I would expect that we apply a penalty if a GC cycle experiences allocation stalls and the scheduling heuristic had declined to trigger more than a specific threshold number of times.
I want to figure out if this mechanism is still present.
There was a problem hiding this comment.
I see that ShenandoahHeuristics::record_concurrent_completion() still checks _declined_trigger_count before imposing a triggering penalty. I just need to find where its value is incremented.
There was a problem hiding this comment.
ShenandoahHeuristics::decline_trigger() increments the count.
There was a problem hiding this comment.
Yes, I tried to preserve all that machinery based on stalls.
| _alloc_failure_full(0) { | ||
|
|
||
| Copy::zero_to_bytes(_degen_point_counts, sizeof(size_t) * ShenandoahGC::_DEGENERATED_LIMIT); | ||
| Copy::zero_to_bytes(_stall_counts, sizeof(size_t) * ShenandoahController::PHASE_LIMIT); |
There was a problem hiding this comment.
In the future, will we also report total time spent by mutator threads in stall in addition to the count of how many times threads experienced stalls?
| _alloc_failure_full(0) { | ||
|
|
||
| Copy::zero_to_bytes(_degen_point_counts, sizeof(size_t) * ShenandoahGC::_DEGENERATED_LIMIT); | ||
| Copy::zero_to_bytes(_stall_counts, sizeof(size_t) * ShenandoahController::PHASE_LIMIT); |
There was a problem hiding this comment.
Probably would also like to see maximum time spent in any single stall on a per-thread basis.
| } | ||
|
|
||
| oop ShenandoahGenerationalHeap::evacuate_object(oop p, Thread* thread) { | ||
| assert(thread == Thread::current(), "Expected thread parameter to be current thread."); |
There was a problem hiding this comment.
Note to self: The GC worker threads use an object iterator and closure to evacuate every object in a region. The closure calls this method for each marked object in the region. If a GC worker thread fails to evacuate an object within its "assigned" region, the closure will continue to invoke this method for subsequent marked objects within the region. The first failed allocation will cause the heap region to be identified as having self-forwarded objects, and this will cause the subsequent invocations of this method for objects residing in the same region to also be self forwarded.
|
|
||
| if (heap->is_degenerated_gc_in_progress()) { | ||
| if (heap->has_self_forwarded_objects()) { | ||
| status |= (1 << 6); |
There was a problem hiding this comment.
Should we be using symbolic constants here?
There was a problem hiding this comment.
Yes, but can we make that a TODO? It's somewhat orthogonal to this change.
| @@ -1151,7 +1145,14 @@ class ShenandoahEvacuationTask : public WorkerTask { | |||
| assert(r->has_live(), "Region %zu should have been reclaimed early", r->index()); | |||
| _sh->marked_object_iterate(r, &cl); | |||
|
|
|||
There was a problem hiding this comment.
This seems overly conservative. We don't know for sure that this thread introduced the self forwards. Maybe some other thread introduced the self-forwarded objects into this region.
I'm also concerned about overgeneralizing the cause for self-forwards. It is possible that the reason that OOM evac resulted in self-forwarded objects is because of a single very large object for which there is not a sufficiently large contiguous segment of memory to hold its copy, anywhere, even though there might be an abundance of memory available to hold the results of evacuating smaller objects. If that's the case, it is appropriate to self-forward that region, but it is not appropriate to shutdown this worker thread.
So there are two things I think we want to check here before shutting down this thread's efforts:
- Confirm that I am the thread that caused this region to have self-forwarded objects, and
- Confirm that the OOM during evac that I experienced is due to an inability to refresh a minimum-sized PLAB or GCLAB. If my existing GC/P LABs have at least min-size available memory and/or there is memory available to refresh my GC/P LABs, then I think we should not take this GC worker out of commission.
Willing to discuss/consider alternative perspectives, especially if we have data to contradict my recommendation here...
There was a problem hiding this comment.
Yes, that's fair. In general, each region will be visited by only one worker, but it's possible that some rogue mutator with an exhausted PLAB could poison a region (or regions) before the worker gets to it. I shall think on this.
| // Note: We may have spurious wakeups here when an old gc cycle completes. | ||
| // We don't have a mechanism to wait for specific types of cycles to complete. | ||
| // In general though, nobody should be notified when an old mark increment | ||
| // completes. |
There was a problem hiding this comment.
Note that the "final" old-mark increment may recycle immediate garbage. In some workloads, that is fairly common (e.g. diluvian). Would you want to special case that?
There was a problem hiding this comment.
I will think on this. In this PR I combined 'alloc waiters' (waiting for memory) and 'gc waiters' (waiting for a complete cycle) because it seemed a simplification at that the time. Note that even before this change, both such waiters could be woken up spuriously for old mark increments (even when they don't reclaim memory). I have a follow up branch based on this PR that notifies waiters when final mark frees up garbage. I'll clean this up and make a dependent PR from this one.
There was a problem hiding this comment.
@kdnilsen - Here is a dependent PR that is meant to address your feedback: #32034. It's not trivial so I didn't want to just fold it in here. The new PR also adds a feature to unblock alloc waiters for immediate garbage even when the cycle moves on to the evacuation phase. I co-opted this old ticket: https://bugs.openjdk.org/browse/JDK-8338534.
|
|
…cipate in safepoints
… in non-generational modes either
We don't want to reset this region's age and don't want to change its affiliation
When Shenandoah experiences a concurrent mode failure, it initiates a safepoint and attempts to finish the collection cycle there. This handoff between concurrent and degenerated cycles is error prone and has been the source of many, many defects. Removing degenerated cycles will simplify the code base, lower maintenance costs and allow the team to focus on more meaningful changes.
Threads which cannot allocate will stall. Threads which cannot evacuate will self-forward the object (this is not new behavior). Memory from regions with self-forwarded objects will be reclaimed simply by moving the region's top to the highest self-forwarded object in the region. More sophisticated reclamation from such regions will follow.
Additional behavior changes:
ShenandoahRegionCounter(used exclusively for the visualizer) reports cycles with evacuation failures instead of degenerated cycles.Progress
Issue
Reviewing
Using
gitCheckout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/31797/head:pull/31797$ git checkout pull/31797Update a local copy of the PR:
$ git checkout pull/31797$ git pull https://git.openjdk.org/jdk.git pull/31797/headUsing Skara CLI tools
Checkout this PR locally:
$ git pr checkout 31797View PR using the GUI difftool:
$ git pr show -t 31797Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/31797.diff
Using Webrev
Link to Webrev Comment