Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,11 @@ bool ShenandoahConcurrentGC::collect(GCCause::Cause cause) {
// This may be skipped if there is nothing to evacuate.
// If so, evac_in_progress would be unset by collection set preparation code.
if (heap->is_evacuation_in_progress()) {
// Final mark may have reclaimed some immediate garbage that could satisfy allocation requests.
// We are proceeding with evacuation, so notify alloc waiters (if we are not evacuating, completion
// of the abbreviated cycle would also notify them).
_controller->notify_alloc_waiters();

// Concurrently evacuate
entry_evacuate();
if (check_cancellation_and_abort()) {
Expand Down
81 changes: 10 additions & 71 deletions src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ void ShenandoahControlThread::run_service() {

// Notify waiters that a cycle is completed. They'll decide for themselves to continue waiting or not.
notify_gc_waiters();
notify_alloc_waiters();

// Report current free set state at the end of cycle, whether
// it is a normal completion, or the abort.
Expand Down Expand Up @@ -215,6 +216,7 @@ void ShenandoahControlThread::run_service() {

// In case any threads are waiting for a cycle to happen, notify them so they observe the shutdown.
notify_gc_waiters();
notify_alloc_waiters();
}

void ShenandoahControlThread::service_concurrent_normal_cycle(GCCause::Cause cause) {
Expand Down Expand Up @@ -277,20 +279,7 @@ void ShenandoahControlThread::service_stw_full_cycle(GCCause::Cause cause) {
gc.collect(cause);
}

void ShenandoahControlThread::request_gc(GCCause::Cause cause) {
if (cause == GCCause::_shenandoah_upgrade_to_full_gc) {
handle_alloc_failure_full();
} else if (ShenandoahCollectorPolicy::should_handle_requested_gc(cause)) {
if (ShenandoahCollectorPolicy::is_allocation_failure(cause)) {
ShenandoahHeap* heap = ShenandoahHeap::heap();
heap->global_generation()->heuristics()->record_allocation_stall();
heap->shenandoah_policy()->record_allocation_stall(get_phase());
}
handle_requested_gc(cause);
}
}

void ShenandoahControlThread::notify_control_thread(GCCause::Cause cause) {
void ShenandoahControlThread::notify_control_thread(GCCause::Cause cause, ShenandoahGeneration* ignored) {
// Although setting gc request is under _controller_lock, the read side (run_service())
// does not take the lock. We need to enforce following order, so that read side sees
// latest requested gc cause when the flag is set. Do not let a lower priority cause
Expand All @@ -305,63 +294,13 @@ void ShenandoahControlThread::notify_control_thread(GCCause::Cause cause) {
controller.notify();
}

void ShenandoahControlThread::handle_requested_gc(GCCause::Cause cause) {
if (should_terminate()) {
log_info(gc)("Control thread is terminating, no more GCs");
return;
}

// For normal requested GCs (System.gc) we want to block the caller. However,
// for whitebox requested GC, we want to initiate the GC and return immediately.
// The whitebox caller thread will arrange for itself to wait until the GC notifies
// it that has reached the requested breakpoint (phase in the GC).
if (cause == GCCause::_wb_breakpoint) {
notify_control_thread(cause);
return;
}

// Make sure we have at least one complete GC cycle before unblocking
// from the explicit GC request.
//
// This is especially important for weak references cleanup and/or native
// resources (e.g. DirectByteBuffers) machinery: when explicit GC request
// comes very late in the already running cycle, it would miss lots of new
// opportunities for cleanup that were made available before the caller
// requested the GC.

MonitorLocker ml(&_gc_waiters_lock);
size_t current_gc_id = get_gc_id();
size_t required_gc_id = current_gc_id + 1;
while (current_gc_id < required_gc_id && !should_terminate()) {
if (ShenandoahCollectorPolicy::is_allocation_failure(cause)) {
_alloc_stall_count.add_then_fetch(1UL);
increase_concurrent_worker_count();
}

notify_control_thread(cause);
ml.wait();
current_gc_id = get_gc_id();
if (ShenandoahCollectorPolicy::is_allocation_failure(cause)) {
break;
}
}
}
void ShenandoahControlThread::notify_alloc_stall(GCCause::Cause cause) {
ShenandoahHeap* heap = ShenandoahHeap::heap();
ShenandoahGeneration* generation = heap->global_generation();

void ShenandoahControlThread::handle_alloc_failure_full() {
if (should_terminate()) {
log_info(gc)("Control thread is terminating, no more GCs");
return;
}
// Inform global heuristics of allocation stall
generation->heuristics()->record_allocation_stall();

// Make sure we have at least one full GC cycle before unblocking
// from the explicit GC request.
const ShenandoahCollectorPolicy* policy = ShenandoahHeap::heap()->shenandoah_policy();
MonitorLocker ml(&_gc_waiters_lock);
size_t full_gc_count = policy->full_gc_count();
const size_t required_count = full_gc_count + 1;
while (full_gc_count < required_count && !should_terminate()) {
notify_control_thread(GCCause::_shenandoah_upgrade_to_full_gc);
ml.wait();
full_gc_count = policy->full_gc_count();
}
// Run a global cycle (no other kind for this control thread).
notify_control_thread(cause, generation);
}
19 changes: 6 additions & 13 deletions src/hotspot/share/gc/shenandoah/shenandoahControlThread.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,13 @@
#ifndef SHARE_GC_SHENANDOAH_SHENANDOAHCONTROLTHREAD_HPP
#define SHARE_GC_SHENANDOAH_SHENANDOAHCONTROLTHREAD_HPP

#include "gc/shared/concurrentGCThread.hpp"
#include "gc/shared/gcCause.hpp"
#include "gc/shenandoah/shenandoahController.hpp"
#include "gc/shenandoah/shenandoahGC.hpp"
#include "gc/shenandoah/shenandoahPadding.hpp"
#include "gc/shenandoah/shenandoahSharedVariables.hpp"

class ShenandoahGeneration;

class ShenandoahControlThread: public ShenandoahController {
private:
typedef enum {
none,
concurrent_normal,
Expand All @@ -49,24 +47,19 @@ class ShenandoahControlThread: public ShenandoahController {
public:
ShenandoahControlThread();

protected:
void run_service() override;
void stop_service() override;

void request_gc(GCCause::Cause cause) override;
// Sets the requested cause, flag and notifies the control thread
void notify_control_thread(GCCause::Cause cause, ShenandoahGeneration* generation) override;
void notify_alloc_stall(GCCause::Cause cause) override;

private:
// Sets the requested cause and flag and notifies the control thread
void notify_control_thread(GCCause::Cause cause);

bool check_cancellation();
void service_concurrent_normal_cycle(GCCause::Cause cause);
void service_stw_full_cycle(GCCause::Cause cause);

// Handle GC request.
// Blocks until GC is over.
void handle_requested_gc(GCCause::Cause cause);

void handle_alloc_failure_full();
};

#endif // SHARE_GC_SHENANDOAH_SHENANDOAHCONTROLTHREAD_HPP
74 changes: 73 additions & 1 deletion src/hotspot/share/gc/shenandoah/shenandoahController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

#include "gc/shared/allocTracer.hpp"
#include "gc/shared/gc_globals.hpp"
#include "gc/shenandoah/shenandoahCollectorPolicy.hpp"
#include "gc/shenandoah/shenandoahController.hpp"
#include "gc/shenandoah/shenandoahHeap.hpp"
#include "gc/shenandoah/shenandoahHeapRegion.inline.hpp"
Expand All @@ -33,6 +34,7 @@ ShenandoahController::ShenandoahController():
_gc_id(0),
_phase(UNSET),
_gc_waiters_lock(WAITERS_LOCK_RANK, "ShenandoahGCWaiters_lock", true),
_alloc_waiters_lock(WAITERS_LOCK_RANK, "ShenandoahAllocWaiters_lock", true),
_alloc_stall_count(0),
_concurrent_worker_count(ConcGCThreads) { }

Expand All @@ -54,7 +56,72 @@ void ShenandoahController::handle_alloc_failure(const ShenandoahAllocRequest &re
log_debug(gc)("Failed to allocate %s, " PROPERFMT, req.type_string(), PROPERFMTARGS(req_byte));
AllocTracer::send_allocation_requiring_gc_event(req_byte, checked_cast<uint>(get_gc_id()));

request_gc(cause);
// This is the inner part of a larger retry loop, so just wait here
MonitorLocker ml(&_alloc_waiters_lock);
_alloc_stall_count.add_then_fetch(1UL);
increase_concurrent_worker_count();
ShenandoahHeap::heap()->shenandoah_policy()->record_allocation_stall(get_phase());
notify_alloc_stall(cause);
if (!should_terminate()) {
ml.wait();
}
}

void ShenandoahController::handle_alloc_failure_full() {
if (should_terminate()) {
log_info(gc)("Control thread is terminating, no more GCs");
return;
}

// Make sure we have at least one full GC cycle before unblocking from the explicit GC request.
ShenandoahHeap* heap = ShenandoahHeap::heap();
const ShenandoahCollectorPolicy* policy = heap->shenandoah_policy();
MonitorLocker ml(&_gc_waiters_lock);
size_t full_gc_count = policy->full_gc_count();
const size_t required_count = full_gc_count + 1;
while (full_gc_count < required_count && !should_terminate()) {
notify_control_thread(GCCause::_shenandoah_upgrade_to_full_gc, heap->global_generation());
ml.wait();
full_gc_count = policy->full_gc_count();
}
}

void ShenandoahController::request_gc(GCCause::Cause cause) {
if (ShenandoahCollectorPolicy::should_handle_requested_gc(cause)) {
handle_requested_gc(cause);
}
}

void ShenandoahController::handle_requested_gc(GCCause::Cause cause) {

// Requested gc's always operate on the entire heap
ShenandoahGeneration* global = ShenandoahHeap::heap()->global_generation();

// For normal requested GCs (System.gc) we want to block the caller. However,
// for whitebox requested GC, we want to initiate the GC and return immediately.
// The whitebox caller thread will arrange for itself to wait until the GC notifies
// it that has reached the requested breakpoint (phase in the GC).
if (cause == GCCause::_wb_breakpoint) {
notify_control_thread(cause, global);
return;
}

// Make sure we have at least one complete GC cycle before unblocking
// from the explicit GC request.
//
// This is especially important for weak references cleanup and/or native
// resources (e.g. DirectByteBuffers) machinery: when explicit GC request
// comes very late in the already running cycle, it would miss lots of new
// opportunities for cleanup that were made available before the caller
// requested the GC.
MonitorLocker ml(&_gc_waiters_lock);
size_t current_gc_id = get_gc_id();
size_t required_gc_id = current_gc_id + 1;
while (current_gc_id < required_gc_id && !should_terminate()) {
notify_control_thread(cause, global);
ml.wait();
current_gc_id = get_gc_id();
}
}

void ShenandoahController::increase_concurrent_worker_count() {
Expand Down Expand Up @@ -93,6 +160,11 @@ void ShenandoahController::notify_gc_waiters() {
ml.notify_all();
}

void ShenandoahController::notify_alloc_waiters() {
MonitorLocker ml(&_alloc_waiters_lock);
ml.notify_all();
}

const char* ShenandoahController::collector_phase_to_string(ShenandoahCollectorPhase phase) {
switch(phase) {
case UNSET: return "Outside of Cycle";
Expand Down
32 changes: 28 additions & 4 deletions src/hotspot/share/gc/shenandoah/shenandoahController.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
#include "gc/shenandoah/shenandoahPadding.hpp"
#include "runtime/atomic.hpp"

class ShenandoahGeneration;

/**
* This interface exposes methods necessary for the heap to interact
* with the threads responsible for driving the collection cycle.
Expand Down Expand Up @@ -60,8 +62,14 @@ class ShenandoahController: public ConcurrentGCThread {
const Mutex::Rank WAITERS_LOCK_RANK = Mutex::safepoint - 5;
const Mutex::Rank CONTROL_LOCK_RANK = Mutex::nosafepoint - 2;

// Threads waiting for a complete gc cycle (full gc or concurrent global) will wait here.
Monitor _gc_waiters_lock;

// Threads waiting for the gc to free memory will wait here. Note that immediate garabge may be reclaimed
// midcycle during final mark, so we want to notify alloc waiters independently of cycle waiters. Similarly,
// no threads should be notified when a concurrent old mark increment is interrupted.
Monitor _alloc_waiters_lock;

// The number of stalls experienced during the cycle. Incremented by mutators, reset by control thread.
shenandoah_padding(2);
Atomic<size_t> _alloc_stall_count;
Expand All @@ -84,20 +92,32 @@ class ShenandoahController: public ConcurrentGCThread {
return _alloc_stall_count.load_relaxed();
}

// Notify the control thread to run a cycle for the given generation
virtual void notify_control_thread(GCCause::Cause cause, ShenandoahGeneration* generation) = 0;

// Notify the control thread about an allocation stall specifically. Implementation
// will decide which generation to use and any other mode specific work that must be done.
virtual void notify_alloc_stall(GCCause::Cause cause) = 0;

public:
ShenandoahController();

// Request a collection cycle. This handles "explicit" gc requests
// like System.gc and "implicit" gc requests, like metaspace oom.
virtual void request_gc(GCCause::Cause cause) = 0;
virtual void request_gc(GCCause::Cause cause);

// Notify threads that the gc has reclaimed memory (or that it completed a cycle, or it's exiting).
// Notify threads that the gc has completed a cycle, or it's exiting.
void notify_gc_waiters();

// This cancels the collection cycle and has an option to block
// until another cycle completes successfully.
// Notify threads that the gc has recovered memory, or it's exiting.
void notify_alloc_waiters();

// Inform the control thread that this allocation request failed. Caller will block until memory is reclaimed.
void handle_alloc_failure(const ShenandoahAllocRequest &req);

// Run a full GC, callers will block until at least one full GC cycle is completed.
void handle_alloc_failure_full();

// Return suggested number of concurrent worker threads
size_t concurrent_worker_count() const {
return _concurrent_worker_count.load_relaxed();
Expand All @@ -110,12 +130,16 @@ class ShenandoahController: public ConcurrentGCThread {
return _phase.load_relaxed();
}

// Record the current phase for the cycle. Used to track where allocation stalls occur.
void set_phase(ShenandoahCollectorPhase phase) {
assert(ShenandoahCollectorPhase::UNSET <= phase, "Phase out of bounds: %d", phase);
assert(phase < ShenandoahCollectorPhase::PHASE_LIMIT, "Phase out of bounds %d", phase);
_phase.store_relaxed(phase);
}

static const char* collector_phase_to_string(ShenandoahCollectorPhase phase);

protected:
void handle_requested_gc(GCCause::Cause cause);
};
#endif // SHARE_GC_SHENANDOAH_SHENANDOAHCONTROLLER_HPP
Loading