| /* |
| * Copyright (c) 2013, 2021, Red Hat, Inc. 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/shared/satbMarkQueue.hpp" |
| #include "gc/shared/strongRootsScope.hpp" |
| #include "gc/shared/taskTerminator.hpp" |
| #include "gc/shenandoah/shenandoahBarrierSet.inline.hpp" |
| #include "gc/shenandoah/shenandoahClosures.inline.hpp" |
| #include "gc/shenandoah/shenandoahConcurrentMark.hpp" |
| #include "gc/shenandoah/shenandoahGeneration.hpp" |
| #include "gc/shenandoah/shenandoahHeap.inline.hpp" |
| #include "gc/shenandoah/shenandoahMark.inline.hpp" |
| #include "gc/shenandoah/shenandoahPhaseTimings.hpp" |
| #include "gc/shenandoah/shenandoahReferenceProcessor.hpp" |
| #include "gc/shenandoah/shenandoahRootProcessor.inline.hpp" |
| #include "gc/shenandoah/shenandoahScanRemembered.inline.hpp" |
| #include "gc/shenandoah/shenandoahStringDedup.hpp" |
| #include "gc/shenandoah/shenandoahTaskqueue.inline.hpp" |
| #include "gc/shenandoah/shenandoahUtils.hpp" |
| #include "memory/iterator.inline.hpp" |
| #include "memory/resourceArea.hpp" |
| #include "runtime/continuation.hpp" |
| #include "runtime/threads.hpp" |
| |
| template <ShenandoahGenerationType GENERATION> |
| class ShenandoahConcurrentMarkingTask : public WorkerTask { |
| private: |
| ShenandoahConcurrentMark* const _cm; |
| TaskTerminator* const _terminator; |
| |
| public: |
| ShenandoahConcurrentMarkingTask(ShenandoahConcurrentMark* cm, TaskTerminator* terminator) : |
| WorkerTask("Shenandoah Concurrent Mark"), _cm(cm), _terminator(terminator) { |
| } |
| |
| void work(uint worker_id) { |
| ShenandoahHeap* heap = ShenandoahHeap::heap(); |
| ShenandoahConcurrentWorkerSession worker_session(worker_id); |
| ShenandoahWorkerTimingsTracker timer(ShenandoahPhaseTimings::conc_mark, ShenandoahPhaseTimings::ParallelMark, worker_id, true); |
| ShenandoahSuspendibleThreadSetJoiner stsj; |
| // Do not use active_generation() : we must use the gc_generation() set by |
| // ShenandoahGCScope on the ControllerThread's stack; no safepoint may |
| // intervene to update active_generation, so we can't |
| // shenandoah_assert_generations_reconciled() here. |
| ShenandoahReferenceProcessor* rp = heap->gc_generation()->ref_processor(); |
| assert(rp != nullptr, "need reference processor"); |
| StringDedup::Requests requests; |
| _cm->mark_loop(worker_id, _terminator, rp, GENERATION, true /*cancellable*/, |
| ShenandoahStringDedup::is_enabled() ? ENQUEUE_DEDUP : NO_DEDUP, |
| &requests); |
| } |
| }; |
| |
| class ShenandoahSATBAndRemarkThreadsClosure : public ThreadClosure { |
| private: |
| SATBMarkQueueSet& _satb_qset; |
| |
| public: |
| explicit ShenandoahSATBAndRemarkThreadsClosure(SATBMarkQueueSet& satb_qset) : |
| _satb_qset(satb_qset) {} |
| |
| void do_thread(Thread* thread) override { |
| // Transfer any partial buffer to the qset for completed buffer processing. |
| _satb_qset.flush_queue(ShenandoahThreadLocalData::satb_mark_queue(thread)); |
| } |
| }; |
| |
| template <ShenandoahGenerationType GENERATION> |
| class ShenandoahFinalMarkingTask : public WorkerTask { |
| private: |
| ShenandoahConcurrentMark* _cm; |
| TaskTerminator* _terminator; |
| bool _dedup_string; |
| |
| public: |
| ShenandoahFinalMarkingTask(ShenandoahConcurrentMark* cm, TaskTerminator* terminator, bool dedup_string) : |
| WorkerTask("Shenandoah Final Mark"), _cm(cm), _terminator(terminator), _dedup_string(dedup_string) { |
| } |
| |
| void work(uint worker_id) { |
| ShenandoahHeap* heap = ShenandoahHeap::heap(); |
| |
| ShenandoahParallelWorkerSession worker_session(worker_id); |
| StringDedup::Requests requests; |
| ShenandoahReferenceProcessor* rp = heap->gc_generation()->ref_processor(); |
| shenandoah_assert_generations_reconciled(); |
| |
| // First drain remaining SATB buffers. |
| { |
| ShenandoahObjToScanQueue* q = _cm->get_queue(worker_id); |
| ShenandoahObjToScanQueue* old_q = _cm->get_old_queue(worker_id); |
| |
| ShenandoahSATBBufferClosure<GENERATION> cl(q, old_q); |
| SATBMarkQueueSet& satb_mq_set = ShenandoahBarrierSet::satb_mark_queue_set(); |
| while (satb_mq_set.apply_closure_to_completed_buffer(&cl)) {} |
| assert(!heap->has_forwarded_objects(), "Not expected"); |
| |
| ShenandoahSATBAndRemarkThreadsClosure tc(satb_mq_set); |
| Threads::possibly_parallel_threads_do(true /* is_par */, &tc); |
| } |
| _cm->mark_loop(worker_id, _terminator, rp, GENERATION, false /*not cancellable*/, |
| _dedup_string ? ENQUEUE_DEDUP : NO_DEDUP, |
| &requests); |
| assert(_cm->task_queues()->is_empty(), "Should be empty"); |
| } |
| }; |
| |
| ShenandoahConcurrentMark::ShenandoahConcurrentMark(ShenandoahGeneration* generation) : |
| ShenandoahMark(generation) {} |
| |
| // Mark concurrent roots during concurrent phases |
| template <ShenandoahGenerationType GENERATION> |
| class ShenandoahMarkConcurrentRootsTask : public WorkerTask { |
| private: |
| SuspendibleThreadSetJoiner _sts_joiner; |
| ShenandoahConcurrentRootScanner _root_scanner; |
| ShenandoahObjToScanQueueSet* const _queue_set; |
| ShenandoahObjToScanQueueSet* const _old_queue_set; |
| ShenandoahReferenceProcessor* const _rp; |
| |
| public: |
| ShenandoahMarkConcurrentRootsTask(ShenandoahObjToScanQueueSet* qs, |
| ShenandoahObjToScanQueueSet* old, |
| ShenandoahReferenceProcessor* rp, |
| ShenandoahPhaseTimings::Phase phase, |
| uint nworkers); |
| void work(uint worker_id); |
| }; |
| |
| template <ShenandoahGenerationType GENERATION> |
| ShenandoahMarkConcurrentRootsTask<GENERATION>::ShenandoahMarkConcurrentRootsTask(ShenandoahObjToScanQueueSet* qs, |
| ShenandoahObjToScanQueueSet* old, |
| ShenandoahReferenceProcessor* rp, |
| ShenandoahPhaseTimings::Phase phase, |
| uint nworkers) : |
| WorkerTask("Shenandoah Concurrent Mark Roots"), |
| _root_scanner(nworkers, phase), |
| _queue_set(qs), |
| _old_queue_set(old), |
| _rp(rp) { |
| assert(!ShenandoahHeap::heap()->has_forwarded_objects(), "Not expected"); |
| } |
| |
| template <ShenandoahGenerationType GENERATION> |
| void ShenandoahMarkConcurrentRootsTask<GENERATION>::work(uint worker_id) { |
| ShenandoahConcurrentWorkerSession worker_session(worker_id); |
| ShenandoahObjToScanQueue* q = _queue_set->queue(worker_id); |
| ShenandoahObjToScanQueue* old_q = (_old_queue_set == nullptr) ? |
| nullptr : _old_queue_set->queue(worker_id); |
| ShenandoahMarkRefsClosure<GENERATION> cl(q, _rp, old_q); |
| _root_scanner.roots_do(&cl, worker_id); |
| } |
| |
| void ShenandoahConcurrentMark::mark_concurrent_roots() { |
| ShenandoahHeap* const heap = ShenandoahHeap::heap(); |
| assert(!heap->has_forwarded_objects(), "Not expected"); |
| |
| WorkerThreads* workers = heap->workers(); |
| ShenandoahReferenceProcessor* rp = _generation->ref_processor(); |
| _generation->reserve_task_queues(workers->active_workers()); |
| switch (_generation->type()) { |
| case YOUNG: { |
| ShenandoahMarkConcurrentRootsTask<YOUNG> task(task_queues(), old_task_queues(), rp, |
| ShenandoahPhaseTimings::conc_mark_roots, workers->active_workers()); |
| workers->run_task(&task); |
| break; |
| } |
| case GLOBAL: { |
| assert(old_task_queues() == nullptr, "Global mark should not have old gen mark queues"); |
| ShenandoahMarkConcurrentRootsTask<GLOBAL> task(task_queues(), nullptr, rp, |
| ShenandoahPhaseTimings::conc_mark_roots, workers->active_workers()); |
| workers->run_task(&task); |
| break; |
| } |
| case NON_GEN: { |
| assert(old_task_queues() == nullptr, "Non-generational mark should not have old gen mark queues"); |
| ShenandoahMarkConcurrentRootsTask<NON_GEN> task(task_queues(), nullptr, rp, |
| ShenandoahPhaseTimings::conc_mark_roots, workers->active_workers()); |
| workers->run_task(&task); |
| break; |
| } |
| case OLD: { |
| // We use a YOUNG generation cycle to bootstrap concurrent old marking. |
| ShouldNotReachHere(); |
| break; |
| } |
| default: |
| ShouldNotReachHere(); |
| } |
| } |
| |
| void ShenandoahConcurrentMark::concurrent_mark() { |
| ShenandoahHeap* const heap = ShenandoahHeap::heap(); |
| WorkerThreads* workers = heap->workers(); |
| uint nworkers = workers->active_workers(); |
| task_queues()->reserve(nworkers); |
| |
| ShenandoahGenerationType gen_type = _generation->type(); |
| ShenandoahSATBMarkQueueSet& qset = ShenandoahBarrierSet::satb_mark_queue_set(); |
| ShenandoahFlushSATBHandshakeClosure flush_satb(qset); |
| for (uint flushes = 0; flushes < ShenandoahMaxSATBBufferFlushes; flushes++) { |
| switch (gen_type) { |
| case YOUNG: { |
| TaskTerminator terminator(nworkers, task_queues()); |
| ShenandoahConcurrentMarkingTask<YOUNG> task(this, &terminator); |
| workers->run_task(&task); |
| break; |
| } |
| case OLD: { |
| TaskTerminator terminator(nworkers, task_queues()); |
| ShenandoahConcurrentMarkingTask<OLD> task(this, &terminator); |
| workers->run_task(&task); |
| break; |
| } |
| case GLOBAL: { |
| TaskTerminator terminator(nworkers, task_queues()); |
| ShenandoahConcurrentMarkingTask<GLOBAL> task(this, &terminator); |
| workers->run_task(&task); |
| break; |
| } |
| case NON_GEN: { |
| TaskTerminator terminator(nworkers, task_queues()); |
| ShenandoahConcurrentMarkingTask<NON_GEN> task(this, &terminator); |
| workers->run_task(&task); |
| break; |
| } |
| default: |
| ShouldNotReachHere(); |
| } |
| |
| if (heap->cancelled_gc()) { |
| // GC is cancelled, break out. |
| break; |
| } |
| |
| size_t before = qset.completed_buffers_num(); |
| { |
| ShenandoahTimingsTracker t(ShenandoahPhaseTimings::conc_mark_satb_flush, true); |
| Handshake::execute(&flush_satb); |
| } |
| size_t after = qset.completed_buffers_num(); |
| |
| if (before == after) { |
| // No more retries needed, break out. |
| break; |
| } |
| } |
| assert(task_queues()->is_empty() || heap->cancelled_gc(), "Should be empty when not cancelled"); |
| } |
| |
| void ShenandoahConcurrentMark::finish_mark() { |
| assert(ShenandoahSafepoint::is_at_shenandoah_safepoint(), "Must be at a safepoint"); |
| assert(Thread::current()->is_VM_thread(), "Must by VM Thread"); |
| finish_mark_work(); |
| assert(task_queues()->is_empty(), "Should be empty"); |
| TASKQUEUE_STATS_ONLY(task_queues()->print_and_reset_taskqueue_stats("")); |
| |
| _generation->set_concurrent_mark_in_progress(false); |
| _generation->set_mark_complete(); |
| |
| end_mark(); |
| } |
| |
| void ShenandoahConcurrentMark::finish_mark_work() { |
| // Finally mark everything else we've got in our queues during the previous steps. |
| // It does two different things for concurrent vs. mark-compact GC: |
| // - For concurrent GC, it starts with empty task queues, drains the remaining |
| // SATB buffers, and then completes the marking closure. |
| // - For mark-compact GC, it starts out with the task queues seeded by initial |
| // root scan, and completes the closure, thus marking through all live objects |
| // The implementation is the same, so it's shared here. |
| ShenandoahHeap* const heap = ShenandoahHeap::heap(); |
| ShenandoahGCPhase phase(ShenandoahPhaseTimings::finish_mark); |
| uint nworkers = heap->workers()->active_workers(); |
| task_queues()->reserve(nworkers); |
| |
| StrongRootsScope scope(nworkers); |
| TaskTerminator terminator(nworkers, task_queues()); |
| |
| switch (_generation->type()) { |
| case YOUNG:{ |
| ShenandoahFinalMarkingTask<YOUNG> task(this, &terminator, ShenandoahStringDedup::is_enabled()); |
| heap->workers()->run_task(&task); |
| break; |
| } |
| case OLD:{ |
| ShenandoahFinalMarkingTask<OLD> task(this, &terminator, ShenandoahStringDedup::is_enabled()); |
| heap->workers()->run_task(&task); |
| break; |
| } |
| case GLOBAL:{ |
| ShenandoahFinalMarkingTask<GLOBAL> task(this, &terminator, ShenandoahStringDedup::is_enabled()); |
| heap->workers()->run_task(&task); |
| break; |
| } |
| case NON_GEN:{ |
| ShenandoahFinalMarkingTask<NON_GEN> task(this, &terminator, ShenandoahStringDedup::is_enabled()); |
| heap->workers()->run_task(&task); |
| break; |
| } |
| default: |
| ShouldNotReachHere(); |
| } |
| |
| |
| assert(task_queues()->is_empty(), "Should be empty"); |
| } |