| /* | |
| * Copyright (c) 2013, 2021, Red Hat, Inc. All rights reserved. | |
| * Copyright (C) 2022, Tencent. All rights reserved. | |
| * Copyright Amazon.com Inc. 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 "gc/shenandoah/shenandoahAsserts.hpp" | |
| #include "gc/shenandoah/shenandoahCollectorPolicy.hpp" | |
| #include "gc/shenandoah/shenandoahConcurrentGC.hpp" | |
| #include "gc/shenandoah/shenandoahDegeneratedGC.hpp" | |
| #include "gc/shenandoah/shenandoahFreeSet.hpp" | |
| #include "gc/shenandoah/shenandoahFullGC.hpp" | |
| #include "gc/shenandoah/shenandoahGeneration.hpp" | |
| #include "gc/shenandoah/shenandoahGenerationalControlThread.hpp" | |
| #include "gc/shenandoah/shenandoahGenerationalHeap.hpp" | |
| #include "gc/shenandoah/shenandoahHeap.inline.hpp" | |
| #include "gc/shenandoah/shenandoahMonitoringSupport.hpp" | |
| #include "gc/shenandoah/shenandoahOldGC.hpp" | |
| #include "gc/shenandoah/shenandoahOldGeneration.hpp" | |
| #include "gc/shenandoah/shenandoahPacer.inline.hpp" | |
| #include "gc/shenandoah/shenandoahUtils.hpp" | |
| #include "gc/shenandoah/shenandoahYoungGeneration.hpp" | |
| #include "logging/log.hpp" | |
| #include "memory/metaspaceStats.hpp" | |
| #include "memory/metaspaceUtils.hpp" | |
| #include "runtime/atomic.hpp" | |
| #include "utilities/events.hpp" | |
| ShenandoahGenerationalControlThread::ShenandoahGenerationalControlThread() : | |
| _control_lock(Mutex::nosafepoint - 2, "ShenandoahGCRequest_lock", true), | |
| _requested_gc_cause(GCCause::_no_gc), | |
| _requested_generation(nullptr), | |
| _gc_mode(none), | |
| _degen_point(ShenandoahGC::_degenerated_unset), | |
| _heap(ShenandoahGenerationalHeap::heap()), | |
| _age_period(0) { | |
| shenandoah_assert_generational(); | |
| set_name("Shenandoah Control Thread"); | |
| create_and_start(); | |
| } | |
| void ShenandoahGenerationalControlThread::run_service() { | |
| const int64_t wait_ms = ShenandoahPacing ? ShenandoahControlIntervalMin : 0; | |
| ShenandoahGCRequest request; | |
| while (!should_terminate()) { | |
| // This control loop iteration has seen this much allocation. | |
| const size_t allocs_seen = reset_allocs_seen(); | |
| // Figure out if we have pending requests. | |
| check_for_request(request); | |
| if (request.cause == GCCause::_shenandoah_stop_vm) { | |
| break; | |
| } | |
| if (request.cause != GCCause::_no_gc) { | |
| run_gc_cycle(request); | |
| } else { | |
| // Report to pacer that we have seen this many words allocated | |
| if (ShenandoahPacing && (allocs_seen > 0)) { | |
| _heap->pacer()->report_alloc(allocs_seen); | |
| } | |
| } | |
| // If the cycle was cancelled, continue the next iteration to deal with it. Otherwise, | |
| // if there was no other cycle requested, cleanup and wait for the next request. | |
| if (!_heap->cancelled_gc()) { | |
| MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag); | |
| if (_requested_gc_cause == GCCause::_no_gc) { | |
| set_gc_mode(ml, none); | |
| ml.wait(wait_ms); | |
| } | |
| } | |
| } | |
| // In case any threads are waiting for a cycle to happen, notify them so they observe the shutdown. | |
| notify_gc_waiters(); | |
| notify_alloc_failure_waiters(); | |
| set_gc_mode(stopped); | |
| } | |
| void ShenandoahGenerationalControlThread::stop_service() { | |
| log_debug(gc, thread)("Stopping control thread"); | |
| MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag); | |
| _heap->cancel_gc(GCCause::_shenandoah_stop_vm); | |
| _requested_gc_cause = GCCause::_shenandoah_stop_vm; | |
| notify_cancellation(ml, GCCause::_shenandoah_stop_vm); | |
| // We can't wait here because it may interfere with the active cycle's ability | |
| // to reach a safepoint (this runs on a java thread). | |
| } | |
| void ShenandoahGenerationalControlThread::check_for_request(ShenandoahGCRequest& request) { | |
| // Hold the lock while we read request cause and generation | |
| MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag); | |
| if (_heap->cancelled_gc()) { | |
| // The previous request was cancelled. Either it was cancelled for an allocation | |
| // failure (degenerated cycle), or old marking was cancelled to run a young collection. | |
| // In either case, the correct generation for the next cycle can be determined by | |
| // the cancellation cause. | |
| request.cause = _heap->cancelled_cause(); | |
| if (request.cause == GCCause::_shenandoah_concurrent_gc) { | |
| request.generation = _heap->young_generation(); | |
| _heap->clear_cancelled_gc(false); | |
| } | |
| } else { | |
| request.cause = _requested_gc_cause; | |
| request.generation = _requested_generation; | |
| // Only clear these if we made a request from them. In the case of a cancelled gc, | |
| // we do not want to inadvertently lose this pending request. | |
| _requested_gc_cause = GCCause::_no_gc; | |
| _requested_generation = nullptr; | |
| } | |
| if (request.cause == GCCause::_no_gc || request.cause == GCCause::_shenandoah_stop_vm) { | |
| return; | |
| } | |
| GCMode mode; | |
| if (ShenandoahCollectorPolicy::is_allocation_failure(request.cause)) { | |
| mode = prepare_for_allocation_failure_gc(request); | |
| } else if (ShenandoahCollectorPolicy::is_explicit_gc(request.cause)) { | |
| mode = prepare_for_explicit_gc(request); | |
| } else { | |
| mode = prepare_for_concurrent_gc(request); | |
| } | |
| set_gc_mode(ml, mode); | |
| } | |
| ShenandoahGenerationalControlThread::GCMode ShenandoahGenerationalControlThread::prepare_for_allocation_failure_gc(ShenandoahGCRequest &request) { | |
| if (_degen_point == ShenandoahGC::_degenerated_unset) { | |
| _degen_point = ShenandoahGC::_degenerated_outside_cycle; | |
| request.generation = _heap->young_generation(); | |
| } else if (request.generation->is_old()) { | |
| // This means we degenerated during the young bootstrap for the old generation | |
| // cycle. The following degenerated cycle should therefore also be young. | |
| request.generation = _heap->young_generation(); | |
| } | |
| ShenandoahHeuristics* heuristics = request.generation->heuristics(); | |
| bool old_gen_evacuation_failed = _heap->old_generation()->clear_failed_evacuation(); | |
| heuristics->log_trigger("Handle Allocation Failure"); | |
| // Do not bother with degenerated cycle if old generation evacuation failed or if humongous allocation failed | |
| if (ShenandoahDegeneratedGC && heuristics->should_degenerate_cycle() && | |
| !old_gen_evacuation_failed && request.cause != GCCause::_shenandoah_humongous_allocation_failure) { | |
| heuristics->record_allocation_failure_gc(); | |
| _heap->shenandoah_policy()->record_alloc_failure_to_degenerated(_degen_point); | |
| return stw_degenerated; | |
| } else { | |
| heuristics->record_allocation_failure_gc(); | |
| _heap->shenandoah_policy()->record_alloc_failure_to_full(); | |
| request.generation = _heap->global_generation(); | |
| return stw_full; | |
| } | |
| } | |
| ShenandoahGenerationalControlThread::GCMode ShenandoahGenerationalControlThread::prepare_for_explicit_gc(ShenandoahGCRequest &request) const { | |
| ShenandoahHeuristics* global_heuristics = _heap->global_generation()->heuristics(); | |
| request.generation = _heap->global_generation(); | |
| global_heuristics->log_trigger("GC request (%s)", GCCause::to_string(request.cause)); | |
| global_heuristics->record_requested_gc(); | |
| if (ShenandoahCollectorPolicy::should_run_full_gc(request.cause)) { | |
| return stw_full;; | |
| } else { | |
| // Unload and clean up everything. Note that this is an _explicit_ request and so does not use | |
| // the same `should_unload_classes` call as the regulator's concurrent gc request. | |
| _heap->set_unload_classes(global_heuristics->can_unload_classes()); | |
| return concurrent_normal; | |
| } | |
| } | |
| ShenandoahGenerationalControlThread::GCMode ShenandoahGenerationalControlThread::prepare_for_concurrent_gc(const ShenandoahGCRequest &request) const { | |
| assert(!(request.generation->is_old() && _heap->old_generation()->is_doing_mixed_evacuations()), | |
| "Old heuristic should not request cycles while it waits for mixed evacuations"); | |
| if (request.generation->is_global()) { | |
| ShenandoahHeuristics* global_heuristics = _heap->global_generation()->heuristics(); | |
| _heap->set_unload_classes(global_heuristics->should_unload_classes()); | |
| } else { | |
| _heap->set_unload_classes(false); | |
| } | |
| // preemption was requested or this is a regular cycle | |
| return request.generation->is_old() ? servicing_old : concurrent_normal; | |
| } | |
| void ShenandoahGenerationalControlThread::maybe_set_aging_cycle() { | |
| if (_age_period-- == 0) { | |
| _heap->set_aging_cycle(true); | |
| _age_period = ShenandoahAgingCyclePeriod - 1; | |
| } else { | |
| _heap->set_aging_cycle(false); | |
| } | |
| } | |
| void ShenandoahGenerationalControlThread::run_gc_cycle(const ShenandoahGCRequest& request) { | |
| log_debug(gc, thread)("Starting GC (%s): %s, %s", gc_mode_name(gc_mode()), GCCause::to_string(request.cause), request.generation->name()); | |
| assert(gc_mode() != none, "GC mode cannot be none here"); | |
| // Blow away all soft references on this cycle, if handling allocation failure, | |
| // either implicit or explicit GC request, or we are requested to do so unconditionally. | |
| if (request.generation->is_global() && (ShenandoahCollectorPolicy::is_allocation_failure(request.cause) || ShenandoahCollectorPolicy::is_explicit_gc(request.cause) || ShenandoahAlwaysClearSoftRefs)) { | |
| _heap->soft_ref_policy()->set_should_clear_all_soft_refs(true); | |
| } | |
| // GC is starting, bump the internal ID | |
| update_gc_id(); | |
| GCIdMark gc_id_mark; | |
| _heap->reset_bytes_allocated_since_gc_start(); | |
| MetaspaceCombinedStats meta_sizes = MetaspaceUtils::get_combined_statistics(); | |
| // If GC was requested, we are sampling the counters even without actual triggers | |
| // from allocation machinery. This captures GC phases more accurately. | |
| _heap->set_forced_counters_update(true); | |
| // If GC was requested, we better dump freeset data for performance debugging | |
| _heap->free_set()->log_status_under_lock(); | |
| { | |
| // Cannot uncommit bitmap slices during concurrent reset | |
| ShenandoahNoUncommitMark forbid_region_uncommit(_heap); | |
| switch (gc_mode()) { | |
| case concurrent_normal: { | |
| service_concurrent_normal_cycle(request); | |
| break; | |
| } | |
| case stw_degenerated: { | |
| service_stw_degenerated_cycle(request); | |
| break; | |
| } | |
| case stw_full: { | |
| service_stw_full_cycle(request.cause); | |
| break; | |
| } | |
| case servicing_old: { | |
| assert(request.generation->is_old(), "Expected old generation here"); | |
| service_concurrent_old_cycle(request); | |
| break; | |
| } | |
| default: | |
| ShouldNotReachHere(); | |
| } | |
| } | |
| // If this cycle completed successfully, notify threads waiting for gc | |
| if (!_heap->cancelled_gc()) { | |
| notify_gc_waiters(); | |
| notify_alloc_failure_waiters(); | |
| } | |
| // Report current free set state at the end of cycle, whether | |
| // it is a normal completion, or the abort. | |
| _heap->free_set()->log_status_under_lock(); | |
| // Notify Universe about new heap usage. This has implications for | |
| // global soft refs policy, and we better report it every time heap | |
| // usage goes down. | |
| _heap->update_capacity_and_used_at_gc(); | |
| // Signal that we have completed a visit to all live objects. | |
| _heap->record_whole_heap_examined_timestamp(); | |
| // Disable forced counters update, and update counters one more time | |
| // to capture the state at the end of GC session. | |
| _heap->handle_force_counters_update(); | |
| _heap->set_forced_counters_update(false); | |
| // Retract forceful part of soft refs policy | |
| _heap->soft_ref_policy()->set_should_clear_all_soft_refs(false); | |
| // Clear metaspace oom flag, if current cycle unloaded classes | |
| if (_heap->unload_classes()) { | |
| _heap->global_generation()->heuristics()->clear_metaspace_oom(); | |
| } | |
| process_phase_timings(); | |
| // Print Metaspace change following GC (if logging is enabled). | |
| MetaspaceUtils::print_metaspace_change(meta_sizes); | |
| // GC is over, we are at idle now | |
| if (ShenandoahPacing) { | |
| _heap->pacer()->setup_for_idle(); | |
| } | |
| // Check if we have seen a new target for soft max heap size or if a gc was requested. | |
| // Either of these conditions will attempt to uncommit regions. | |
| if (ShenandoahUncommit) { | |
| if (_heap->check_soft_max_changed()) { | |
| _heap->notify_soft_max_changed(); | |
| } else if (ShenandoahCollectorPolicy::is_explicit_gc(request.cause)) { | |
| _heap->notify_explicit_gc_requested(); | |
| } | |
| } | |
| log_debug(gc, thread)("Completed GC (%s): %s, %s, cancelled: %s", | |
| gc_mode_name(gc_mode()), GCCause::to_string(request.cause), request.generation->name(), GCCause::to_string(_heap->cancelled_cause())); | |
| } | |
| void ShenandoahGenerationalControlThread::process_phase_timings() const { | |
| // Commit worker statistics to cycle data | |
| _heap->phase_timings()->flush_par_workers_to_cycle(); | |
| if (ShenandoahPacing) { | |
| _heap->pacer()->flush_stats_to_cycle(); | |
| } | |
| ShenandoahEvacuationTracker* evac_tracker = _heap->evac_tracker(); | |
| ShenandoahCycleStats evac_stats = evac_tracker->flush_cycle_to_global(); | |
| // Print GC stats for current cycle | |
| { | |
| LogTarget(Info, gc, stats) lt; | |
| if (lt.is_enabled()) { | |
| ResourceMark rm; | |
| LogStream ls(lt); | |
| _heap->phase_timings()->print_cycle_on(&ls); | |
| evac_tracker->print_evacuations_on(&ls, &evac_stats.workers, | |
| &evac_stats.mutators); | |
| if (ShenandoahPacing) { | |
| _heap->pacer()->print_cycle_on(&ls); | |
| } | |
| } | |
| } | |
| // Commit statistics to globals | |
| _heap->phase_timings()->flush_cycle_to_global(); | |
| } | |
| // Young and old concurrent cycles are initiated by the regulator. Implicit | |
| // and explicit GC requests are handled by the controller thread and always | |
| // run a global cycle (which is concurrent by default, but may be overridden | |
| // by command line options). Old cycles always degenerate to a global cycle. | |
| // Young cycles are degenerated to complete the young cycle. Young | |
| // and old degen may upgrade to Full GC. Full GC may also be | |
| // triggered directly by a System.gc() invocation. | |
| // | |
| // | |
| // +-----+ Idle +-----+-----------+---------------------+ | |
| // | + | | | | |
| // | | | | | | |
| // | | v | | | |
| // | | Bootstrap Old +-- | ------------+ | | |
| // | | + | | | | |
| // | | | | | | | |
| // | v v v v | | |
| // | Resume Old <----------+ Young +--> Young Degen | | |
| // | + + ^ + + | | |
| // v | | | | | | | |
| // Global <-+ | +----------------------------+ | | | |
| // + | | | | |
| // | v v | | |
| // +---> Global Degen +--------------------> Full <----+ | |
| // | |
| void ShenandoahGenerationalControlThread::service_concurrent_normal_cycle(const ShenandoahGCRequest& request) { | |
| log_info(gc, ergo)("Start GC cycle (%s)", request.generation->name()); | |
| if (request.generation->is_old()) { | |
| service_concurrent_old_cycle(request); | |
| } else { | |
| service_concurrent_cycle(request.generation, request.cause, false); | |
| } | |
| } | |
| void ShenandoahGenerationalControlThread::service_concurrent_old_cycle(const ShenandoahGCRequest& request) { | |
| ShenandoahOldGeneration* old_generation = _heap->old_generation(); | |
| ShenandoahYoungGeneration* young_generation = _heap->young_generation(); | |
| ShenandoahOldGeneration::State original_state = old_generation->state(); | |
| TraceCollectorStats tcs(_heap->monitoring_support()->concurrent_collection_counters()); | |
| switch (original_state) { | |
| case ShenandoahOldGeneration::FILLING: { | |
| ShenandoahGCSession session(request.cause, old_generation); | |
| assert(gc_mode() == servicing_old, "Filling should be servicing old"); | |
| _allow_old_preemption.set(); | |
| old_generation->entry_coalesce_and_fill(); | |
| _allow_old_preemption.unset(); | |
| // Before bootstrapping begins, we must acknowledge any cancellation request. | |
| // If the gc has not been cancelled, this does nothing. If it has been cancelled, | |
| // this will clear the cancellation request and exit before starting the bootstrap | |
| // phase. This will allow the young GC cycle to proceed normally. If we do not | |
| // acknowledge the cancellation request, the subsequent young cycle will observe | |
| // the request and essentially cancel itself. | |
| if (check_cancellation_or_degen(ShenandoahGC::_degenerated_outside_cycle)) { | |
| log_info(gc, thread)("Preparation for old generation cycle was cancelled"); | |
| return; | |
| } | |
| // Coalescing threads completed and nothing was cancelled. it is safe to transition from this state. | |
| old_generation->transition_to(ShenandoahOldGeneration::WAITING_FOR_BOOTSTRAP); | |
| return; | |
| } | |
| case ShenandoahOldGeneration::WAITING_FOR_BOOTSTRAP: | |
| old_generation->transition_to(ShenandoahOldGeneration::BOOTSTRAPPING); | |
| case ShenandoahOldGeneration::BOOTSTRAPPING: { | |
| // Configure the young generation's concurrent mark to put objects in | |
| // old regions into the concurrent mark queues associated with the old | |
| // generation. The young cycle will run as normal except that rather than | |
| // ignore old references it will mark and enqueue them in the old concurrent | |
| // task queues but it will not traverse them. | |
| set_gc_mode(bootstrapping_old); | |
| young_generation->set_old_gen_task_queues(old_generation->task_queues()); | |
| service_concurrent_cycle(young_generation, request.cause, true); | |
| process_phase_timings(); | |
| if (_heap->cancelled_gc()) { | |
| // Young generation bootstrap cycle has failed. Concurrent mark for old generation | |
| // is going to resume after degenerated bootstrap cycle completes. | |
| log_info(gc)("Bootstrap cycle for old generation was cancelled"); | |
| return; | |
| } | |
| assert(_degen_point == ShenandoahGC::_degenerated_unset, "Degen point should not be set if gc wasn't cancelled"); | |
| // From here we will 'resume' the old concurrent mark. This will skip reset | |
| // and init mark for the concurrent mark. All of that work will have been | |
| // done by the bootstrapping young cycle. | |
| set_gc_mode(servicing_old); | |
| old_generation->transition_to(ShenandoahOldGeneration::MARKING); | |
| } | |
| case ShenandoahOldGeneration::MARKING: { | |
| ShenandoahGCSession session(request.cause, old_generation); | |
| bool marking_complete = resume_concurrent_old_cycle(old_generation, request.cause); | |
| if (marking_complete) { | |
| assert(old_generation->state() != ShenandoahOldGeneration::MARKING, "Should not still be marking"); | |
| if (original_state == ShenandoahOldGeneration::MARKING) { | |
| _heap->mmu_tracker()->record_old_marking_increment(true); | |
| _heap->log_heap_status("At end of Concurrent Old Marking finishing increment"); | |
| } | |
| } else if (original_state == ShenandoahOldGeneration::MARKING) { | |
| _heap->mmu_tracker()->record_old_marking_increment(false); | |
| _heap->log_heap_status("At end of Concurrent Old Marking increment"); | |
| } | |
| break; | |
| } | |
| default: | |
| fatal("Unexpected state for old GC: %s", ShenandoahOldGeneration::state_name(old_generation->state())); | |
| } | |
| } | |
| bool ShenandoahGenerationalControlThread::resume_concurrent_old_cycle(ShenandoahOldGeneration* generation, GCCause::Cause cause) { | |
| assert(_heap->is_concurrent_old_mark_in_progress(), "Old mark should be in progress"); | |
| log_debug(gc)("Resuming old generation with " UINT32_FORMAT " marking tasks queued", generation->task_queues()->tasks()); | |
| // We can only tolerate being cancelled during concurrent marking or during preparation for mixed | |
| // evacuation. This flag here (passed by reference) is used to control precisely where the regulator | |
| // is allowed to cancel a GC. | |
| ShenandoahOldGC gc(generation, _allow_old_preemption); | |
| if (gc.collect(cause)) { | |
| _heap->notify_gc_progress(); | |
| generation->record_success_concurrent(false); | |
| } | |
| if (_heap->cancelled_gc()) { | |
| // It's possible the gc cycle was cancelled after the last time the collection checked for cancellation. In which | |
| // case, the old gc cycle is still completed, and we have to deal with this cancellation. We set the degeneration | |
| // point to be outside the cycle because if this is an allocation failure, that is what must be done (there is no | |
| // degenerated old cycle). If the cancellation was due to a heuristic wanting to start a young cycle, then we are | |
| // not actually going to a degenerated cycle, so don't set the degeneration point here. | |
| if (ShenandoahCollectorPolicy::is_allocation_failure(cause)) { | |
| check_cancellation_or_degen(ShenandoahGC::_degenerated_outside_cycle); | |
| } else if (cause == GCCause::_shenandoah_concurrent_gc) { | |
| _heap->shenandoah_policy()->record_interrupted_old(); | |
| } | |
| return false; | |
| } | |
| return true; | |
| } | |
| // Normal cycle goes via all concurrent phases. If allocation failure (af) happens during | |
| // any of the concurrent phases, it first degrades to Degenerated GC and completes GC there. | |
| // If second allocation failure happens during Degenerated GC cycle (for example, when GC | |
| // tries to evac something and no memory is available), cycle degrades to Full GC. | |
| // | |
| // There are also a shortcut through the normal cycle: immediate garbage shortcut, when | |
| // heuristics says there are no regions to compact, and all the collection comes from immediately | |
| // reclaimable regions. | |
| // | |
| // ................................................................................................ | |
| // | |
| // (immediate garbage shortcut) Concurrent GC | |
| // /-------------------------------------------\ | |
| // | | | |
| // | | | |
| // | | | |
| // | v | |
| // [START] ----> Conc Mark ----o----> Conc Evac --o--> Conc Update-Refs ---o----> [END] | |
| // | | | ^ | |
| // | (af) | (af) | (af) | | |
| // ..................|....................|.................|..............|....................... | |
| // | | | | | |
| // | | | | Degenerated GC | |
| // v v v | | |
| // STW Mark ----------> STW Evac ----> STW Update-Refs ----->o | |
| // | | | ^ | |
| // | (af) | (af) | (af) | | |
| // ..................|....................|.................|..............|....................... | |
| // | | | | | |
| // | v | | Full GC | |
| // \------------------->o<----------------/ | | |
| // | | | |
| // v | | |
| // Full GC --------------------------/ | |
| // | |
| void ShenandoahGenerationalControlThread::service_concurrent_cycle(ShenandoahGeneration* generation, | |
| GCCause::Cause cause, | |
| bool do_old_gc_bootstrap) { | |
| // At this point: | |
| // if (generation == YOUNG), this is a normal young cycle or a bootstrap cycle | |
| // if (generation == GLOBAL), this is a GLOBAL cycle | |
| // In either case, we want to age old objects if this is an aging cycle | |
| maybe_set_aging_cycle(); | |
| ShenandoahGCSession session(cause, generation); | |
| TraceCollectorStats tcs(_heap->monitoring_support()->concurrent_collection_counters()); | |
| assert(!generation->is_old(), "Old GC takes a different control path"); | |
| ShenandoahConcurrentGC gc(generation, do_old_gc_bootstrap); | |
| if (gc.collect(cause)) { | |
| // Cycle is complete | |
| _heap->notify_gc_progress(); | |
| generation->record_success_concurrent(gc.abbreviated()); | |
| } else { | |
| assert(_heap->cancelled_gc(), "Must have been cancelled"); | |
| check_cancellation_or_degen(gc.degen_point()); | |
| } | |
| const char* msg; | |
| ShenandoahMmuTracker* mmu_tracker = _heap->mmu_tracker(); | |
| if (generation->is_young()) { | |
| if (_heap->cancelled_gc()) { | |
| msg = (do_old_gc_bootstrap) ? "At end of Interrupted Concurrent Bootstrap GC" : | |
| "At end of Interrupted Concurrent Young GC"; | |
| } else { | |
| // We only record GC results if GC was successful | |
| msg = (do_old_gc_bootstrap) ? "At end of Concurrent Bootstrap GC" : | |
| "At end of Concurrent Young GC"; | |
| if (_heap->collection_set()->has_old_regions()) { | |
| mmu_tracker->record_mixed(get_gc_id()); | |
| } else if (do_old_gc_bootstrap) { | |
| mmu_tracker->record_bootstrap(get_gc_id()); | |
| } else { | |
| mmu_tracker->record_young(get_gc_id()); | |
| } | |
| } | |
| } else { | |
| assert(generation->is_global(), "If not young, must be GLOBAL"); | |
| assert(!do_old_gc_bootstrap, "Do not bootstrap with GLOBAL GC"); | |
| if (_heap->cancelled_gc()) { | |
| msg = "At end of Interrupted Concurrent GLOBAL GC"; | |
| } else { | |
| // We only record GC results if GC was successful | |
| msg = "At end of Concurrent Global GC"; | |
| mmu_tracker->record_global(get_gc_id()); | |
| } | |
| } | |
| _heap->log_heap_status(msg); | |
| } | |
| bool ShenandoahGenerationalControlThread::check_cancellation_or_degen(ShenandoahGC::ShenandoahDegenPoint point) { | |
| if (!_heap->cancelled_gc()) { | |
| return false; | |
| } | |
| if (_heap->cancelled_cause() == GCCause::_shenandoah_stop_vm | |
| || _heap->cancelled_cause() == GCCause::_shenandoah_concurrent_gc) { | |
| log_debug(gc, thread)("Cancellation detected, reason: %s", GCCause::to_string(_heap->cancelled_cause())); | |
| return true; | |
| } | |
| if (ShenandoahCollectorPolicy::is_allocation_failure(_heap->cancelled_cause())) { | |
| assert(_degen_point == ShenandoahGC::_degenerated_unset, | |
| "Should not be set yet: %s", ShenandoahGC::degen_point_to_string(_degen_point)); | |
| _degen_point = point; | |
| log_debug(gc, thread)("Cancellation detected:, reason: %s, degen point: %s", | |
| GCCause::to_string(_heap->cancelled_cause()), | |
| ShenandoahGC::degen_point_to_string(_degen_point)); | |
| return true; | |
| } | |
| fatal("Cancel GC either for alloc failure GC, or gracefully exiting, or to pause old generation marking"); | |
| return false; | |
| } | |
| void ShenandoahGenerationalControlThread::service_stw_full_cycle(GCCause::Cause cause) { | |
| ShenandoahGCSession session(cause, _heap->global_generation()); | |
| maybe_set_aging_cycle(); | |
| ShenandoahFullGC gc; | |
| gc.collect(cause); | |
| _degen_point = ShenandoahGC::_degenerated_unset; | |
| } | |
| void ShenandoahGenerationalControlThread::service_stw_degenerated_cycle(const ShenandoahGCRequest& request) { | |
| assert(_degen_point != ShenandoahGC::_degenerated_unset, "Degenerated point should be set"); | |
| ShenandoahGCSession session(request.cause, request.generation); | |
| ShenandoahDegenGC gc(_degen_point, request.generation); | |
| gc.collect(request.cause); | |
| _degen_point = ShenandoahGC::_degenerated_unset; | |
| assert(_heap->young_generation()->task_queues()->is_empty(), "Unexpected young generation marking tasks"); | |
| if (request.generation->is_global()) { | |
| assert(_heap->old_generation()->task_queues()->is_empty(), "Unexpected old generation marking tasks"); | |
| assert(_heap->global_generation()->task_queues()->is_empty(), "Unexpected global generation marking tasks"); | |
| } else { | |
| assert(request.generation->is_young(), "Expected degenerated young cycle, if not global."); | |
| ShenandoahOldGeneration* old = _heap->old_generation(); | |
| if (old->is_bootstrapping()) { | |
| old->transition_to(ShenandoahOldGeneration::MARKING); | |
| } | |
| } | |
| } | |
| void ShenandoahGenerationalControlThread::request_gc(GCCause::Cause cause) { | |
| if (ShenandoahCollectorPolicy::is_allocation_failure(cause)) { | |
| // GC should already be cancelled. Here we are just notifying the control thread to | |
| // wake up and handle the cancellation request, so we don't need to set _requested_gc_cause. | |
| notify_cancellation(cause); | |
| } else if (ShenandoahCollectorPolicy::should_handle_requested_gc(cause)) { | |
| handle_requested_gc(cause); | |
| } | |
| } | |
| bool ShenandoahGenerationalControlThread::request_concurrent_gc(ShenandoahGeneration* generation) { | |
| if (_heap->cancelled_gc()) { | |
| // Ignore subsequent requests from the heuristics | |
| log_debug(gc, thread)("Reject request for concurrent gc: gc_requested: %s, gc_cancelled: %s", | |
| GCCause::to_string(_requested_gc_cause), | |
| BOOL_TO_STR(_heap->cancelled_gc())); | |
| return false; | |
| } | |
| MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag); | |
| if (gc_mode() == servicing_old) { | |
| if (!preempt_old_marking(generation)) { | |
| log_debug(gc, thread)("Cannot start young, old collection is not preemptible"); | |
| return false; | |
| } | |
| // Cancel the old GC and wait for the control thread to start servicing the new request. | |
| log_info(gc)("Preempting old generation mark to allow %s GC", generation->name()); | |
| while (gc_mode() == servicing_old) { | |
| ShenandoahHeap::heap()->cancel_gc(GCCause::_shenandoah_concurrent_gc); | |
| notify_cancellation(ml, GCCause::_shenandoah_concurrent_gc); | |
| ml.wait(); | |
| } | |
| return true; | |
| } | |
| if (gc_mode() == none) { | |
| const size_t current_gc_id = get_gc_id(); | |
| while (gc_mode() == none && current_gc_id == get_gc_id()) { | |
| if (_requested_gc_cause != GCCause::_no_gc) { | |
| log_debug(gc, thread)("Reject request for concurrent gc because another gc is pending: %s", GCCause::to_string(_requested_gc_cause)); | |
| return false; | |
| } | |
| notify_control_thread(ml, GCCause::_shenandoah_concurrent_gc, generation); | |
| ml.wait(); | |
| } | |
| return true; | |
| } | |
| log_debug(gc, thread)("Reject request for concurrent gc: mode: %s, allow_old_preemption: %s", | |
| gc_mode_name(gc_mode()), | |
| BOOL_TO_STR(_allow_old_preemption.is_set())); | |
| return false; | |
| } | |
| void ShenandoahGenerationalControlThread::notify_control_thread(GCCause::Cause cause, ShenandoahGeneration* generation) { | |
| MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag); | |
| notify_control_thread(ml, cause, generation); | |
| } | |
| void ShenandoahGenerationalControlThread::notify_control_thread(MonitorLocker& ml, GCCause::Cause cause, ShenandoahGeneration* generation) { | |
| assert(_control_lock.is_locked(), "Request lock must be held here"); | |
| log_debug(gc, thread)("Notify control (%s): %s, %s", gc_mode_name(gc_mode()), GCCause::to_string(cause), generation->name()); | |
| _requested_gc_cause = cause; | |
| _requested_generation = generation; | |
| ml.notify(); | |
| } | |
| void ShenandoahGenerationalControlThread::notify_cancellation(GCCause::Cause cause) { | |
| MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag); | |
| notify_cancellation(ml, cause); | |
| } | |
| void ShenandoahGenerationalControlThread::notify_cancellation(MonitorLocker& ml, GCCause::Cause cause) { | |
| assert(_heap->cancelled_gc(), "GC should already be cancelled"); | |
| log_debug(gc,thread)("Notify control (%s): %s", gc_mode_name(gc_mode()), GCCause::to_string(cause)); | |
| ml.notify(); | |
| } | |
| bool ShenandoahGenerationalControlThread::preempt_old_marking(ShenandoahGeneration* generation) { | |
| return generation->is_young() && _allow_old_preemption.try_unset(); | |
| } | |
| void ShenandoahGenerationalControlThread::handle_requested_gc(GCCause::Cause cause) { | |
| // 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, ShenandoahHeap::heap()->global_generation()); | |
| 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(); | |
| const size_t required_gc_id = current_gc_id + 1; | |
| while (current_gc_id < required_gc_id && !should_terminate()) { | |
| // Make requests to run a global cycle until at least one is completed | |
| notify_control_thread(cause, ShenandoahHeap::heap()->global_generation()); | |
| ml.wait(); | |
| current_gc_id = get_gc_id(); | |
| } | |
| } | |
| void ShenandoahGenerationalControlThread::notify_gc_waiters() { | |
| MonitorLocker ml(&_gc_waiters_lock); | |
| ml.notify_all(); | |
| } | |
| const char* ShenandoahGenerationalControlThread::gc_mode_name(GCMode mode) { | |
| switch (mode) { | |
| case none: return "idle"; | |
| case concurrent_normal: return "normal"; | |
| case stw_degenerated: return "degenerated"; | |
| case stw_full: return "full"; | |
| case servicing_old: return "old"; | |
| case bootstrapping_old: return "bootstrap"; | |
| case stopped: return "stopped"; | |
| default: return "unknown"; | |
| } | |
| } | |
| void ShenandoahGenerationalControlThread::set_gc_mode(GCMode new_mode) { | |
| MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag); | |
| set_gc_mode(ml, new_mode); | |
| } | |
| void ShenandoahGenerationalControlThread::set_gc_mode(MonitorLocker& ml, GCMode new_mode) { | |
| if (_gc_mode != new_mode) { | |
| log_debug(gc, thread)("Transition from: %s to: %s", gc_mode_name(_gc_mode), gc_mode_name(new_mode)); | |
| EventMark event("Control thread transition from: %s, to %s", gc_mode_name(_gc_mode), gc_mode_name(new_mode)); | |
| _gc_mode = new_mode; | |
| ml.notify_all(); | |
| } | |
| } |