diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp index 4a257265931b8..2e69dc7762f13 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp @@ -468,6 +468,11 @@ HeapWord* G1CollectedHeap::attempt_allocation_slow(uint node_index, size_t word_ log_warning(gc, alloc)("%s: Retried allocation %u times for %zu words", Thread::current()->name(), try_count, word_size); } + + if (is_shutting_down()) { + stall_for_vm_shutdown(); + return nullptr; + } } ShouldNotReachHere(); @@ -703,6 +708,11 @@ HeapWord* G1CollectedHeap::attempt_allocation_humongous(size_t word_size) { log_warning(gc, alloc)("%s: Retried allocation %u times for %zu words", Thread::current()->name(), try_count, word_size); } + + if (is_shutting_down()) { + stall_for_vm_shutdown(); + return nullptr; + } } ShouldNotReachHere(); @@ -1504,10 +1514,6 @@ jint G1CollectedHeap::initialize() { return JNI_OK; } -bool G1CollectedHeap::concurrent_mark_is_terminating() const { - return _cm_thread->should_terminate(); -} - void G1CollectedHeap::stop() { // Stop all concurrent threads. We do this to make sure these threads // do not continue to execute and access resources (e.g. logging) @@ -1811,7 +1817,7 @@ bool G1CollectedHeap::try_collect_concurrently(GCCause::Cause cause, // If VMOp skipped initiating concurrent marking cycle because // we're terminating, then we're done. - if (op.terminating()) { + if (is_shutting_down()) { LOG_COLLECT_CONCURRENTLY(cause, "skipped: terminating"); return false; } diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp index 8d26bcb1c0b99..4ba2d3244fe55 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp @@ -899,9 +899,6 @@ class G1CollectedHeap : public CollectedHeap { // specified by the policy object. jint initialize() override; - // Returns whether concurrent mark threads (and the VM) are about to terminate. - bool concurrent_mark_is_terminating() const; - void safepoint_synchronize_begin() override; void safepoint_synchronize_end() override; diff --git a/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp b/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp index a7dc0a7f33f62..f4861f7c83c5e 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp @@ -1883,7 +1883,7 @@ bool G1ConcurrentMark::concurrent_cycle_abort() { // nothing, but this situation should be extremely rare (a full gc after shutdown // has been signalled is already rare), and this work should be negligible compared // to actual full gc work. - if (!cm_thread()->in_progress() && !_g1h->concurrent_mark_is_terminating()) { + if (!cm_thread()->in_progress() && !_g1h->is_shutting_down()) { return false; } diff --git a/src/hotspot/share/gc/g1/g1Policy.cpp b/src/hotspot/share/gc/g1/g1Policy.cpp index 9f872aa6ccd67..ea6445ce19834 100644 --- a/src/hotspot/share/gc/g1/g1Policy.cpp +++ b/src/hotspot/share/gc/g1/g1Policy.cpp @@ -662,6 +662,7 @@ bool G1Policy::should_retain_evac_failed_region(uint index) const { } void G1Policy::record_pause_start_time() { + assert(!_g1h->is_shutting_down(), "Invariant!"); Ticks now = Ticks::now(); _cur_pause_start_sec = now.seconds(); @@ -1246,7 +1247,7 @@ void G1Policy::decide_on_concurrent_start_pause() { // We should not be starting a concurrent start pause if the concurrent mark // thread is terminating. - if (_g1h->concurrent_mark_is_terminating()) { + if (_g1h->is_shutting_down()) { return; } diff --git a/src/hotspot/share/gc/g1/g1RemSet.cpp b/src/hotspot/share/gc/g1/g1RemSet.cpp index 2a09512730cf7..c4167b2eb96cf 100644 --- a/src/hotspot/share/gc/g1/g1RemSet.cpp +++ b/src/hotspot/share/gc/g1/g1RemSet.cpp @@ -1105,7 +1105,7 @@ class G1MergeHeapRootsTask : public WorkerTask { // There might actually have been scheduled multiple collections, but at that point we do // not care that much about performance and just do the work multiple times if needed. return (_g1h->collector_state()->clear_bitmap_in_progress() || - _g1h->concurrent_mark_is_terminating()) && + _g1h->is_shutting_down()) && hr->is_old(); } diff --git a/src/hotspot/share/gc/g1/g1VMOperations.cpp b/src/hotspot/share/gc/g1/g1VMOperations.cpp index 6757172b625a5..34e14f742aee3 100644 --- a/src/hotspot/share/gc/g1/g1VMOperations.cpp +++ b/src/hotspot/share/gc/g1/g1VMOperations.cpp @@ -64,7 +64,6 @@ VM_G1TryInitiateConcMark::VM_G1TryInitiateConcMark(uint gc_count_before, _mark_in_progress(false), _cycle_already_in_progress(false), _whitebox_attached(false), - _terminating(false), _gc_succeeded(false) {} @@ -83,19 +82,10 @@ void VM_G1TryInitiateConcMark::doit() { GCCauseSetter x(g1h, _gc_cause); - // Record for handling by caller. - _terminating = g1h->concurrent_mark_is_terminating(); - _mark_in_progress = g1h->collector_state()->mark_in_progress(); _cycle_already_in_progress = g1h->concurrent_mark()->cm_thread()->in_progress(); - if (_terminating && GCCause::is_user_requested_gc(_gc_cause)) { - // When terminating, the request to initiate a concurrent cycle will be - // ignored by do_collection_pause_at_safepoint; instead it will just do - // a young-only or mixed GC (depending on phase). For a user request - // there's no point in even doing that much, so done. For some non-user - // requests the alternative GC might still be needed. - } else if (!g1h->policy()->force_concurrent_start_if_outside_cycle(_gc_cause)) { + if (!g1h->policy()->force_concurrent_start_if_outside_cycle(_gc_cause)) { // Failure to force the next GC pause to be a concurrent start indicates // there is already a concurrent marking cycle in progress. Flags to indicate // that were already set, so return immediately. @@ -119,7 +109,6 @@ VM_G1CollectForAllocation::VM_G1CollectForAllocation(size_t word_size, void VM_G1CollectForAllocation::doit() { G1CollectedHeap* g1h = G1CollectedHeap::heap(); - GCCauseSetter x(g1h, _gc_cause); // Try a partial collection of some kind. g1h->do_collection_pause_at_safepoint(); @@ -156,6 +145,14 @@ void VM_G1PauseConcurrent::doit() { bool VM_G1PauseConcurrent::doit_prologue() { Heap_lock->lock(); + G1CollectedHeap* g1h = G1CollectedHeap::heap(); + if (g1h->is_shutting_down()) { + Heap_lock->unlock(); + // JVM shutdown has started. This ensures that any further operations will be properly aborted + // and will not interfere with the shutdown process. + g1h->concurrent_mark()->abort_marking_threads(); + return false; + } return true; } diff --git a/src/hotspot/share/gc/g1/g1VMOperations.hpp b/src/hotspot/share/gc/g1/g1VMOperations.hpp index 05b27c4508c98..2adeaed04d90e 100644 --- a/src/hotspot/share/gc/g1/g1VMOperations.hpp +++ b/src/hotspot/share/gc/g1/g1VMOperations.hpp @@ -48,7 +48,6 @@ class VM_G1TryInitiateConcMark : public VM_GC_Collect_Operation { bool _mark_in_progress; bool _cycle_already_in_progress; bool _whitebox_attached; - bool _terminating; // The concurrent start pause may be cancelled for some reasons. Keep track of // this. bool _gc_succeeded; @@ -63,7 +62,6 @@ class VM_G1TryInitiateConcMark : public VM_GC_Collect_Operation { bool mark_in_progress() const { return _mark_in_progress; } bool cycle_already_in_progress() const { return _cycle_already_in_progress; } bool whitebox_attached() const { return _whitebox_attached; } - bool terminating() const { return _terminating; } bool gc_succeeded() const { return _gc_succeeded && VM_GC_Operation::gc_succeeded(); } }; diff --git a/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp b/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp index 213e8f95d63db..ae2c36e44c56f 100644 --- a/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp +++ b/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp @@ -326,6 +326,11 @@ HeapWord* ParallelScavengeHeap::mem_allocate_work(size_t size, bool is_tlab) { assert(is_in_or_null(op.result()), "result not in heap"); return op.result(); } + + if (is_shutting_down()) { + stall_for_vm_shutdown(); + return nullptr; + } } // Was the gc-overhead reached inside the safepoint? If so, this mutator diff --git a/src/hotspot/share/gc/serial/serialHeap.cpp b/src/hotspot/share/gc/serial/serialHeap.cpp index f26e442706264..89218648fe039 100644 --- a/src/hotspot/share/gc/serial/serialHeap.cpp +++ b/src/hotspot/share/gc/serial/serialHeap.cpp @@ -340,6 +340,11 @@ HeapWord* SerialHeap::mem_allocate_work(size_t size, bool is_tlab) { break; } + if (is_shutting_down()) { + stall_for_vm_shutdown(); + return nullptr; + } + // Give a warning if we seem to be looping forever. if ((QueuedAllocationWarningCount > 0) && (try_count % QueuedAllocationWarningCount == 0)) { diff --git a/src/hotspot/share/gc/shared/collectedHeap.cpp b/src/hotspot/share/gc/shared/collectedHeap.cpp index 9b6956ca75a4d..8ef73c6a0b825 100644 --- a/src/hotspot/share/gc/shared/collectedHeap.cpp +++ b/src/hotspot/share/gc/shared/collectedHeap.cpp @@ -385,6 +385,12 @@ MetaWord* CollectedHeap::satisfy_failed_metadata_allocation(ClassLoaderData* loa if (op.gc_succeeded()) { return op.result(); } + + if (is_shutting_down()) { + stall_for_vm_shutdown(); + return nullptr; + } + loop_count++; if ((QueuedAllocationWarningCount > 0) && (loop_count % QueuedAllocationWarningCount == 0)) { @@ -603,6 +609,23 @@ void CollectedHeap::post_initialize() { initialize_serviceability(); } +bool CollectedHeap::is_shutting_down() const { + return Universe::is_shutting_down(); +} + +void CollectedHeap::stall_for_vm_shutdown() { + assert(is_shutting_down(), "Precondition"); + // Stall the thread (2 seconds) instead of an indefinite wait to avoid deadlock + // if the VM shutdown triggers a GC. + // The 2-seconds sleep is: + // - long enough to keep daemon threads stalled, while the shutdown + // sequence completes in the common case. + // - short enough to avoid excessive stall time if the shutdown itself + // triggers a GC. + JavaThread::current()->sleep(2 * MILLIUNITS); + log_warning(gc, alloc)("%s: Stall for VM-Shutdown timed out; allocation may fail with OOME", Thread::current()->name()); +} + void CollectedHeap::before_exit() { print_tracing_info(); diff --git a/src/hotspot/share/gc/shared/collectedHeap.hpp b/src/hotspot/share/gc/shared/collectedHeap.hpp index f4f5ce7907424..0516091228309 100644 --- a/src/hotspot/share/gc/shared/collectedHeap.hpp +++ b/src/hotspot/share/gc/shared/collectedHeap.hpp @@ -246,6 +246,13 @@ class CollectedHeap : public CHeapObj { // This is the correct place to place such initialization methods. virtual void post_initialize(); + bool is_shutting_down() const; + + // If the VM is shutting down, we may have skipped VM_CollectForAllocation. + // In this case, stall the allocation request briefly in the hope that + // the VM shutdown completes before the allocation request returns. + void stall_for_vm_shutdown(); + void before_exit(); // Stop and resume concurrent GC threads interfering with safepoint operations diff --git a/src/hotspot/share/gc/shared/gcVMOperations.cpp b/src/hotspot/share/gc/shared/gcVMOperations.cpp index 97bae4f6d4418..36aa0c9843dfe 100644 --- a/src/hotspot/share/gc/shared/gcVMOperations.cpp +++ b/src/hotspot/share/gc/shared/gcVMOperations.cpp @@ -111,7 +111,7 @@ bool VM_GC_Operation::doit_prologue() { VM_Heap_Sync_Operation::doit_prologue(); // Check invocations - if (skip_operation()) { + if (skip_operation() || Universe::is_shutting_down()) { // skip collection Heap_lock->unlock(); if (should_use_gclocker()) { diff --git a/src/hotspot/share/memory/universe.cpp b/src/hotspot/share/memory/universe.cpp index 9251fecbb6cc4..a3afcc5ba64b0 100644 --- a/src/hotspot/share/memory/universe.cpp +++ b/src/hotspot/share/memory/universe.cpp @@ -183,6 +183,7 @@ int Universe::_base_vtable_size = 0; bool Universe::_bootstrapping = false; bool Universe::_module_initialized = false; bool Universe::_fully_initialized = false; +volatile bool Universe::_is_shutting_down = false; OopStorage* Universe::_vm_weak = nullptr; OopStorage* Universe::_vm_global = nullptr; @@ -1344,7 +1345,14 @@ static void log_cpu_time() { } void Universe::before_exit() { - log_cpu_time(); + { + // Acquire the Heap_lock to synchronize with VM_Heap_Sync_Operations, + // which may depend on the value of _is_shutting_down flag. + MutexLocker hl(Heap_lock); + log_cpu_time(); + AtomicAccess::release_store(&_is_shutting_down, true); + } + heap()->before_exit(); // Print GC/heap related information. diff --git a/src/hotspot/share/memory/universe.hpp b/src/hotspot/share/memory/universe.hpp index 90874d2392db8..3b1f2523ed845 100644 --- a/src/hotspot/share/memory/universe.hpp +++ b/src/hotspot/share/memory/universe.hpp @@ -128,6 +128,9 @@ class Universe: AllStatic { static bool _module_initialized; // true after call_initPhase2 called static bool _fully_initialized; // true after universe_init and initialize_vtables called + // Shutdown + static volatile bool _is_shutting_down; + // the array of preallocated errors with backtraces static objArrayOop preallocated_out_of_memory_errors(); @@ -324,6 +327,8 @@ class Universe: AllStatic { static bool is_module_initialized() { return _module_initialized; } static bool is_fully_initialized() { return _fully_initialized; } + static bool is_shutting_down() { return AtomicAccess::load_acquire(&_is_shutting_down); } + static bool on_page_boundary(void* addr); static bool should_fill_in_stack_trace(Handle throwable); static void check_alignment(uintx size, uintx alignment, const char* name); diff --git a/src/hotspot/share/services/cpuTimeUsage.cpp b/src/hotspot/share/services/cpuTimeUsage.cpp index d6b01bcbf9ae7..27b5e90fbaf56 100644 --- a/src/hotspot/share/services/cpuTimeUsage.cpp +++ b/src/hotspot/share/services/cpuTimeUsage.cpp @@ -28,6 +28,7 @@ #include "memory/universe.hpp" #include "runtime/globals.hpp" #include "runtime/os.hpp" +#include "runtime/osThread.hpp" #include "runtime/perfData.hpp" #include "runtime/vmThread.hpp" #include "services/cpuTimeUsage.hpp"