| /* |
| * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * 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/shared/gcId.hpp" |
| #include "gc/shared/gcLocker.hpp" |
| #include "gc/shared/gcVMOperations.hpp" |
| #include "gc/shared/isGCActiveMark.hpp" |
| #include "gc/x/xAbort.inline.hpp" |
| #include "gc/x/xBreakpoint.hpp" |
| #include "gc/x/xCollectedHeap.hpp" |
| #include "gc/x/xDriver.hpp" |
| #include "gc/x/xHeap.inline.hpp" |
| #include "gc/x/xMessagePort.inline.hpp" |
| #include "gc/x/xServiceability.hpp" |
| #include "gc/x/xStat.hpp" |
| #include "gc/x/xVerify.hpp" |
| #include "logging/log.hpp" |
| #include "memory/universe.hpp" |
| #include "runtime/threads.hpp" |
| #include "runtime/vmOperations.hpp" |
| #include "runtime/vmThread.hpp" |
| |
| static const XStatPhaseCycle XPhaseCycle("Garbage Collection Cycle"); |
| static const XStatPhasePause XPhasePauseMarkStart("Pause Mark Start"); |
| static const XStatPhaseConcurrent XPhaseConcurrentMark("Concurrent Mark"); |
| static const XStatPhaseConcurrent XPhaseConcurrentMarkContinue("Concurrent Mark Continue"); |
| static const XStatPhaseConcurrent XPhaseConcurrentMarkFree("Concurrent Mark Free"); |
| static const XStatPhasePause XPhasePauseMarkEnd("Pause Mark End"); |
| static const XStatPhaseConcurrent XPhaseConcurrentProcessNonStrongReferences("Concurrent Process Non-Strong References"); |
| static const XStatPhaseConcurrent XPhaseConcurrentResetRelocationSet("Concurrent Reset Relocation Set"); |
| static const XStatPhaseConcurrent XPhaseConcurrentSelectRelocationSet("Concurrent Select Relocation Set"); |
| static const XStatPhasePause XPhasePauseRelocateStart("Pause Relocate Start"); |
| static const XStatPhaseConcurrent XPhaseConcurrentRelocated("Concurrent Relocate"); |
| static const XStatCriticalPhase XCriticalPhaseGCLockerStall("GC Locker Stall", false /* verbose */); |
| static const XStatSampler XSamplerJavaThreads("System", "Java Threads", XStatUnitThreads); |
| |
| XDriverRequest::XDriverRequest() : |
| XDriverRequest(GCCause::_no_gc) {} |
| |
| XDriverRequest::XDriverRequest(GCCause::Cause cause) : |
| XDriverRequest(cause, ConcGCThreads) {} |
| |
| XDriverRequest::XDriverRequest(GCCause::Cause cause, uint nworkers) : |
| _cause(cause), |
| _nworkers(nworkers) {} |
| |
| bool XDriverRequest::operator==(const XDriverRequest& other) const { |
| return _cause == other._cause; |
| } |
| |
| GCCause::Cause XDriverRequest::cause() const { |
| return _cause; |
| } |
| |
| uint XDriverRequest::nworkers() const { |
| return _nworkers; |
| } |
| |
| class VM_XOperation : public VM_Operation { |
| private: |
| const uint _gc_id; |
| bool _gc_locked; |
| bool _success; |
| |
| public: |
| VM_XOperation() : |
| _gc_id(GCId::current()), |
| _gc_locked(false), |
| _success(false) {} |
| |
| virtual bool needs_inactive_gc_locker() const { |
| // An inactive GC locker is needed in operations where we change the bad |
| // mask or move objects. Changing the bad mask will invalidate all oops, |
| // which makes it conceptually the same thing as moving all objects. |
| return false; |
| } |
| |
| virtual bool skip_thread_oop_barriers() const { |
| return true; |
| } |
| |
| virtual bool do_operation() = 0; |
| |
| virtual bool doit_prologue() { |
| Heap_lock->lock(); |
| return true; |
| } |
| |
| virtual void doit() { |
| // Abort if GC locker state is incompatible |
| if (needs_inactive_gc_locker() && GCLocker::check_active_before_gc()) { |
| _gc_locked = true; |
| return; |
| } |
| |
| // Setup GC id and active marker |
| GCIdMark gc_id_mark(_gc_id); |
| IsGCActiveMark gc_active_mark; |
| |
| // Verify before operation |
| XVerify::before_zoperation(); |
| |
| // Execute operation |
| _success = do_operation(); |
| |
| // Update statistics |
| XStatSample(XSamplerJavaThreads, Threads::number_of_threads()); |
| } |
| |
| virtual void doit_epilogue() { |
| Heap_lock->unlock(); |
| } |
| |
| bool gc_locked() const { |
| return _gc_locked; |
| } |
| |
| bool success() const { |
| return _success; |
| } |
| }; |
| |
| class VM_XMarkStart : public VM_XOperation { |
| public: |
| virtual VMOp_Type type() const { |
| return VMOp_XMarkStart; |
| } |
| |
| virtual bool needs_inactive_gc_locker() const { |
| return true; |
| } |
| |
| virtual bool do_operation() { |
| XStatTimer timer(XPhasePauseMarkStart); |
| XServiceabilityPauseTracer tracer; |
| |
| XCollectedHeap::heap()->increment_total_collections(true /* full */); |
| |
| XHeap::heap()->mark_start(); |
| return true; |
| } |
| }; |
| |
| class VM_XMarkEnd : public VM_XOperation { |
| public: |
| virtual VMOp_Type type() const { |
| return VMOp_XMarkEnd; |
| } |
| |
| virtual bool do_operation() { |
| XStatTimer timer(XPhasePauseMarkEnd); |
| XServiceabilityPauseTracer tracer; |
| return XHeap::heap()->mark_end(); |
| } |
| }; |
| |
| class VM_XRelocateStart : public VM_XOperation { |
| public: |
| virtual VMOp_Type type() const { |
| return VMOp_XRelocateStart; |
| } |
| |
| virtual bool needs_inactive_gc_locker() const { |
| return true; |
| } |
| |
| virtual bool do_operation() { |
| XStatTimer timer(XPhasePauseRelocateStart); |
| XServiceabilityPauseTracer tracer; |
| XHeap::heap()->relocate_start(); |
| return true; |
| } |
| }; |
| |
| class VM_XVerify : public VM_Operation { |
| public: |
| virtual VMOp_Type type() const { |
| return VMOp_XVerify; |
| } |
| |
| virtual bool skip_thread_oop_barriers() const { |
| return true; |
| } |
| |
| virtual void doit() { |
| XVerify::after_weak_processing(); |
| } |
| }; |
| |
| XDriver::XDriver() : |
| _gc_cycle_port(), |
| _gc_locker_port() { |
| set_name("XDriver"); |
| create_and_start(); |
| } |
| |
| bool XDriver::is_busy() const { |
| return _gc_cycle_port.is_busy(); |
| } |
| |
| void XDriver::collect(const XDriverRequest& request) { |
| switch (request.cause()) { |
| case GCCause::_heap_dump: |
| case GCCause::_heap_inspection: |
| case GCCause::_wb_young_gc: |
| case GCCause::_wb_full_gc: |
| case GCCause::_dcmd_gc_run: |
| case GCCause::_java_lang_system_gc: |
| case GCCause::_full_gc_alot: |
| case GCCause::_scavenge_alot: |
| case GCCause::_jvmti_force_gc: |
| case GCCause::_metadata_GC_clear_soft_refs: |
| case GCCause::_codecache_GC_aggressive: |
| // Start synchronous GC |
| _gc_cycle_port.send_sync(request); |
| break; |
| |
| case GCCause::_z_timer: |
| case GCCause::_z_warmup: |
| case GCCause::_z_allocation_rate: |
| case GCCause::_z_allocation_stall: |
| case GCCause::_z_proactive: |
| case GCCause::_z_high_usage: |
| case GCCause::_codecache_GC_threshold: |
| case GCCause::_metadata_GC_threshold: |
| // Start asynchronous GC |
| _gc_cycle_port.send_async(request); |
| break; |
| |
| case GCCause::_gc_locker: |
| // Restart VM operation previously blocked by the GC locker |
| _gc_locker_port.signal(); |
| break; |
| |
| case GCCause::_wb_breakpoint: |
| XBreakpoint::start_gc(); |
| _gc_cycle_port.send_async(request); |
| break; |
| |
| default: |
| // Other causes not supported |
| fatal("Unsupported GC cause (%s)", GCCause::to_string(request.cause())); |
| break; |
| } |
| } |
| |
| template <typename T> |
| bool XDriver::pause() { |
| for (;;) { |
| T op; |
| VMThread::execute(&op); |
| if (op.gc_locked()) { |
| // Wait for GC to become unlocked and restart the VM operation |
| XStatTimer timer(XCriticalPhaseGCLockerStall); |
| _gc_locker_port.wait(); |
| continue; |
| } |
| |
| // Notify VM operation completed |
| _gc_locker_port.ack(); |
| |
| return op.success(); |
| } |
| } |
| |
| void XDriver::pause_mark_start() { |
| pause<VM_XMarkStart>(); |
| } |
| |
| void XDriver::concurrent_mark() { |
| XStatTimer timer(XPhaseConcurrentMark); |
| XBreakpoint::at_after_marking_started(); |
| XHeap::heap()->mark(true /* initial */); |
| XBreakpoint::at_before_marking_completed(); |
| } |
| |
| bool XDriver::pause_mark_end() { |
| return pause<VM_XMarkEnd>(); |
| } |
| |
| void XDriver::concurrent_mark_continue() { |
| XStatTimer timer(XPhaseConcurrentMarkContinue); |
| XHeap::heap()->mark(false /* initial */); |
| } |
| |
| void XDriver::concurrent_mark_free() { |
| XStatTimer timer(XPhaseConcurrentMarkFree); |
| XHeap::heap()->mark_free(); |
| } |
| |
| void XDriver::concurrent_process_non_strong_references() { |
| XStatTimer timer(XPhaseConcurrentProcessNonStrongReferences); |
| XBreakpoint::at_after_reference_processing_started(); |
| XHeap::heap()->process_non_strong_references(); |
| } |
| |
| void XDriver::concurrent_reset_relocation_set() { |
| XStatTimer timer(XPhaseConcurrentResetRelocationSet); |
| XHeap::heap()->reset_relocation_set(); |
| } |
| |
| void XDriver::pause_verify() { |
| if (ZVerifyRoots || ZVerifyObjects) { |
| VM_XVerify op; |
| VMThread::execute(&op); |
| } |
| } |
| |
| void XDriver::concurrent_select_relocation_set() { |
| XStatTimer timer(XPhaseConcurrentSelectRelocationSet); |
| XHeap::heap()->select_relocation_set(); |
| } |
| |
| void XDriver::pause_relocate_start() { |
| pause<VM_XRelocateStart>(); |
| } |
| |
| void XDriver::concurrent_relocate() { |
| XStatTimer timer(XPhaseConcurrentRelocated); |
| XHeap::heap()->relocate(); |
| } |
| |
| void XDriver::check_out_of_memory() { |
| XHeap::heap()->check_out_of_memory(); |
| } |
| |
| static bool should_clear_soft_references(const XDriverRequest& request) { |
| // Clear soft references if implied by the GC cause |
| if (request.cause() == GCCause::_wb_full_gc || |
| request.cause() == GCCause::_metadata_GC_clear_soft_refs || |
| request.cause() == GCCause::_z_allocation_stall) { |
| // Clear |
| return true; |
| } |
| |
| // Don't clear |
| return false; |
| } |
| |
| static uint select_active_worker_threads_dynamic(const XDriverRequest& request) { |
| // Use requested number of worker threads |
| return request.nworkers(); |
| } |
| |
| static uint select_active_worker_threads_static(const XDriverRequest& request) { |
| const GCCause::Cause cause = request.cause(); |
| const uint nworkers = request.nworkers(); |
| |
| // Boost number of worker threads if implied by the GC cause |
| if (cause == GCCause::_wb_full_gc || |
| cause == GCCause::_java_lang_system_gc || |
| cause == GCCause::_metadata_GC_clear_soft_refs || |
| cause == GCCause::_z_allocation_stall) { |
| // Boost |
| const uint boosted_nworkers = MAX2(nworkers, ParallelGCThreads); |
| return boosted_nworkers; |
| } |
| |
| // Use requested number of worker threads |
| return nworkers; |
| } |
| |
| static uint select_active_worker_threads(const XDriverRequest& request) { |
| if (UseDynamicNumberOfGCThreads) { |
| return select_active_worker_threads_dynamic(request); |
| } else { |
| return select_active_worker_threads_static(request); |
| } |
| } |
| |
| class XDriverGCScope : public StackObj { |
| private: |
| GCIdMark _gc_id; |
| GCCause::Cause _gc_cause; |
| GCCauseSetter _gc_cause_setter; |
| XStatTimer _timer; |
| XServiceabilityCycleTracer _tracer; |
| |
| public: |
| XDriverGCScope(const XDriverRequest& request) : |
| _gc_id(), |
| _gc_cause(request.cause()), |
| _gc_cause_setter(XCollectedHeap::heap(), _gc_cause), |
| _timer(XPhaseCycle), |
| _tracer() { |
| // Update statistics |
| XStatCycle::at_start(); |
| |
| // Set up soft reference policy |
| const bool clear = should_clear_soft_references(request); |
| XHeap::heap()->set_soft_reference_policy(clear); |
| |
| // Select number of worker threads to use |
| const uint nworkers = select_active_worker_threads(request); |
| XHeap::heap()->set_active_workers(nworkers); |
| } |
| |
| ~XDriverGCScope() { |
| // Update statistics |
| XStatCycle::at_end(_gc_cause, XHeap::heap()->active_workers()); |
| |
| // Update data used by soft reference policy |
| Universe::heap()->update_capacity_and_used_at_gc(); |
| |
| // Signal that we have completed a visit to all live objects |
| Universe::heap()->record_whole_heap_examined_timestamp(); |
| } |
| }; |
| |
| // Macro to execute a termination check after a concurrent phase. Note |
| // that it's important that the termination check comes after the call |
| // to the function f, since we can't abort between pause_relocate_start() |
| // and concurrent_relocate(). We need to let concurrent_relocate() call |
| // abort_page() on the remaining entries in the relocation set. |
| #define concurrent(f) \ |
| do { \ |
| concurrent_##f(); \ |
| if (should_terminate()) { \ |
| return; \ |
| } \ |
| } while (false) |
| |
| void XDriver::gc(const XDriverRequest& request) { |
| XDriverGCScope scope(request); |
| |
| // Phase 1: Pause Mark Start |
| pause_mark_start(); |
| |
| // Phase 2: Concurrent Mark |
| concurrent(mark); |
| |
| // Phase 3: Pause Mark End |
| while (!pause_mark_end()) { |
| // Phase 3.5: Concurrent Mark Continue |
| concurrent(mark_continue); |
| } |
| |
| // Phase 4: Concurrent Mark Free |
| concurrent(mark_free); |
| |
| // Phase 5: Concurrent Process Non-Strong References |
| concurrent(process_non_strong_references); |
| |
| // Phase 6: Concurrent Reset Relocation Set |
| concurrent(reset_relocation_set); |
| |
| // Phase 7: Pause Verify |
| pause_verify(); |
| |
| // Phase 8: Concurrent Select Relocation Set |
| concurrent(select_relocation_set); |
| |
| // Phase 9: Pause Relocate Start |
| pause_relocate_start(); |
| |
| // Phase 10: Concurrent Relocate |
| concurrent(relocate); |
| } |
| |
| void XDriver::run_service() { |
| // Main loop |
| while (!should_terminate()) { |
| // Wait for GC request |
| const XDriverRequest request = _gc_cycle_port.receive(); |
| if (request.cause() == GCCause::_no_gc) { |
| continue; |
| } |
| |
| XBreakpoint::at_before_gc(); |
| |
| // Run GC |
| gc(request); |
| |
| if (should_terminate()) { |
| // Abort |
| break; |
| } |
| |
| // Notify GC completed |
| _gc_cycle_port.ack(); |
| |
| // Check for out of memory condition |
| check_out_of_memory(); |
| |
| XBreakpoint::at_after_gc(); |
| } |
| } |
| |
| void XDriver::stop_service() { |
| XAbort::abort(); |
| _gc_cycle_port.send_async(GCCause::_no_gc); |
| } |