| /* |
| * Copyright (c) 2021, 2025, 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 "classfile/classLoaderDataGraph.inline.hpp" |
| #include "classfile/javaClasses.inline.hpp" |
| #include "compiler/oopMap.hpp" |
| #include "gc/g1/g1Allocator.hpp" |
| #include "gc/g1/g1CardSetMemory.hpp" |
| #include "gc/g1/g1CollectedHeap.inline.hpp" |
| #include "gc/g1/g1CollectionSetCandidates.inline.hpp" |
| #include "gc/g1/g1CollectorState.hpp" |
| #include "gc/g1/g1ConcurrentMark.hpp" |
| #include "gc/g1/g1EvacFailureRegions.inline.hpp" |
| #include "gc/g1/g1EvacInfo.hpp" |
| #include "gc/g1/g1GCPhaseTimes.hpp" |
| #include "gc/g1/g1HeapRegionPrinter.hpp" |
| #include "gc/g1/g1MonitoringSupport.hpp" |
| #include "gc/g1/g1ParScanThreadState.inline.hpp" |
| #include "gc/g1/g1Policy.hpp" |
| #include "gc/g1/g1RedirtyCardsQueue.hpp" |
| #include "gc/g1/g1RegionPinCache.inline.hpp" |
| #include "gc/g1/g1RemSet.hpp" |
| #include "gc/g1/g1RootProcessor.hpp" |
| #include "gc/g1/g1Trace.hpp" |
| #include "gc/g1/g1YoungCollector.hpp" |
| #include "gc/g1/g1YoungGCAllocationFailureInjector.hpp" |
| #include "gc/g1/g1YoungGCPostEvacuateTasks.hpp" |
| #include "gc/g1/g1YoungGCPreEvacuateTasks.hpp" |
| #include "gc/shared/concurrentGCBreakpoints.hpp" |
| #include "gc/shared/gc_globals.hpp" |
| #include "gc/shared/gcTimer.hpp" |
| #include "gc/shared/gcTraceTime.inline.hpp" |
| #include "gc/shared/referenceProcessor.hpp" |
| #include "gc/shared/weakProcessor.inline.hpp" |
| #include "gc/shared/workerPolicy.hpp" |
| #include "gc/shared/workerThread.hpp" |
| #include "jfr/jfrEvents.hpp" |
| #include "memory/resourceArea.hpp" |
| #include "runtime/threads.hpp" |
| #include "utilities/ticks.hpp" |
| |
| // GCTraceTime wrapper that constructs the message according to GC pause type and |
| // GC cause. |
| // The code relies on the fact that GCTraceTimeWrapper stores the string passed |
| // initially as a reference only, so that we can modify it as needed. |
| class G1YoungGCTraceTime { |
| G1YoungCollector* _collector; |
| |
| G1GCPauseType _pause_type; |
| GCCause::Cause _pause_cause; |
| |
| static const uint MaxYoungGCNameLength = 128; |
| char _young_gc_name_data[MaxYoungGCNameLength]; |
| |
| GCTraceTime(Info, gc) _tt; |
| |
| const char* update_young_gc_name() { |
| char evacuation_failed_string[48]; |
| evacuation_failed_string[0] = '\0'; |
| |
| if (_collector->evacuation_failed()) { |
| snprintf(evacuation_failed_string, |
| ARRAY_SIZE(evacuation_failed_string), |
| " (Evacuation Failure: %s%s%s)", |
| _collector->evacuation_alloc_failed() ? "Allocation" : "", |
| _collector->evacuation_alloc_failed() && _collector->evacuation_pinned() ? " / " : "", |
| _collector->evacuation_pinned() ? "Pinned" : ""); |
| } |
| snprintf(_young_gc_name_data, |
| MaxYoungGCNameLength, |
| "Pause Young (%s) (%s)%s", |
| G1GCPauseTypeHelper::to_string(_pause_type), |
| GCCause::to_string(_pause_cause), |
| evacuation_failed_string); |
| return _young_gc_name_data; |
| } |
| |
| public: |
| G1YoungGCTraceTime(G1YoungCollector* collector, GCCause::Cause cause) : |
| _collector(collector), |
| // Take snapshot of current pause type at start as it may be modified during gc. |
| // The strings for all Concurrent Start pauses are the same, so the parameter |
| // does not matter here. |
| _pause_type(_collector->collector_state()->young_gc_pause_type(false /* concurrent_operation_is_full_mark */)), |
| _pause_cause(cause), |
| // Fake a "no cause" and manually add the correct string in update_young_gc_name() |
| // to make the string look more natural. |
| _tt(update_young_gc_name(), nullptr, GCCause::_no_gc, true) { |
| } |
| |
| ~G1YoungGCTraceTime() { |
| update_young_gc_name(); |
| } |
| }; |
| |
| class G1YoungGCNotifyPauseMark : public StackObj { |
| G1YoungCollector* _collector; |
| |
| public: |
| G1YoungGCNotifyPauseMark(G1YoungCollector* collector) : _collector(collector) { |
| G1CollectedHeap::heap()->policy()->record_young_gc_pause_start(); |
| } |
| |
| ~G1YoungGCNotifyPauseMark() { |
| G1CollectedHeap::heap()->policy()->record_young_gc_pause_end(_collector->evacuation_failed()); |
| } |
| }; |
| |
| class G1YoungGCJFRTracerMark : public G1JFRTracerMark { |
| G1EvacInfo _evacuation_info; |
| |
| G1NewTracer* tracer() const { return (G1NewTracer*)_tracer; } |
| |
| public: |
| |
| G1EvacInfo* evacuation_info() { return &_evacuation_info; } |
| |
| G1YoungGCJFRTracerMark(STWGCTimer* gc_timer_stw, G1NewTracer* gc_tracer_stw, GCCause::Cause cause) : |
| G1JFRTracerMark(gc_timer_stw, gc_tracer_stw), _evacuation_info() { } |
| |
| void report_pause_type(G1GCPauseType type) { |
| tracer()->report_young_gc_pause(type); |
| } |
| |
| ~G1YoungGCJFRTracerMark() { |
| G1CollectedHeap* g1h = G1CollectedHeap::heap(); |
| |
| tracer()->report_evacuation_info(&_evacuation_info); |
| tracer()->report_tenuring_threshold(g1h->policy()->tenuring_threshold()); |
| } |
| }; |
| |
| class G1YoungGCVerifierMark : public StackObj { |
| G1YoungCollector* _collector; |
| G1HeapVerifier::G1VerifyType _type; |
| |
| static G1HeapVerifier::G1VerifyType young_collection_verify_type() { |
| G1CollectorState* state = G1CollectedHeap::heap()->collector_state(); |
| if (state->in_concurrent_start_gc()) { |
| return G1HeapVerifier::G1VerifyConcurrentStart; |
| } else if (state->in_young_only_phase()) { |
| return G1HeapVerifier::G1VerifyYoungNormal; |
| } else { |
| return G1HeapVerifier::G1VerifyMixed; |
| } |
| } |
| |
| public: |
| G1YoungGCVerifierMark(G1YoungCollector* collector) : _collector(collector), _type(young_collection_verify_type()) { |
| G1CollectedHeap::heap()->verify_before_young_collection(_type); |
| } |
| |
| ~G1YoungGCVerifierMark() { |
| // Inject evacuation failure tag into type if needed. |
| G1HeapVerifier::G1VerifyType type = _type; |
| if (_collector->evacuation_failed()) { |
| type = (G1HeapVerifier::G1VerifyType)(type | G1HeapVerifier::G1VerifyYoungEvacFail); |
| } |
| G1CollectedHeap::heap()->verify_after_young_collection(type); |
| } |
| }; |
| |
| G1Allocator* G1YoungCollector::allocator() const { |
| return _g1h->allocator(); |
| } |
| |
| G1CollectionSet* G1YoungCollector::collection_set() const { |
| return _g1h->collection_set(); |
| } |
| |
| G1CollectorState* G1YoungCollector::collector_state() const { |
| return _g1h->collector_state(); |
| } |
| |
| G1ConcurrentMark* G1YoungCollector::concurrent_mark() const { |
| return _g1h->concurrent_mark(); |
| } |
| |
| STWGCTimer* G1YoungCollector::gc_timer_stw() const { |
| return _g1h->gc_timer_stw(); |
| } |
| |
| G1NewTracer* G1YoungCollector::gc_tracer_stw() const { |
| return _g1h->gc_tracer_stw(); |
| } |
| |
| G1Policy* G1YoungCollector::policy() const { |
| return _g1h->policy(); |
| } |
| |
| G1GCPhaseTimes* G1YoungCollector::phase_times() const { |
| return _g1h->phase_times(); |
| } |
| |
| G1MonitoringSupport* G1YoungCollector::monitoring_support() const { |
| return _g1h->monitoring_support(); |
| } |
| |
| G1RemSet* G1YoungCollector::rem_set() const { |
| return _g1h->rem_set(); |
| } |
| |
| G1ScannerTasksQueueSet* G1YoungCollector::task_queues() const { |
| return _g1h->task_queues(); |
| } |
| |
| G1SurvivorRegions* G1YoungCollector::survivor_regions() const { |
| return _g1h->survivor(); |
| } |
| |
| ReferenceProcessor* G1YoungCollector::ref_processor_stw() const { |
| return _g1h->ref_processor_stw(); |
| } |
| |
| WorkerThreads* G1YoungCollector::workers() const { |
| return _g1h->workers(); |
| } |
| |
| G1YoungGCAllocationFailureInjector* G1YoungCollector::allocation_failure_injector() const { |
| return _g1h->allocation_failure_injector(); |
| } |
| |
| |
| void G1YoungCollector::wait_for_root_region_scanning() { |
| Ticks start = Ticks::now(); |
| // We have to wait until the CM threads finish scanning the |
| // root regions as it's the only way to ensure that all the |
| // objects on them have been correctly scanned before we start |
| // moving them during the GC. |
| bool waited = concurrent_mark()->wait_until_root_region_scan_finished(); |
| Tickspan wait_time; |
| if (waited) { |
| wait_time = (Ticks::now() - start); |
| } |
| phase_times()->record_root_region_scan_wait_time(wait_time.seconds() * MILLIUNITS); |
| } |
| |
| class G1PrintCollectionSetClosure : public G1HeapRegionClosure { |
| public: |
| virtual bool do_heap_region(G1HeapRegion* r) { |
| G1HeapRegionPrinter::cset(r); |
| return false; |
| } |
| }; |
| |
| void G1YoungCollector::calculate_collection_set(G1EvacInfo* evacuation_info, double target_pause_time_ms) { |
| // Forget the current allocation region (we might even choose it to be part |
| // of the collection set!) before finalizing the collection set. |
| allocator()->release_mutator_alloc_regions(); |
| |
| collection_set()->finalize_initial_collection_set(target_pause_time_ms, survivor_regions()); |
| evacuation_info->set_collection_set_regions(collection_set()->region_length() + |
| collection_set()->num_optional_regions()); |
| |
| concurrent_mark()->verify_no_collection_set_oops(); |
| |
| if (G1HeapRegionPrinter::is_active()) { |
| G1PrintCollectionSetClosure cl; |
| collection_set()->iterate(&cl); |
| collection_set()->iterate_optional(&cl); |
| } |
| } |
| |
| class G1PrepareEvacuationTask : public WorkerTask { |
| class G1PrepareRegionsClosure : public G1HeapRegionClosure { |
| G1CollectedHeap* _g1h; |
| G1PrepareEvacuationTask* _parent_task; |
| uint _worker_humongous_total; |
| uint _worker_humongous_candidates; |
| |
| G1MonotonicArenaMemoryStats _card_set_stats; |
| |
| void sample_card_set_size(G1HeapRegion* hr) { |
| // Sample card set sizes for humongous before GC: this makes the policy to give |
| // back memory to the OS keep the most recent amount of memory for these regions. |
| if (hr->is_starts_humongous()) { |
| _card_set_stats.add(hr->rem_set()->card_set_memory_stats()); |
| } |
| } |
| |
| bool humongous_region_is_candidate(G1HeapRegion* region) const { |
| assert(region->is_starts_humongous(), "Must start a humongous object"); |
| |
| oop obj = cast_to_oop(region->bottom()); |
| |
| // Dead objects cannot be eager reclaim candidates. Due to class |
| // unloading it is unsafe to query their classes so we return early. |
| if (_g1h->is_obj_dead(obj, region)) { |
| return false; |
| } |
| |
| // If we do not have a complete remembered set for the region, then we can |
| // not be sure that we have all references to it. |
| if (!region->rem_set()->is_complete()) { |
| return false; |
| } |
| // We also cannot collect the humongous object if it is pinned. |
| if (region->has_pinned_objects()) { |
| return false; |
| } |
| // Candidate selection must satisfy the following constraints |
| // while concurrent marking is in progress: |
| // |
| // * In order to maintain SATB invariants, an object must not be |
| // reclaimed if it was allocated before the start of marking and |
| // has not had its references scanned. Such an object must have |
| // its references (including type metadata) scanned to ensure no |
| // live objects are missed by the marking process. Objects |
| // allocated after the start of concurrent marking don't need to |
| // be scanned. |
| // |
| // * An object must not be reclaimed if it is on the concurrent |
| // mark stack. Objects allocated after the start of concurrent |
| // marking are never pushed on the mark stack. |
| // |
| // Nominating only objects allocated after the start of concurrent |
| // marking is sufficient to meet both constraints. This may miss |
| // some objects that satisfy the constraints, but the marking data |
| // structures don't support efficiently performing the needed |
| // additional tests or scrubbing of the mark stack. |
| // |
| // However, we presently only nominate is_typeArray() objects. |
| // A humongous object containing references induces remembered |
| // set entries on other regions. In order to reclaim such an |
| // object, those remembered sets would need to be cleaned up. |
| // |
| // We also treat is_typeArray() objects specially, allowing them |
| // to be reclaimed even if allocated before the start of |
| // concurrent mark. For this we rely on mark stack insertion to |
| // exclude is_typeArray() objects, preventing reclaiming an object |
| // that is in the mark stack. We also rely on the metadata for |
| // such objects to be built-in and so ensured to be kept live. |
| // Frequent allocation and drop of large binary blobs is an |
| // important use case for eager reclaim, and this special handling |
| // may reduce needed headroom. |
| |
| return obj->is_typeArray() && |
| _g1h->is_potential_eager_reclaim_candidate(region); |
| } |
| |
| public: |
| G1PrepareRegionsClosure(G1CollectedHeap* g1h, G1PrepareEvacuationTask* parent_task) : |
| _g1h(g1h), |
| _parent_task(parent_task), |
| _worker_humongous_total(0), |
| _worker_humongous_candidates(0) { } |
| |
| ~G1PrepareRegionsClosure() { |
| _parent_task->add_humongous_candidates(_worker_humongous_candidates); |
| _parent_task->add_humongous_total(_worker_humongous_total); |
| } |
| |
| virtual bool do_heap_region(G1HeapRegion* hr) { |
| // First prepare the region for scanning |
| _g1h->rem_set()->prepare_region_for_scan(hr); |
| |
| sample_card_set_size(hr); |
| |
| // Now check if region is a humongous candidate |
| if (!hr->is_starts_humongous()) { |
| _g1h->register_region_with_region_attr(hr); |
| return false; |
| } |
| |
| uint index = hr->hrm_index(); |
| if (humongous_region_is_candidate(hr)) { |
| _g1h->register_humongous_candidate_region_with_region_attr(index); |
| _worker_humongous_candidates++; |
| // We will later handle the remembered sets of these regions. |
| } else { |
| _g1h->register_region_with_region_attr(hr); |
| } |
| log_debug(gc, humongous)("Humongous region %u (object size %zu @ " PTR_FORMAT ") remset %zu code roots %zu " |
| "marked %d pinned count %zu reclaim candidate %d type array %d", |
| index, |
| cast_to_oop(hr->bottom())->size() * HeapWordSize, |
| p2i(hr->bottom()), |
| hr->rem_set()->occupied(), |
| hr->rem_set()->code_roots_list_length(), |
| _g1h->concurrent_mark()->mark_bitmap()->is_marked(hr->bottom()), |
| hr->pinned_count(), |
| _g1h->is_humongous_reclaim_candidate(index), |
| cast_to_oop(hr->bottom())->is_typeArray() |
| ); |
| _worker_humongous_total++; |
| |
| return false; |
| } |
| |
| G1MonotonicArenaMemoryStats card_set_stats() const { |
| return _card_set_stats; |
| } |
| }; |
| |
| G1CollectedHeap* _g1h; |
| G1HeapRegionClaimer _claimer; |
| volatile uint _humongous_total; |
| volatile uint _humongous_candidates; |
| |
| G1MonotonicArenaMemoryStats _all_card_set_stats; |
| |
| public: |
| G1PrepareEvacuationTask(G1CollectedHeap* g1h) : |
| WorkerTask("Prepare Evacuation"), |
| _g1h(g1h), |
| _claimer(_g1h->workers()->active_workers()), |
| _humongous_total(0), |
| _humongous_candidates(0) { } |
| |
| void work(uint worker_id) { |
| G1PrepareRegionsClosure cl(_g1h, this); |
| _g1h->heap_region_par_iterate_from_worker_offset(&cl, &_claimer, worker_id); |
| |
| MutexLocker x(G1RareEvent_lock, Mutex::_no_safepoint_check_flag); |
| _all_card_set_stats.add(cl.card_set_stats()); |
| } |
| |
| void add_humongous_candidates(uint candidates) { |
| Atomic::add(&_humongous_candidates, candidates); |
| } |
| |
| void add_humongous_total(uint total) { |
| Atomic::add(&_humongous_total, total); |
| } |
| |
| uint humongous_candidates() { |
| return _humongous_candidates; |
| } |
| |
| uint humongous_total() { |
| return _humongous_total; |
| } |
| |
| const G1MonotonicArenaMemoryStats all_card_set_stats() const { |
| return _all_card_set_stats; |
| } |
| }; |
| |
| Tickspan G1YoungCollector::run_task_timed(WorkerTask* task) { |
| Ticks start = Ticks::now(); |
| workers()->run_task(task); |
| return Ticks::now() - start; |
| } |
| |
| void G1YoungCollector::set_young_collection_default_active_worker_threads(){ |
| uint active_workers = WorkerPolicy::calc_active_workers(workers()->max_workers(), |
| workers()->active_workers(), |
| Threads::number_of_non_daemon_threads()); |
| active_workers = workers()->set_active_workers(active_workers); |
| log_info(gc,task)("Using %u workers of %u for evacuation", active_workers, workers()->max_workers()); |
| } |
| |
| void G1YoungCollector::pre_evacuate_collection_set(G1EvacInfo* evacuation_info) { |
| // Flush various data in thread-local buffers to be able to determine the collection |
| // set |
| { |
| Ticks start = Ticks::now(); |
| G1PreEvacuateCollectionSetBatchTask cl; |
| G1CollectedHeap::heap()->run_batch_task(&cl); |
| phase_times()->record_pre_evacuate_prepare_time_ms((Ticks::now() - start).seconds() * 1000.0); |
| } |
| |
| // Needs log buffers flushed. |
| calculate_collection_set(evacuation_info, policy()->max_pause_time_ms()); |
| |
| if (collector_state()->in_concurrent_start_gc()) { |
| Ticks start = Ticks::now(); |
| concurrent_mark()->pre_concurrent_start(_gc_cause); |
| phase_times()->record_prepare_concurrent_task_time_ms((Ticks::now() - start).seconds() * 1000.0); |
| } |
| |
| // Please see comment in g1CollectedHeap.hpp and |
| // G1CollectedHeap::ref_processing_init() to see how |
| // reference processing currently works in G1. |
| ref_processor_stw()->start_discovery(false /* always_clear */); |
| |
| _evac_failure_regions.pre_collection(_g1h->max_num_regions()); |
| |
| _g1h->gc_prologue(false); |
| |
| // Initialize the GC alloc regions. |
| allocator()->init_gc_alloc_regions(evacuation_info); |
| |
| { |
| Ticks start = Ticks::now(); |
| rem_set()->prepare_for_scan_heap_roots(); |
| |
| _g1h->prepare_group_cardsets_for_scan(); |
| |
| phase_times()->record_prepare_heap_roots_time_ms((Ticks::now() - start).seconds() * 1000.0); |
| } |
| |
| { |
| G1PrepareEvacuationTask g1_prep_task(_g1h); |
| Tickspan task_time = run_task_timed(&g1_prep_task); |
| |
| G1MonotonicArenaMemoryStats sampled_card_set_stats = g1_prep_task.all_card_set_stats(); |
| sampled_card_set_stats.add(_g1h->young_regions_card_set_memory_stats()); |
| _g1h->set_young_gen_card_set_stats(sampled_card_set_stats); |
| |
| _g1h->set_humongous_stats(g1_prep_task.humongous_total(), g1_prep_task.humongous_candidates()); |
| |
| phase_times()->record_register_regions(task_time.seconds() * 1000.0); |
| } |
| |
| assert(_g1h->verifier()->check_region_attr_table(), "Inconsistency in the region attributes table."); |
| |
| #if COMPILER2_OR_JVMCI |
| DerivedPointerTable::clear(); |
| #endif |
| |
| allocation_failure_injector()->arm_if_needed(); |
| } |
| |
| class G1ParEvacuateFollowersClosure : public VoidClosure { |
| double _start_term; |
| double _term_time; |
| size_t _term_attempts; |
| |
| void start_term_time() { _term_attempts++; _start_term = os::elapsedTime(); } |
| void end_term_time() { _term_time += (os::elapsedTime() - _start_term); } |
| |
| G1CollectedHeap* _g1h; |
| G1ParScanThreadState* _par_scan_state; |
| G1ScannerTasksQueueSet* _queues; |
| TaskTerminator* _terminator; |
| G1GCPhaseTimes::GCParPhases _phase; |
| |
| G1ParScanThreadState* par_scan_state() { return _par_scan_state; } |
| G1ScannerTasksQueueSet* queues() { return _queues; } |
| TaskTerminator* terminator() { return _terminator; } |
| |
| inline bool offer_termination() { |
| EventGCPhaseParallel event; |
| G1ParScanThreadState* const pss = par_scan_state(); |
| start_term_time(); |
| const bool res = (terminator() == nullptr) ? true : terminator()->offer_termination(); |
| end_term_time(); |
| event.commit(GCId::current(), pss->worker_id(), G1GCPhaseTimes::phase_name(G1GCPhaseTimes::Termination)); |
| return res; |
| } |
| |
| public: |
| G1ParEvacuateFollowersClosure(G1CollectedHeap* g1h, |
| G1ParScanThreadState* par_scan_state, |
| G1ScannerTasksQueueSet* queues, |
| TaskTerminator* terminator, |
| G1GCPhaseTimes::GCParPhases phase) |
| : _start_term(0.0), _term_time(0.0), _term_attempts(0), |
| _g1h(g1h), _par_scan_state(par_scan_state), |
| _queues(queues), _terminator(terminator), _phase(phase) {} |
| |
| void do_void() { |
| EventGCPhaseParallel event; |
| G1ParScanThreadState* const pss = par_scan_state(); |
| pss->trim_queue(); |
| event.commit(GCId::current(), pss->worker_id(), G1GCPhaseTimes::phase_name(_phase)); |
| do { |
| EventGCPhaseParallel event; |
| pss->steal_and_trim_queue(queues()); |
| event.commit(GCId::current(), pss->worker_id(), G1GCPhaseTimes::phase_name(_phase)); |
| } while (!offer_termination()); |
| } |
| |
| double term_time() const { return _term_time; } |
| size_t term_attempts() const { return _term_attempts; } |
| }; |
| |
| class G1EvacuateRegionsBaseTask : public WorkerTask { |
| |
| // All pinned regions in the collection set must be registered as failed |
| // regions as there is no guarantee that there is a reference reachable by |
| // Java code (i.e. only by native code) that adds it to the evacuation failed |
| // regions. |
| void record_pinned_regions(G1ParScanThreadState* pss, uint worker_id) { |
| class RecordPinnedRegionClosure : public G1HeapRegionClosure { |
| G1ParScanThreadState* _pss; |
| uint _worker_id; |
| |
| public: |
| RecordPinnedRegionClosure(G1ParScanThreadState* pss, uint worker_id) : _pss(pss), _worker_id(worker_id) { } |
| |
| bool do_heap_region(G1HeapRegion* r) { |
| if (r->has_pinned_objects()) { |
| _pss->record_evacuation_failed_region(r, _worker_id, true /* cause_pinned */); |
| } |
| return false; |
| } |
| } cl(pss, worker_id); |
| |
| _g1h->collection_set_iterate_increment_from(&cl, worker_id); |
| } |
| |
| protected: |
| G1CollectedHeap* _g1h; |
| G1ParScanThreadStateSet* _per_thread_states; |
| |
| G1ScannerTasksQueueSet* _task_queues; |
| TaskTerminator _terminator; |
| |
| void evacuate_live_objects(G1ParScanThreadState* pss, |
| uint worker_id, |
| G1GCPhaseTimes::GCParPhases objcopy_phase, |
| G1GCPhaseTimes::GCParPhases termination_phase) { |
| G1GCPhaseTimes* p = _g1h->phase_times(); |
| |
| Ticks start = Ticks::now(); |
| G1ParEvacuateFollowersClosure cl(_g1h, pss, _task_queues, &_terminator, objcopy_phase); |
| cl.do_void(); |
| |
| assert(pss->queue_is_empty(), "should be empty"); |
| |
| Tickspan evac_time = (Ticks::now() - start); |
| p->record_or_add_time_secs(objcopy_phase, worker_id, evac_time.seconds() - cl.term_time()); |
| |
| if (termination_phase == G1GCPhaseTimes::Termination) { |
| p->record_time_secs(termination_phase, worker_id, cl.term_time()); |
| p->record_thread_work_item(termination_phase, worker_id, cl.term_attempts()); |
| } else { |
| p->record_or_add_time_secs(termination_phase, worker_id, cl.term_time()); |
| p->record_or_add_thread_work_item(termination_phase, worker_id, cl.term_attempts()); |
| } |
| assert(pss->trim_ticks().value() == 0, |
| "Unexpected partial trimming during evacuation value " JLONG_FORMAT, |
| pss->trim_ticks().value()); |
| } |
| |
| virtual void start_work(uint worker_id) { } |
| |
| virtual void end_work(uint worker_id) { } |
| |
| virtual void scan_roots(G1ParScanThreadState* pss, uint worker_id) = 0; |
| |
| virtual void evacuate_live_objects(G1ParScanThreadState* pss, uint worker_id) = 0; |
| |
| private: |
| volatile bool _pinned_regions_recorded; |
| |
| public: |
| G1EvacuateRegionsBaseTask(const char* name, |
| G1ParScanThreadStateSet* per_thread_states, |
| G1ScannerTasksQueueSet* task_queues, |
| uint num_workers) : |
| WorkerTask(name), |
| _g1h(G1CollectedHeap::heap()), |
| _per_thread_states(per_thread_states), |
| _task_queues(task_queues), |
| _terminator(num_workers, _task_queues), |
| _pinned_regions_recorded(false) |
| { } |
| |
| void work(uint worker_id) { |
| start_work(worker_id); |
| |
| { |
| ResourceMark rm; |
| |
| G1ParScanThreadState* pss = _per_thread_states->state_for_worker(worker_id); |
| pss->set_ref_discoverer(_g1h->ref_processor_stw()); |
| |
| if (!Atomic::cmpxchg(&_pinned_regions_recorded, false, true)) { |
| record_pinned_regions(pss, worker_id); |
| } |
| scan_roots(pss, worker_id); |
| evacuate_live_objects(pss, worker_id); |
| } |
| |
| end_work(worker_id); |
| } |
| }; |
| |
| class G1EvacuateRegionsTask : public G1EvacuateRegionsBaseTask { |
| G1RootProcessor* _root_processor; |
| bool _has_optional_evacuation_work; |
| |
| void scan_roots(G1ParScanThreadState* pss, uint worker_id) { |
| _root_processor->evacuate_roots(pss, worker_id); |
| _g1h->rem_set()->scan_heap_roots(pss, worker_id, G1GCPhaseTimes::ScanHR, G1GCPhaseTimes::ObjCopy, _has_optional_evacuation_work); |
| _g1h->rem_set()->scan_collection_set_code_roots(pss, worker_id, G1GCPhaseTimes::CodeRoots, G1GCPhaseTimes::ObjCopy); |
| // There are no optional roots to scan right now. |
| #ifdef ASSERT |
| class VerifyOptionalCollectionSetRootsEmptyClosure : public G1HeapRegionClosure { |
| G1ParScanThreadState* _pss; |
| |
| public: |
| VerifyOptionalCollectionSetRootsEmptyClosure(G1ParScanThreadState* pss) : _pss(pss) { } |
| |
| bool do_heap_region(G1HeapRegion* r) override { |
| assert(!r->has_index_in_opt_cset(), "must be"); |
| return false; |
| } |
| } cl(pss); |
| _g1h->collection_set_iterate_increment_from(&cl, worker_id); |
| #endif |
| } |
| |
| void evacuate_live_objects(G1ParScanThreadState* pss, uint worker_id) { |
| G1EvacuateRegionsBaseTask::evacuate_live_objects(pss, worker_id, G1GCPhaseTimes::ObjCopy, G1GCPhaseTimes::Termination); |
| } |
| |
| void start_work(uint worker_id) { |
| _g1h->phase_times()->record_time_secs(G1GCPhaseTimes::GCWorkerStart, worker_id, Ticks::now().seconds()); |
| } |
| |
| void end_work(uint worker_id) { |
| _g1h->phase_times()->record_time_secs(G1GCPhaseTimes::GCWorkerEnd, worker_id, Ticks::now().seconds()); |
| } |
| |
| public: |
| G1EvacuateRegionsTask(G1CollectedHeap* g1h, |
| G1ParScanThreadStateSet* per_thread_states, |
| G1ScannerTasksQueueSet* task_queues, |
| G1RootProcessor* root_processor, |
| uint num_workers, |
| bool has_optional_evacuation_work) : |
| G1EvacuateRegionsBaseTask("G1 Evacuate Regions", per_thread_states, task_queues, num_workers), |
| _root_processor(root_processor), |
| _has_optional_evacuation_work(has_optional_evacuation_work) |
| { } |
| }; |
| |
| void G1YoungCollector::evacuate_initial_collection_set(G1ParScanThreadStateSet* per_thread_states, |
| bool has_optional_evacuation_work) { |
| G1GCPhaseTimes* p = phase_times(); |
| |
| rem_set()->merge_heap_roots(true /* initial_evacuation */); |
| |
| Tickspan task_time; |
| const uint num_workers = workers()->active_workers(); |
| |
| Ticks start_processing = Ticks::now(); |
| { |
| G1RootProcessor root_processor(_g1h, num_workers); |
| G1EvacuateRegionsTask g1_par_task(_g1h, |
| per_thread_states, |
| task_queues(), |
| &root_processor, |
| num_workers, |
| has_optional_evacuation_work); |
| task_time = run_task_timed(&g1_par_task); |
| // Closing the inner scope will execute the destructor for the |
| // G1RootProcessor object. By subtracting the WorkerThreads task from the total |
| // time of this scope, we get the "NMethod List Cleanup" time. This list is |
| // constructed during "STW two-phase nmethod root processing", see more in |
| // nmethod.hpp |
| } |
| Tickspan total_processing = Ticks::now() - start_processing; |
| |
| p->record_initial_evac_time(task_time.seconds() * 1000.0); |
| p->record_or_add_nmethod_list_cleanup_time((total_processing - task_time).seconds() * 1000.0); |
| |
| rem_set()->complete_evac_phase(has_optional_evacuation_work); |
| } |
| |
| class G1EvacuateOptionalRegionsTask : public G1EvacuateRegionsBaseTask { |
| |
| void scan_roots(G1ParScanThreadState* pss, uint worker_id) { |
| _g1h->rem_set()->scan_heap_roots(pss, worker_id, G1GCPhaseTimes::OptScanHR, G1GCPhaseTimes::OptObjCopy, true /* remember_already_scanned_cards */); |
| _g1h->rem_set()->scan_collection_set_code_roots(pss, worker_id, G1GCPhaseTimes::OptCodeRoots, G1GCPhaseTimes::OptObjCopy); |
| _g1h->rem_set()->scan_collection_set_optional_roots(pss, worker_id, G1GCPhaseTimes::OptScanHR, G1GCPhaseTimes::ObjCopy); |
| } |
| |
| void evacuate_live_objects(G1ParScanThreadState* pss, uint worker_id) { |
| G1EvacuateRegionsBaseTask::evacuate_live_objects(pss, worker_id, G1GCPhaseTimes::OptObjCopy, G1GCPhaseTimes::OptTermination); |
| } |
| |
| public: |
| G1EvacuateOptionalRegionsTask(G1ParScanThreadStateSet* per_thread_states, |
| G1ScannerTasksQueueSet* queues, |
| uint num_workers) : |
| G1EvacuateRegionsBaseTask("G1 Evacuate Optional Regions", per_thread_states, queues, num_workers) { |
| } |
| }; |
| |
| void G1YoungCollector::evacuate_next_optional_regions(G1ParScanThreadStateSet* per_thread_states) { |
| // To access the protected constructor/destructor |
| class G1MarkScope : public MarkScope { }; |
| |
| Tickspan task_time; |
| |
| Ticks start_processing = Ticks::now(); |
| { |
| G1MarkScope code_mark_scope; |
| G1EvacuateOptionalRegionsTask task(per_thread_states, task_queues(), workers()->active_workers()); |
| task_time = run_task_timed(&task); |
| // See comment in evacuate_initial_collection_set() for the reason of the scope. |
| } |
| Tickspan total_processing = Ticks::now() - start_processing; |
| |
| G1GCPhaseTimes* p = phase_times(); |
| p->record_or_add_optional_evac_time(task_time.seconds() * 1000.0); |
| p->record_or_add_nmethod_list_cleanup_time((total_processing - task_time).seconds() * 1000.0); |
| } |
| |
| void G1YoungCollector::evacuate_optional_collection_set(G1ParScanThreadStateSet* per_thread_states) { |
| const double collection_start_time_ms = phase_times()->cur_collection_start_sec() * 1000.0; |
| |
| while (!evacuation_alloc_failed() && collection_set()->num_optional_regions() > 0) { |
| |
| double time_used_ms = os::elapsedTime() * 1000.0 - collection_start_time_ms; |
| double time_left_ms = MaxGCPauseMillis - time_used_ms; |
| |
| if (time_left_ms < 0 || |
| !collection_set()->finalize_optional_for_evacuation(time_left_ms * policy()->optional_evacuation_fraction())) { |
| log_trace(gc, ergo, cset)("Skipping evacuation of %u optional regions, no more regions can be evacuated in %.3fms", |
| collection_set()->num_optional_regions(), time_left_ms); |
| break; |
| } |
| |
| rem_set()->merge_heap_roots(false /* initial_evacuation */); |
| |
| evacuate_next_optional_regions(per_thread_states); |
| |
| rem_set()->complete_evac_phase(true /* has_more_than_one_evacuation_phase */); |
| } |
| |
| collection_set()->abandon_optional_collection_set(per_thread_states); |
| } |
| |
| // Non Copying Keep Alive closure |
| class G1KeepAliveClosure: public OopClosure { |
| G1CollectedHeap*_g1h; |
| public: |
| G1KeepAliveClosure(G1CollectedHeap* g1h) :_g1h(g1h) {} |
| void do_oop(narrowOop* p) { guarantee(false, "Not needed"); } |
| void do_oop(oop* p) { |
| oop obj = *p; |
| assert(obj != nullptr, "the caller should have filtered out null values"); |
| |
| const G1HeapRegionAttr region_attr =_g1h->region_attr(obj); |
| if (!region_attr.is_in_cset_or_humongous_candidate()) { |
| return; |
| } |
| if (region_attr.is_in_cset()) { |
| assert(obj->is_forwarded(), "invariant" ); |
| *p = obj->forwardee(); |
| } else { |
| assert(!obj->is_forwarded(), "invariant" ); |
| assert(region_attr.is_humongous_candidate(), |
| "Only allowed G1HeapRegionAttr state is IsHumongous, but is %d", region_attr.type()); |
| _g1h->set_humongous_is_live(obj); |
| } |
| } |
| }; |
| |
| // Copying Keep Alive closure - can be called from both |
| // serial and parallel code as long as different worker |
| // threads utilize different G1ParScanThreadState instances |
| // and different queues. |
| class G1CopyingKeepAliveClosure: public OopClosure { |
| G1CollectedHeap* _g1h; |
| G1ParScanThreadState* _par_scan_state; |
| |
| public: |
| G1CopyingKeepAliveClosure(G1CollectedHeap* g1h, |
| G1ParScanThreadState* pss): |
| _g1h(g1h), |
| _par_scan_state(pss) |
| {} |
| |
| virtual void do_oop(narrowOop* p) { do_oop_work(p); } |
| virtual void do_oop( oop* p) { do_oop_work(p); } |
| |
| template <class T> void do_oop_work(T* p) { |
| oop obj = RawAccess<>::oop_load(p); |
| |
| if (_g1h->is_in_cset_or_humongous_candidate(obj)) { |
| // If the referent object has been forwarded (either copied |
| // to a new location or to itself in the event of an |
| // evacuation failure) then we need to update the reference |
| // field and, if both reference and referent are in the G1 |
| // heap, update the RSet for the referent. |
| // |
| // If the referent has not been forwarded then we have to keep |
| // it alive by policy. Therefore we have copy the referent. |
| // |
| // When the queue is drained (after each phase of reference processing) |
| // the object and it's followers will be copied, the reference field set |
| // to point to the new location, and the RSet updated. |
| _par_scan_state->push_on_queue(ScannerTask(p)); |
| } |
| } |
| }; |
| |
| class G1STWRefProcProxyTask : public RefProcProxyTask { |
| G1CollectedHeap& _g1h; |
| G1ParScanThreadStateSet& _pss; |
| TaskTerminator _terminator; |
| G1ScannerTasksQueueSet& _task_queues; |
| |
| // Special closure for enqueuing discovered fields: during enqueue the card table |
| // may not be in shape to properly handle normal barrier calls (e.g. card marks |
| // in regions that failed evacuation, scribbling of various values by card table |
| // scan code). Additionally the regular barrier enqueues into the "global" |
| // DCQS, but during GC we need these to-be-refined entries in the GC local queue |
| // so that after clearing the card table, the redirty cards phase will properly |
| // mark all dirty cards to be picked up by refinement. |
| class G1EnqueueDiscoveredFieldClosure : public EnqueueDiscoveredFieldClosure { |
| G1CollectedHeap* _g1h; |
| G1ParScanThreadState* _pss; |
| |
| public: |
| G1EnqueueDiscoveredFieldClosure(G1CollectedHeap* g1h, G1ParScanThreadState* pss) : _g1h(g1h), _pss(pss) { } |
| |
| void enqueue(HeapWord* discovered_field_addr, oop value) override { |
| assert(_g1h->is_in(discovered_field_addr), PTR_FORMAT " is not in heap ", p2i(discovered_field_addr)); |
| // Store the value first, whatever it is. |
| RawAccess<>::oop_store(discovered_field_addr, value); |
| if (value == nullptr) { |
| return; |
| } |
| _pss->write_ref_field_post(discovered_field_addr, value); |
| } |
| }; |
| |
| public: |
| G1STWRefProcProxyTask(uint max_workers, G1CollectedHeap& g1h, G1ParScanThreadStateSet& pss, G1ScannerTasksQueueSet& task_queues) |
| : RefProcProxyTask("G1STWRefProcProxyTask", max_workers), |
| _g1h(g1h), |
| _pss(pss), |
| _terminator(max_workers, &task_queues), |
| _task_queues(task_queues) {} |
| |
| void work(uint worker_id) override { |
| assert(worker_id < _max_workers, "sanity"); |
| uint index = (_tm == RefProcThreadModel::Single) ? 0 : worker_id; |
| |
| G1ParScanThreadState* pss = _pss.state_for_worker(index); |
| pss->set_ref_discoverer(nullptr); |
| |
| G1STWIsAliveClosure is_alive(&_g1h); |
| G1CopyingKeepAliveClosure keep_alive(&_g1h, pss); |
| G1EnqueueDiscoveredFieldClosure enqueue(&_g1h, pss); |
| G1ParEvacuateFollowersClosure complete_gc(&_g1h, pss, &_task_queues, _tm == RefProcThreadModel::Single ? nullptr : &_terminator, G1GCPhaseTimes::ObjCopy); |
| _rp_task->rp_work(worker_id, &is_alive, &keep_alive, &enqueue, &complete_gc); |
| |
| // We have completed copying any necessary live referent objects. |
| assert(pss->queue_is_empty(), "both queue and overflow should be empty"); |
| } |
| |
| void prepare_run_task_hook() override { |
| _terminator.reset_for_reuse(_queue_count); |
| } |
| }; |
| |
| void G1YoungCollector::process_discovered_references(G1ParScanThreadStateSet* per_thread_states) { |
| Ticks start = Ticks::now(); |
| |
| ReferenceProcessor* rp = ref_processor_stw(); |
| assert(rp->discovery_enabled(), "should have been enabled"); |
| |
| uint no_of_gc_workers = workers()->active_workers(); |
| rp->set_active_mt_degree(no_of_gc_workers); |
| |
| G1STWRefProcProxyTask task(rp->max_num_queues(), *_g1h, *per_thread_states, *task_queues()); |
| ReferenceProcessorPhaseTimes& pt = *phase_times()->ref_phase_times(); |
| ReferenceProcessorStats stats = rp->process_discovered_references(task, pt); |
| |
| gc_tracer_stw()->report_gc_reference_stats(stats); |
| |
| _g1h->make_pending_list_reachable(); |
| |
| phase_times()->record_ref_proc_time((Ticks::now() - start).seconds() * MILLIUNITS); |
| } |
| |
| void G1YoungCollector::post_evacuate_cleanup_1(G1ParScanThreadStateSet* per_thread_states) { |
| Ticks start = Ticks::now(); |
| { |
| G1PostEvacuateCollectionSetCleanupTask1 cl(per_thread_states, &_evac_failure_regions); |
| _g1h->run_batch_task(&cl); |
| } |
| phase_times()->record_post_evacuate_cleanup_task_1_time((Ticks::now() - start).seconds() * 1000.0); |
| } |
| |
| void G1YoungCollector::post_evacuate_cleanup_2(G1ParScanThreadStateSet* per_thread_states, |
| G1EvacInfo* evacuation_info) { |
| Ticks start = Ticks::now(); |
| { |
| G1PostEvacuateCollectionSetCleanupTask2 cl(per_thread_states, evacuation_info, &_evac_failure_regions); |
| _g1h->run_batch_task(&cl); |
| } |
| phase_times()->record_post_evacuate_cleanup_task_2_time((Ticks::now() - start).seconds() * 1000.0); |
| } |
| |
| void G1YoungCollector::enqueue_candidates_as_root_regions() { |
| assert(collector_state()->in_concurrent_start_gc(), "must be"); |
| |
| G1CollectionSetCandidates* candidates = collection_set()->candidates(); |
| candidates->iterate_regions([&] (G1HeapRegion* r) { |
| _g1h->concurrent_mark()->add_root_region(r); |
| }); |
| } |
| |
| void G1YoungCollector::post_evacuate_collection_set(G1EvacInfo* evacuation_info, |
| G1ParScanThreadStateSet* per_thread_states) { |
| G1GCPhaseTimes* p = phase_times(); |
| |
| // Process any discovered reference objects - we have |
| // to do this _before_ we retire the GC alloc regions |
| // as we may have to copy some 'reachable' referent |
| // objects (and their reachable sub-graphs) that were |
| // not copied during the pause. |
| process_discovered_references(per_thread_states); |
| |
| G1STWIsAliveClosure is_alive(_g1h); |
| G1KeepAliveClosure keep_alive(_g1h); |
| |
| WeakProcessor::weak_oops_do(workers(), &is_alive, &keep_alive, p->weak_phase_times()); |
| |
| allocator()->release_gc_alloc_regions(evacuation_info); |
| |
| #if TASKQUEUE_STATS |
| // Logging uses thread states, which are deleted by cleanup, so this must |
| // be done before cleanup. |
| per_thread_states->print_partial_array_task_stats(); |
| #endif // TASKQUEUE_STATS |
| |
| post_evacuate_cleanup_1(per_thread_states); |
| |
| post_evacuate_cleanup_2(per_thread_states, evacuation_info); |
| |
| // Regions in the collection set candidates are roots for the marking (they are |
| // not marked through considering they are very likely to be reclaimed soon. |
| // They need to be enqueued explicitly compared to survivor regions. |
| if (collector_state()->in_concurrent_start_gc()) { |
| enqueue_candidates_as_root_regions(); |
| } |
| |
| _evac_failure_regions.post_collection(); |
| |
| assert_used_and_recalculate_used_equal(_g1h); |
| |
| _g1h->rebuild_free_region_list(); |
| |
| _g1h->record_obj_copy_mem_stats(); |
| |
| evacuation_info->set_bytes_used(_g1h->bytes_used_during_gc()); |
| |
| _g1h->prepare_for_mutator_after_young_collection(); |
| |
| _g1h->gc_epilogue(false); |
| |
| _g1h->expand_heap_after_young_collection(); |
| } |
| |
| bool G1YoungCollector::evacuation_failed() const { |
| return _evac_failure_regions.has_regions_evac_failed(); |
| } |
| |
| bool G1YoungCollector::evacuation_pinned() const { |
| return _evac_failure_regions.has_regions_evac_pinned(); |
| } |
| |
| bool G1YoungCollector::evacuation_alloc_failed() const { |
| return _evac_failure_regions.has_regions_alloc_failed(); |
| } |
| |
| G1YoungCollector::G1YoungCollector(GCCause::Cause gc_cause) : |
| _g1h(G1CollectedHeap::heap()), |
| _gc_cause(gc_cause), |
| _concurrent_operation_is_full_mark(false), |
| _evac_failure_regions() |
| { |
| } |
| |
| void G1YoungCollector::collect() { |
| // Do timing/tracing/statistics/pre- and post-logging/verification work not |
| // directly related to the collection. They should not be accounted for in |
| // collection work timing. |
| |
| // The G1YoungGCTraceTime message depends on collector state, so must come after |
| // determining collector state. |
| G1YoungGCTraceTime tm(this, _gc_cause); |
| |
| // JFR |
| G1YoungGCJFRTracerMark jtm(gc_timer_stw(), gc_tracer_stw(), _gc_cause); |
| // JStat/MXBeans |
| G1YoungGCMonitoringScope ms(monitoring_support(), |
| !collection_set()->candidates()->is_empty() /* all_memory_pools_affected */); |
| // Create the heap printer before internal pause timing to have |
| // heap information printed as last part of detailed GC log. |
| G1HeapPrinterMark hpm(_g1h); |
| // Young GC internal pause timing |
| G1YoungGCNotifyPauseMark npm(this); |
| |
| // Verification may use the workers, so they must be set up before. |
| // Individual parallel phases may override this. |
| set_young_collection_default_active_worker_threads(); |
| |
| // Wait for root region scan here to make sure that it is done before any |
| // use of the STW workers to maximize cpu use (i.e. all cores are available |
| // just to do that). |
| wait_for_root_region_scanning(); |
| |
| G1YoungGCVerifierMark vm(this); |
| { |
| // Actual collection work starts and is executed (only) in this scope. |
| |
| // Young GC internal collection timing. The elapsed time recorded in the |
| // policy for the collection deliberately elides verification (and some |
| // other trivial setup above). |
| policy()->record_young_collection_start(); |
| |
| pre_evacuate_collection_set(jtm.evacuation_info()); |
| |
| G1ParScanThreadStateSet per_thread_states(_g1h, |
| workers()->active_workers(), |
| collection_set(), |
| &_evac_failure_regions); |
| |
| bool may_do_optional_evacuation = collection_set()->num_optional_regions() != 0; |
| // Actually do the work... |
| evacuate_initial_collection_set(&per_thread_states, may_do_optional_evacuation); |
| |
| if (may_do_optional_evacuation) { |
| evacuate_optional_collection_set(&per_thread_states); |
| } |
| post_evacuate_collection_set(jtm.evacuation_info(), &per_thread_states); |
| |
| // Refine the type of a concurrent mark operation now that we did the |
| // evacuation, eventually aborting it. |
| _concurrent_operation_is_full_mark = policy()->concurrent_operation_is_full_mark("Revise IHOP"); |
| |
| // Need to report the collection pause now since record_collection_pause_end() |
| // modifies it to the next state. |
| jtm.report_pause_type(collector_state()->young_gc_pause_type(_concurrent_operation_is_full_mark)); |
| |
| policy()->record_young_collection_end(_concurrent_operation_is_full_mark, evacuation_alloc_failed()); |
| } |
| TASKQUEUE_STATS_ONLY(_g1h->task_queues()->print_and_reset_taskqueue_stats("Oop Queue");) |
| } |