# HG changeset patch # Parent 203eeddca2eb5025382991196e9a1f49fdcbfbca diff --git a/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.cpp b/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.cpp --- a/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.cpp @@ -39,6 +39,7 @@ oop o; if (STOREVAL_WRITE_BARRIER) { bool evac; + ShenandoahOOMDuringEvacScope oom_evac_scope; o = _heap->evac_update_with_forwarded(p, evac); if ((ALWAYS_ENQUEUE || evac) && !oopDesc::is_null(o)) { ShenandoahBarrierSet::enqueue(o); @@ -276,6 +277,7 @@ if (evac_in_progress && _heap->in_collection_set(obj) && oopDesc::unsafe_equals(obj, fwd)) { + ShenandoahOOMDuringEvacScope oom_evac_scope; bool evac; oop copy = _heap->evacuate_object(obj, Thread::current(), evac, true /* from write barrier */); if (evac && _heap->is_concurrent_partial_in_progress()) { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.inline.hpp --- a/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.inline.hpp @@ -219,6 +219,7 @@ if (_heap->in_collection_set(obj)) { oop forw = ShenandoahBarrierSet::resolve_forwarded_not_null(obj); if (oopDesc::unsafe_equals(forw, obj)) { + ShenandoahOOMDuringEvacScope oom_evac_scope; bool evac; forw = _heap->evacuate_object(forw, thread, evac); if (evac) { @@ -232,6 +233,7 @@ if (_heap->in_collection_set(obj)) { oop forw = ShenandoahBarrierSet::resolve_forwarded_not_null(obj); if (oopDesc::unsafe_equals(forw, obj)) { + ShenandoahOOMDuringEvacScope oom_evac_scope; bool evac; forw = _heap->evacuate_object(forw, thread, evac); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentThread.cpp b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentThread.cpp --- a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentThread.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentThread.cpp @@ -421,18 +421,6 @@ heap->collector_policy()->set_should_clear_all_soft_refs(true); try_set_alloc_failure_gc(); heap->cancel_concgc(GCCause::_shenandoah_allocation_failure_evac); - - if ((! Thread::current()->is_GC_task_thread()) && (! Thread::current()->is_ConcurrentGC_thread())) { - assert(! Threads_lock->owned_by_self() - || SafepointSynchronize::is_at_safepoint(), "must not hold Threads_lock here"); - ResourceMark rm; - log_info(gc)("%s. Thread \"%s\" waits until evacuation finishes.", - GCCause::to_string(GCCause::_shenandoah_allocation_failure_evac), - Thread::current()->name()); - while (heap->is_evacuation_in_progress()) { // wait. - Thread::current()->_ParkEvent->park(1); - } - } } void ShenandoahConcurrentThread::notify_alloc_failure_waiters() { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp @@ -903,6 +903,7 @@ void work(uint worker_id) { + ShenandoahOOMDuringEvacScope oom_evac_scope; SuspendibleThreadSetJoiner stsj(ShenandoahSuspendibleWorkers); // If concurrent code cache evac is enabled, evacuate it here. @@ -1082,6 +1083,7 @@ } void work(uint worker_id) { + ShenandoahOOMDuringEvacScope oom_evac_scope; ShenandoahEvacuateUpdateRootsClosure cl; if (ShenandoahConcurrentEvacCodeRoots) { @@ -2902,3 +2904,11 @@ memory_pools.append(_dummy_pool); return memory_pools; } + +void ShenandoahHeap::enter_evacuation() { + _oom_evac_handler.enter_evacuation(); +} + +void ShenandoahHeap::leave_evacuation() { + _oom_evac_handler.leave_evacuation(); +} diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp @@ -26,6 +26,7 @@ #include "gc/shared/markBitMap.hpp" #include "gc/shenandoah/shenandoahHeapLock.hpp" +#include "gc/shenandoah/shenandoahOOMDuringEvacHandler.hpp" #include "gc/shenandoah/shenandoahSharedVariables.hpp" #include "gc/shenandoah/shenandoahWorkGroup.hpp" #include "services/memoryManager.hpp" @@ -279,6 +280,8 @@ MemoryPool* _memory_pool; MemoryPool* _dummy_pool; + ShenandoahOOMDuringEvacHandler _oom_evac_handler; + #ifdef ASSERT int _heap_expansion_count; #endif @@ -525,6 +528,11 @@ BoolObjectClosure* is_alive_closure(); + // Call before starting evacuation. + void enter_evacuation(); + // Call after finished with evacuation. + void leave_evacuation(); + private: template inline void marked_object_iterate(ShenandoahHeapRegion* region, T* cl, HeapWord* limit); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.inline.hpp --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.inline.hpp @@ -249,6 +249,7 @@ inline void ShenandoahHeap::clear_cancelled_concgc() { _cancelled_concgc.set(CANCELLABLE); + _oom_evac_handler.clear(); } inline HeapWord* ShenandoahHeap::allocate_from_gclab(Thread* thread, size_t size) { @@ -281,16 +282,16 @@ bool alloc_from_gclab = true; HeapWord* filler; #ifdef ASSERT - // Checking that current Java thread does not hold Threads_lock when we get here. - // If that ever be the case, we'd deadlock in oom_during_evacuation. - if ((! Thread::current()->is_GC_task_thread()) && (! Thread::current()->is_ConcurrentGC_thread())) { - assert(! Threads_lock->owned_by_self() - || SafepointSynchronize::is_at_safepoint(), "must not hold Threads_lock here"); + + if (Thread::current()->is_oom_during_evac()) { + // This thread went through the OOM during evac protocol and it is safe to return + // the forward pointer. It must not attempt to evacuate any more. + return ShenandoahBarrierSet::resolve_forwarded(p); } + assert(thread->is_evac_allowed(), "must be enclosed in ShenandoahOOMDuringEvacHandler"); + if (ShenandoahOOMDuringEvacALot && - (! Thread::current()->is_GC_task_thread()) && - (! Thread::current()->is_ConcurrentGC_thread()) && (os::random() & 1) == 0) { // Simulate OOM every ~2nd slow-path call filler = NULL; } else { @@ -306,9 +307,9 @@ if (filler == NULL) { concurrent_thread()->handle_alloc_failure_evac(); - // If this is a Java thread, it should have waited - // until all GC threads are done, and then we - // return the forwardee. + + _oom_evac_handler.handle_out_of_memory_during_evacuation(); + return ShenandoahBarrierSet::resolve_forwarded(p); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahOOMDuringEvacHandler.cpp b/src/hotspot/share/gc/shenandoah/shenandoahOOMDuringEvacHandler.cpp new file mode 100644 --- /dev/null +++ b/src/hotspot/share/gc/shenandoah/shenandoahOOMDuringEvacHandler.cpp @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2018, Red Hat, Inc. and/or its affiliates. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" + +#include "gc/shenandoah/shenandoahOOMDuringEvacHandler.hpp" + +const juint ShenandoahOOMDuringEvacHandler::OOM_MARKER_MASK = 0x80000000; + +ShenandoahOOMDuringEvacHandler::ShenandoahOOMDuringEvacHandler() : + _threads_in_evac(0) { +} + +void ShenandoahOOMDuringEvacHandler::wait_for_no_evac_threads() { + while ((OrderAccess::load_acquire(&_threads_in_evac) & ~OOM_MARKER_MASK) != 0) { + SpinPause(); + } + // At this point we are sure that no threads can evacuate anything. Raise + // the thread-local oom_during_evac flag to indicate that any attempt + // to evacuate should simply return the forwarding pointer instead (which is safe now). + Thread::current()->set_oom_during_evac(true); +} + +void ShenandoahOOMDuringEvacHandler::enter_evacuation() { + juint threads_in_evac = OrderAccess::load_acquire(&_threads_in_evac); + + assert(!Thread::current()->is_evac_allowed(), "sanity"); + assert(!Thread::current()->is_oom_during_evac(), "TL oom-during-evac must not be set"); + + if ((threads_in_evac & OOM_MARKER_MASK) != 0) { + wait_for_no_evac_threads(); + return; + } + + while (true) { + juint other = Atomic::cmpxchg(threads_in_evac + 1, &_threads_in_evac, threads_in_evac); + if (other == threads_in_evac) { + // Success: caller may safely enter evacuation + DEBUG_ONLY(Thread::current()->set_evac_allowed(true)); + return; + } else { + // Failure: + // - if offender has OOM_MARKER_MASK, then loop until no more threads in evac + // - otherwise re-try CAS + if ((other & OOM_MARKER_MASK) != 0) { + wait_for_no_evac_threads(); + return; + } + threads_in_evac = other; + } + } +} + +void ShenandoahOOMDuringEvacHandler::leave_evacuation() { + if (!Thread::current()->is_oom_during_evac()) { + assert((OrderAccess::load_acquire(&_threads_in_evac) & ~OOM_MARKER_MASK) > 0, "sanity"); + // NOTE: It's ok to simply decrement, even with mask set, because it's unsigned. + Atomic::dec(&_threads_in_evac); + } else { + // If we get here, the current thread has already gone through the + // OOM-during-evac protocol and has thus either never entered or successfully left + // the evacuation region. Simply flip its TL oom-during-evac flag back off. + Thread::current()->set_oom_during_evac(false); + } + DEBUG_ONLY(Thread::current()->set_evac_allowed(false)); + assert(!Thread::current()->is_oom_during_evac(), "TL oom-during-evac must be turned off"); +} + +void ShenandoahOOMDuringEvacHandler::handle_out_of_memory_during_evacuation() { + assert(Thread::current()->is_evac_allowed(), "sanity"); + assert(!Thread::current()->is_oom_during_evac(), "TL oom-during-evac must not be set"); + + juint threads_in_evac = OrderAccess::load_acquire(&_threads_in_evac); + while (true) { + juint other = Atomic::cmpxchg((threads_in_evac - 1) | OOM_MARKER_MASK, + &_threads_in_evac, threads_in_evac); + if (other == threads_in_evac) { + // Success: wait for other threads to get out of the protocol and return. + wait_for_no_evac_threads(); + return; + } else { + // Failure: try again with updated new value. + threads_in_evac = other; + } + } +} + +void ShenandoahOOMDuringEvacHandler::clear() { + assert(ShenandoahSafepoint::is_at_shenandoah_safepoint(), "must be at a safepoint"); + assert((_threads_in_evac & ~OOM_MARKER_MASK) == 0, "sanity"); + _threads_in_evac = 0; +} + +ShenandoahOOMDuringEvacScope::ShenandoahOOMDuringEvacScope() { + ShenandoahHeap::heap()->enter_evacuation(); +} + +ShenandoahOOMDuringEvacScope::~ShenandoahOOMDuringEvacScope() { + ShenandoahHeap::heap()->leave_evacuation(); +} diff --git a/src/hotspot/share/gc/shenandoah/shenandoahOOMDuringEvacHandler.hpp b/src/hotspot/share/gc/shenandoah/shenandoahOOMDuringEvacHandler.hpp new file mode 100644 --- /dev/null +++ b/src/hotspot/share/gc/shenandoah/shenandoahOOMDuringEvacHandler.hpp @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2018, Red Hat, Inc. and/or its affiliates. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_VM_GC_SHENANDOAH_SHENANDOAHOOMDURINGEVACHANDLER_HPP +#define SHARE_VM_GC_SHENANDOAH_SHENANDOAHOOMDURINGEVACHANDLER_HPP + +/** + * Provides safe handling of out-of-memory situations during evacuation. + * + * When a Java thread encounters out-of-memory while evacuating an object in a + * write-barrier (i.e. it can not copy the object to to-space), another thread + * (Java or GC thread) may still succeed to evac the same object concurrently, thus + * potentially leaving the OOMing thread with a from-space copy of an object, even + * though a to-space copy may (eventually) exist. If it then goes on to write to + * that from-space-copy, it will corrupt memory/violate the JMM. + * + * The way it is solved here is to maintain a counter of threads inside the + * 'evacuation path'. The 'evacuation path' is the part of evacuation that does the actual + * allocation, copying and CASing of the copy object, and is protected by this + * OOM-during-evac-handler. + * + *Upon entry of the evac-path, any threads will attempt to increase the counter, + * using a CAS. Depending on the result of the CAS: + * - success: carry on with evac + * - failure: + * - if offending value is a valid counter, then try again + * - if offending value is OOM-during-evac special value: loop until + * counter drops to 0, then exit with read-barrier + * + * Upon exit, any threads will decrease the counter using atomic dec. + * + * Upon OOM-during-evac, any thread will attempt to CAS OOM-during-evac + * special value into the counter. Depending on result: + * - success: busy-loop until counter drops to zero, then exit with RB + * - failure: + * - offender is valid counter update: try again + * - offender is OOM-during-evac: busy loop until counter drops to + * zero, then exit with RB + */ +class ShenandoahOOMDuringEvacHandler { +private: + static const juint OOM_MARKER_MASK; + + volatile juint _threads_in_evac; + + void wait_for_no_evac_threads(); + +public: + ShenandoahOOMDuringEvacHandler(); + + /** + * Attempt to enter the protected evacuation path. + * + * When this returns true, it is safe to continue with normal evacuation. + * When this method returns false, evacuation must not be entered, and caller + * may safely continue with a read-barrier (if Java thread). + */ + void enter_evacuation(); + + /** + * Leave evacuation path. + */ + void leave_evacuation(); + + /** + * Signal out-of-memory during evacuation. It will prevent any other threads + * from entering the evacuation path, then wait until all threads have left the + * evacuation path, and then return. It is then safe to continue with a read-barrier. + */ + void handle_out_of_memory_during_evacuation(); + + void clear(); +}; + +class ShenandoahOOMDuringEvacScope : public StackObj { +public: + ShenandoahOOMDuringEvacScope(); + ~ShenandoahOOMDuringEvacScope(); +}; + +#endif // SHARE_VM_GC_SHENANDOAH_SHENANDOAHOOMDURINGEVACHANDLER_HPP diff --git a/src/hotspot/share/gc/shenandoah/shenandoahPartialGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahPartialGC.cpp --- a/src/hotspot/share/gc/shenandoah/shenandoahPartialGC.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahPartialGC.cpp @@ -120,6 +120,8 @@ ShenandoahObjToScanQueueSet* queues = _heap->partial_gc()->task_queues(); ShenandoahObjToScanQueue* q = queues->queue(worker_id); + ShenandoahOOMDuringEvacScope oom_evac_scope; + // Step 1: Process ordinary GC roots. { ShenandoahPartialEvacuateUpdateRootsClosure roots_cl(q); @@ -149,6 +151,8 @@ if (partial_gc->check_and_handle_cancelled_gc(_terminator)) return; + ShenandoahOOMDuringEvacScope oom_evac_scope; + ShenandoahPartialEvacuateUpdateHeapClosure cl(q); // Step 2: Process all root regions. @@ -184,6 +188,8 @@ ShenandoahObjToScanQueueSet* queues = partial_gc->task_queues(); ShenandoahObjToScanQueue* q = queues->queue(worker_id); + ShenandoahOOMDuringEvacScope oom_evac_scope; + // Drain outstanding SATB queues. { ShenandoahPartialSATBBufferClosure satb_cl(q); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahTraversalGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahTraversalGC.cpp --- a/src/hotspot/share/gc/shenandoah/shenandoahTraversalGC.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahTraversalGC.cpp @@ -167,6 +167,7 @@ _heap(ShenandoahHeap::heap()) {} void work(uint worker_id) { + ShenandoahOOMDuringEvacScope oom_evac_scope; ShenandoahObjToScanQueueSet* queues = _heap->traversal_gc()->task_queues(); ShenandoahObjToScanQueue* q = queues->queue(worker_id); @@ -202,6 +203,7 @@ _heap(ShenandoahHeap::heap()) {} void work(uint worker_id) { + ShenandoahOOMDuringEvacScope oom_evac_scope; ShenandoahTraversalGC* traversal_gc = _heap->traversal_gc(); ShenandoahObjToScanQueueSet* queues = traversal_gc->task_queues(); ShenandoahObjToScanQueue* q = queues->queue(worker_id); @@ -224,6 +226,7 @@ _heap(ShenandoahHeap::heap()) {} void work(uint worker_id) { + ShenandoahOOMDuringEvacScope oom_evac_scope; ShenandoahTraversalGC* traversal_gc = _heap->traversal_gc(); ShenandoahObjToScanQueueSet* queues = traversal_gc->task_queues(); diff --git a/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp b/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp --- a/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp @@ -369,7 +369,7 @@ experimental(bool, ShenandoahCommonGCStateLoads, false, \ "Enable commonning for GC state loads in generated code.") \ \ - diagnostic(bool, ShenandoahAsmWB, true, \ + diagnostic(bool, ShenandoahAsmWB, false, \ "Enable/disable inline assembler write barrier code.") \ \ diagnostic(bool, ShenandoahConcMarkGC, true, \ diff --git a/src/hotspot/share/runtime/thread.cpp b/src/hotspot/share/runtime/thread.cpp --- a/src/hotspot/share/runtime/thread.cpp +++ b/src/hotspot/share/runtime/thread.cpp @@ -217,7 +217,7 @@ // JavaThread -Thread::Thread() { +Thread::Thread() : _oom_during_evac(0) { // stack and get_thread set_stack_base(NULL); set_stack_size(0); @@ -309,6 +309,32 @@ #endif // ASSERT } +void Thread::set_oom_during_evac(bool oom) { + if (oom) { + _oom_during_evac |= 1; + } else { + _oom_during_evac &= ~1; + } +} + +bool Thread::is_oom_during_evac() const { + return (_oom_during_evac & 1) == 1; +} + +#ifdef ASSERT +void Thread::set_evac_allowed(bool evac_allowed) { + if (evac_allowed) { + _oom_during_evac |= 2; + } else { + _oom_during_evac &= ~2; + } +} + +bool Thread::is_evac_allowed() const { + return (_oom_during_evac & 2) == 2; +} +#endif + void Thread::initialize_thread_current() { #ifndef USE_LIBRARY_BASED_TLS_ONLY assert(_thr_current == NULL, "Thread::current already initialized"); diff --git a/src/hotspot/share/runtime/thread.hpp b/src/hotspot/share/runtime/thread.hpp --- a/src/hotspot/share/runtime/thread.hpp +++ b/src/hotspot/share/runtime/thread.hpp @@ -334,6 +334,8 @@ int _vm_operation_started_count; // VM_Operation support int _vm_operation_completed_count; // VM_Operation support + char _oom_during_evac; + ObjectMonitor* _current_pending_monitor; // ObjectMonitor this thread // is waiting to lock bool _current_pending_monitor_is_from_java; // locking is from Java code @@ -441,6 +443,14 @@ inline void set_trace_flag(); inline void clear_trace_flag(); + bool is_oom_during_evac() const; + void set_oom_during_evac(bool oom); + +#ifdef ASSERT + bool is_evac_allowed() const; + void set_evac_allowed(bool evac_allowed); +#endif + // Support for Unhandled Oop detection // Add the field for both, fastdebug and debug, builds to keep // Thread's fields layout the same.