| /* |
| * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. |
| * Copyright (c) 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 "gc/shenandoah/heuristics/shenandoahOldHeuristics.hpp" |
| #include "gc/shenandoah/shenandoahAsserts.hpp" |
| #include "gc/shenandoah/shenandoahCardTable.hpp" |
| #include "gc/shenandoah/shenandoahClosures.inline.hpp" |
| #include "gc/shenandoah/shenandoahCollectorPolicy.hpp" |
| #include "gc/shenandoah/shenandoahFreeSet.hpp" |
| #include "gc/shenandoah/shenandoahGenerationalHeap.hpp" |
| #include "gc/shenandoah/shenandoahHeap.hpp" |
| #include "gc/shenandoah/shenandoahHeap.inline.hpp" |
| #include "gc/shenandoah/shenandoahHeapRegion.hpp" |
| #include "gc/shenandoah/shenandoahHeapRegionClosures.hpp" |
| #include "gc/shenandoah/shenandoahMonitoringSupport.hpp" |
| #include "gc/shenandoah/shenandoahOldGeneration.hpp" |
| #include "gc/shenandoah/shenandoahReferenceProcessor.hpp" |
| #include "gc/shenandoah/shenandoahScanRemembered.inline.hpp" |
| #include "gc/shenandoah/shenandoahUtils.hpp" |
| #include "gc/shenandoah/shenandoahWorkerPolicy.hpp" |
| #include "gc/shenandoah/shenandoahYoungGeneration.hpp" |
| #include "runtime/threads.hpp" |
| #include "utilities/events.hpp" |
| |
| class ShenandoahFlushAllSATB : public ThreadClosure { |
| private: |
| SATBMarkQueueSet& _satb_qset; |
| |
| public: |
| explicit ShenandoahFlushAllSATB(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)); |
| } |
| }; |
| |
| class ShenandoahProcessOldSATB : public SATBBufferClosure { |
| private: |
| ShenandoahObjToScanQueue* _queue; |
| ShenandoahHeap* _heap; |
| ShenandoahMarkingContext* const _mark_context; |
| size_t _trashed_oops; |
| |
| public: |
| explicit ShenandoahProcessOldSATB(ShenandoahObjToScanQueue* q) : |
| _queue(q), |
| _heap(ShenandoahHeap::heap()), |
| _mark_context(_heap->marking_context()), |
| _trashed_oops(0) {} |
| |
| void do_buffer(void** buffer, size_t size) override { |
| assert(size == 0 || !_heap->has_forwarded_objects() || _heap->is_concurrent_old_mark_in_progress(), "Forwarded objects are not expected here"); |
| for (size_t i = 0; i < size; ++i) { |
| oop *p = (oop *) &buffer[i]; |
| ShenandoahHeapRegion* region = _heap->heap_region_containing(*p); |
| if (region->is_old() && region->is_active()) { |
| ShenandoahMark::mark_through_ref<oop, OLD>(p, _queue, nullptr, _mark_context, false); |
| } else { |
| _trashed_oops++; |
| } |
| } |
| } |
| |
| size_t trashed_oops() const { |
| return _trashed_oops; |
| } |
| }; |
| |
| class ShenandoahPurgeSATBTask : public WorkerTask { |
| private: |
| ShenandoahObjToScanQueueSet* _mark_queues; |
| // Keep track of the number of oops that are not transferred to mark queues. |
| // This is volatile because workers update it, but the vm thread reads it. |
| volatile size_t _trashed_oops; |
| |
| public: |
| explicit ShenandoahPurgeSATBTask(ShenandoahObjToScanQueueSet* queues) : |
| WorkerTask("Purge SATB"), |
| _mark_queues(queues), |
| _trashed_oops(0) { |
| Threads::change_thread_claim_token(); |
| } |
| |
| ~ShenandoahPurgeSATBTask() { |
| if (_trashed_oops > 0) { |
| log_debug(gc)("Purged %zu oops from old generation SATB buffers", _trashed_oops); |
| } |
| } |
| |
| void work(uint worker_id) override { |
| ShenandoahParallelWorkerSession worker_session(worker_id); |
| ShenandoahSATBMarkQueueSet &satb_queues = ShenandoahBarrierSet::satb_mark_queue_set(); |
| ShenandoahFlushAllSATB flusher(satb_queues); |
| Threads::possibly_parallel_threads_do(true /* is_par */, &flusher); |
| |
| ShenandoahObjToScanQueue* mark_queue = _mark_queues->queue(worker_id); |
| ShenandoahProcessOldSATB processor(mark_queue); |
| while (satb_queues.apply_closure_to_completed_buffer(&processor)) {} |
| |
| Atomic::add(&_trashed_oops, processor.trashed_oops()); |
| } |
| }; |
| |
| class ShenandoahTransferOldSATBTask : public WorkerTask { |
| ShenandoahSATBMarkQueueSet& _satb_queues; |
| ShenandoahObjToScanQueueSet* _mark_queues; |
| // Keep track of the number of oops that are not transferred to mark queues. |
| // This is volatile because workers update it, but the control thread reads it. |
| volatile size_t _trashed_oops; |
| |
| public: |
| explicit ShenandoahTransferOldSATBTask(ShenandoahSATBMarkQueueSet& satb_queues, ShenandoahObjToScanQueueSet* mark_queues) : |
| WorkerTask("Transfer SATB"), |
| _satb_queues(satb_queues), |
| _mark_queues(mark_queues), |
| _trashed_oops(0) {} |
| |
| ~ShenandoahTransferOldSATBTask() { |
| if (_trashed_oops > 0) { |
| log_debug(gc)("Purged %zu oops from old generation SATB buffers", _trashed_oops); |
| } |
| } |
| |
| void work(uint worker_id) override { |
| ShenandoahObjToScanQueue* mark_queue = _mark_queues->queue(worker_id); |
| ShenandoahProcessOldSATB processor(mark_queue); |
| while (_satb_queues.apply_closure_to_completed_buffer(&processor)) {} |
| |
| Atomic::add(&_trashed_oops, processor.trashed_oops()); |
| } |
| }; |
| |
| class ShenandoahConcurrentCoalesceAndFillTask : public WorkerTask { |
| private: |
| uint _nworkers; |
| ShenandoahHeapRegion** _coalesce_and_fill_region_array; |
| uint _coalesce_and_fill_region_count; |
| volatile bool _is_preempted; |
| |
| public: |
| ShenandoahConcurrentCoalesceAndFillTask(uint nworkers, |
| ShenandoahHeapRegion** coalesce_and_fill_region_array, |
| uint region_count) : |
| WorkerTask("Shenandoah Concurrent Coalesce and Fill"), |
| _nworkers(nworkers), |
| _coalesce_and_fill_region_array(coalesce_and_fill_region_array), |
| _coalesce_and_fill_region_count(region_count), |
| _is_preempted(false) { |
| } |
| |
| void work(uint worker_id) override { |
| ShenandoahWorkerTimingsTracker timer(ShenandoahPhaseTimings::conc_coalesce_and_fill, ShenandoahPhaseTimings::ScanClusters, worker_id); |
| for (uint region_idx = worker_id; region_idx < _coalesce_and_fill_region_count; region_idx += _nworkers) { |
| ShenandoahHeapRegion* r = _coalesce_and_fill_region_array[region_idx]; |
| if (r->is_humongous()) { |
| // There is only one object in this region and it is not garbage, |
| // so no need to coalesce or fill. |
| continue; |
| } |
| |
| if (!r->oop_coalesce_and_fill(true)) { |
| // Coalesce and fill has been preempted |
| Atomic::store(&_is_preempted, true); |
| return; |
| } |
| } |
| } |
| |
| // Value returned from is_completed() is only valid after all worker thread have terminated. |
| bool is_completed() { |
| return !Atomic::load(&_is_preempted); |
| } |
| }; |
| |
| ShenandoahOldGeneration::ShenandoahOldGeneration(uint max_queues, size_t max_capacity, size_t soft_max_capacity) |
| : ShenandoahGeneration(OLD, max_queues, max_capacity, soft_max_capacity), |
| _coalesce_and_fill_region_array(NEW_C_HEAP_ARRAY(ShenandoahHeapRegion*, ShenandoahHeap::heap()->num_regions(), mtGC)), |
| _old_heuristics(nullptr), |
| _region_balance(0), |
| _promoted_reserve(0), |
| _promoted_expended(0), |
| _promotion_potential(0), |
| _pad_for_promote_in_place(0), |
| _promotable_humongous_regions(0), |
| _promotable_regular_regions(0), |
| _is_parsable(true), |
| _card_scan(nullptr), |
| _state(WAITING_FOR_BOOTSTRAP), |
| _growth_before_compaction(INITIAL_GROWTH_BEFORE_COMPACTION), |
| _min_growth_before_compaction ((ShenandoahMinOldGenGrowthPercent * FRACTIONAL_DENOMINATOR) / 100) |
| { |
| _live_bytes_after_last_mark = ShenandoahHeap::heap()->capacity() * INITIAL_LIVE_FRACTION / FRACTIONAL_DENOMINATOR; |
| // Always clear references for old generation |
| ref_processor()->set_soft_reference_policy(true); |
| |
| if (ShenandoahCardBarrier) { |
| ShenandoahCardTable* card_table = ShenandoahBarrierSet::barrier_set()->card_table(); |
| size_t card_count = card_table->cards_required(ShenandoahHeap::heap()->reserved_region().word_size()); |
| auto rs = new ShenandoahDirectCardMarkRememberedSet(card_table, card_count); |
| _card_scan = new ShenandoahScanRemembered(rs); |
| } |
| } |
| |
| void ShenandoahOldGeneration::set_promoted_reserve(size_t new_val) { |
| shenandoah_assert_heaplocked_or_safepoint(); |
| _promoted_reserve = new_val; |
| } |
| |
| size_t ShenandoahOldGeneration::get_promoted_reserve() const { |
| return _promoted_reserve; |
| } |
| |
| void ShenandoahOldGeneration::augment_promoted_reserve(size_t increment) { |
| shenandoah_assert_heaplocked_or_safepoint(); |
| _promoted_reserve += increment; |
| } |
| |
| void ShenandoahOldGeneration::reset_promoted_expended() { |
| shenandoah_assert_heaplocked_or_safepoint(); |
| Atomic::store(&_promoted_expended, (size_t) 0); |
| } |
| |
| size_t ShenandoahOldGeneration::expend_promoted(size_t increment) { |
| shenandoah_assert_heaplocked_or_safepoint(); |
| assert(get_promoted_expended() + increment <= get_promoted_reserve(), "Do not expend more promotion than budgeted"); |
| return Atomic::add(&_promoted_expended, increment); |
| } |
| |
| size_t ShenandoahOldGeneration::unexpend_promoted(size_t decrement) { |
| return Atomic::sub(&_promoted_expended, decrement); |
| } |
| |
| size_t ShenandoahOldGeneration::get_promoted_expended() const { |
| return Atomic::load(&_promoted_expended); |
| } |
| |
| bool ShenandoahOldGeneration::can_allocate(const ShenandoahAllocRequest &req) const { |
| assert(req.type() != ShenandoahAllocRequest::_alloc_gclab, "GCLAB pertains only to young-gen memory"); |
| |
| const size_t requested_bytes = req.size() * HeapWordSize; |
| // The promotion reserve may also be used for evacuations. If we can promote this object, |
| // then we can also evacuate it. |
| if (can_promote(requested_bytes)) { |
| // The promotion reserve should be able to accommodate this request. The request |
| // might still fail if alignment with the card table increases the size. The request |
| // may also fail if the heap is badly fragmented and the free set cannot find room for it. |
| return true; |
| } |
| |
| if (req.type() == ShenandoahAllocRequest::_alloc_plab) { |
| // The promotion reserve cannot accommodate this plab request. Check if we still have room for |
| // evacuations. Note that we cannot really know how much of the plab will be used for evacuations, |
| // so here we only check that some evacuation reserve still exists. |
| return get_evacuation_reserve() > 0; |
| } |
| |
| // This is a shared allocation request. We've already checked that it can't be promoted, so if |
| // it is a promotion, we return false. Otherwise, it is a shared evacuation request, and we allow |
| // the allocation to proceed. |
| return !req.is_promotion(); |
| } |
| |
| void |
| ShenandoahOldGeneration::configure_plab_for_current_thread(const ShenandoahAllocRequest &req) { |
| // Note: Even when a mutator is performing a promotion outside a LAB, we use a 'shared_gc' request. |
| if (req.is_gc_alloc()) { |
| const size_t actual_size = req.actual_size() * HeapWordSize; |
| if (req.type() == ShenandoahAllocRequest::_alloc_plab) { |
| // We've created a new plab. Now we configure it whether it will be used for promotions |
| // and evacuations - or just evacuations. |
| Thread* thread = Thread::current(); |
| ShenandoahThreadLocalData::reset_plab_promoted(thread); |
| |
| // The actual size of the allocation may be larger than the requested bytes (due to alignment on card boundaries). |
| // If this puts us over our promotion budget, we need to disable future PLAB promotions for this thread. |
| if (can_promote(actual_size)) { |
| // Assume the entirety of this PLAB will be used for promotion. This prevents promotion from overreach. |
| // When we retire this plab, we'll unexpend what we don't really use. |
| expend_promoted(actual_size); |
| ShenandoahThreadLocalData::enable_plab_promotions(thread); |
| ShenandoahThreadLocalData::set_plab_actual_size(thread, actual_size); |
| } else { |
| // Disable promotions in this thread because entirety of this PLAB must be available to hold old-gen evacuations. |
| ShenandoahThreadLocalData::disable_plab_promotions(thread); |
| ShenandoahThreadLocalData::set_plab_actual_size(thread, 0); |
| } |
| } else if (req.is_promotion()) { |
| // Shared promotion. |
| expend_promoted(actual_size); |
| } |
| } |
| } |
| |
| size_t ShenandoahOldGeneration::get_live_bytes_after_last_mark() const { |
| return _live_bytes_after_last_mark; |
| } |
| |
| void ShenandoahOldGeneration::set_live_bytes_after_last_mark(size_t bytes) { |
| if (bytes == 0) { |
| // Restart search for best old-gen size to the initial state |
| _live_bytes_after_last_mark = ShenandoahHeap::heap()->capacity() * INITIAL_LIVE_FRACTION / FRACTIONAL_DENOMINATOR; |
| _growth_before_compaction = INITIAL_GROWTH_BEFORE_COMPACTION; |
| } else { |
| _live_bytes_after_last_mark = bytes; |
| _growth_before_compaction /= 2; |
| if (_growth_before_compaction < _min_growth_before_compaction) { |
| _growth_before_compaction = _min_growth_before_compaction; |
| } |
| } |
| } |
| |
| void ShenandoahOldGeneration::handle_failed_transfer() { |
| _old_heuristics->trigger_cannot_expand(); |
| } |
| |
| size_t ShenandoahOldGeneration::usage_trigger_threshold() const { |
| size_t result = _live_bytes_after_last_mark + (_live_bytes_after_last_mark * _growth_before_compaction) / FRACTIONAL_DENOMINATOR; |
| return result; |
| } |
| |
| bool ShenandoahOldGeneration::contains(ShenandoahAffiliation affiliation) const { |
| return affiliation == OLD_GENERATION; |
| } |
| bool ShenandoahOldGeneration::contains(ShenandoahHeapRegion* region) const { |
| return region->is_old(); |
| } |
| |
| void ShenandoahOldGeneration::parallel_heap_region_iterate(ShenandoahHeapRegionClosure* cl) { |
| ShenandoahIncludeRegionClosure<OLD_GENERATION> old_regions_cl(cl); |
| ShenandoahHeap::heap()->parallel_heap_region_iterate(&old_regions_cl); |
| } |
| |
| void ShenandoahOldGeneration::heap_region_iterate(ShenandoahHeapRegionClosure* cl) { |
| ShenandoahIncludeRegionClosure<OLD_GENERATION> old_regions_cl(cl); |
| ShenandoahHeap::heap()->heap_region_iterate(&old_regions_cl); |
| } |
| |
| void ShenandoahOldGeneration::set_concurrent_mark_in_progress(bool in_progress) { |
| ShenandoahHeap::heap()->set_concurrent_old_mark_in_progress(in_progress); |
| } |
| |
| bool ShenandoahOldGeneration::is_concurrent_mark_in_progress() { |
| return ShenandoahHeap::heap()->is_concurrent_old_mark_in_progress(); |
| } |
| |
| void ShenandoahOldGeneration::cancel_marking() { |
| if (is_concurrent_mark_in_progress()) { |
| log_debug(gc)("Abandon SATB buffers"); |
| ShenandoahBarrierSet::satb_mark_queue_set().abandon_partial_marking(); |
| } |
| |
| ShenandoahGeneration::cancel_marking(); |
| } |
| |
| void ShenandoahOldGeneration::cancel_gc() { |
| shenandoah_assert_safepoint(); |
| if (is_idle()) { |
| #ifdef ASSERT |
| validate_waiting_for_bootstrap(); |
| #endif |
| } else { |
| log_info(gc)("Terminating old gc cycle."); |
| // Stop marking |
| cancel_marking(); |
| // Stop tracking old regions |
| abandon_collection_candidates(); |
| // Remove old generation access to young generation mark queues |
| ShenandoahHeap::heap()->young_generation()->set_old_gen_task_queues(nullptr); |
| // Transition to IDLE now. |
| transition_to(ShenandoahOldGeneration::WAITING_FOR_BOOTSTRAP); |
| } |
| } |
| |
| void ShenandoahOldGeneration::prepare_gc() { |
| // Now that we have made the old generation parsable, it is safe to reset the mark bitmap. |
| assert(state() != FILLING, "Cannot reset old without making it parsable"); |
| |
| ShenandoahGeneration::prepare_gc(); |
| } |
| |
| bool ShenandoahOldGeneration::entry_coalesce_and_fill() { |
| ShenandoahHeap* const heap = ShenandoahHeap::heap(); |
| |
| static const char* msg = "Coalescing and filling (Old)"; |
| ShenandoahConcurrentPhase gc_phase(msg, ShenandoahPhaseTimings::conc_coalesce_and_fill); |
| |
| TraceCollectorStats tcs(heap->monitoring_support()->concurrent_collection_counters()); |
| EventMark em("%s", msg); |
| ShenandoahWorkerScope scope(heap->workers(), |
| ShenandoahWorkerPolicy::calc_workers_for_conc_marking(), |
| msg); |
| |
| return coalesce_and_fill(); |
| } |
| |
| // Make the old generation regions parsable, so they can be safely |
| // scanned when looking for objects in memory indicated by dirty cards. |
| bool ShenandoahOldGeneration::coalesce_and_fill() { |
| transition_to(FILLING); |
| |
| // This code will see the same set of regions to fill on each resumption as it did |
| // on the initial run. That's okay because each region keeps track of its own coalesce |
| // and fill state. Regions that were filled on a prior attempt will not try to fill again. |
| uint coalesce_and_fill_regions_count = _old_heuristics->get_coalesce_and_fill_candidates(_coalesce_and_fill_region_array); |
| assert(coalesce_and_fill_regions_count <= ShenandoahHeap::heap()->num_regions(), "Sanity"); |
| if (coalesce_and_fill_regions_count == 0) { |
| // No regions need to be filled. |
| abandon_collection_candidates(); |
| return true; |
| } |
| |
| ShenandoahHeap* const heap = ShenandoahHeap::heap(); |
| WorkerThreads* workers = heap->workers(); |
| uint nworkers = workers->active_workers(); |
| ShenandoahConcurrentCoalesceAndFillTask task(nworkers, _coalesce_and_fill_region_array, coalesce_and_fill_regions_count); |
| |
| log_debug(gc)("Starting (or resuming) coalesce-and-fill of " UINT32_FORMAT " old heap regions", coalesce_and_fill_regions_count); |
| workers->run_task(&task); |
| if (task.is_completed()) { |
| // We no longer need to track regions that need to be coalesced and filled. |
| abandon_collection_candidates(); |
| return true; |
| } else { |
| // Coalesce-and-fill has been preempted. We'll finish that effort in the future. Do not invoke |
| // ShenandoahGeneration::prepare_gc() until coalesce-and-fill is done because it resets the mark bitmap |
| // and invokes set_mark_incomplete(). Coalesce-and-fill depends on the mark bitmap. |
| log_debug(gc)("Suspending coalesce-and-fill of old heap regions"); |
| return false; |
| } |
| } |
| |
| void ShenandoahOldGeneration::concurrent_transfer_pointers_from_satb() const { |
| const ShenandoahHeap* heap = ShenandoahHeap::heap(); |
| assert(heap->is_concurrent_old_mark_in_progress(), "Only necessary during old marking."); |
| log_debug(gc)("Transfer SATB buffers"); |
| |
| // Step 1. All threads need to 'complete' partially filled, thread local SATB buffers. This |
| // is accomplished in ShenandoahConcurrentGC::complete_abbreviated_cycle using a Handshake |
| // operation. |
| // Step 2. Use worker threads to transfer oops from old, active regions in the completed |
| // SATB buffers to old generation mark queues. |
| ShenandoahSATBMarkQueueSet& satb_queues = ShenandoahBarrierSet::satb_mark_queue_set(); |
| ShenandoahTransferOldSATBTask transfer_task(satb_queues, task_queues()); |
| heap->workers()->run_task(&transfer_task); |
| } |
| |
| void ShenandoahOldGeneration::transfer_pointers_from_satb() const { |
| const ShenandoahHeap* heap = ShenandoahHeap::heap(); |
| assert(heap->is_concurrent_old_mark_in_progress(), "Only necessary during old marking."); |
| log_debug(gc)("Transfer SATB buffers"); |
| ShenandoahPurgeSATBTask purge_satb_task(task_queues()); |
| heap->workers()->run_task(&purge_satb_task); |
| } |
| |
| bool ShenandoahOldGeneration::contains(oop obj) const { |
| return ShenandoahHeap::heap()->is_in_old(obj); |
| } |
| |
| void ShenandoahOldGeneration::prepare_regions_and_collection_set(bool concurrent) { |
| ShenandoahHeap* heap = ShenandoahHeap::heap(); |
| assert(!heap->is_full_gc_in_progress(), "Only for concurrent and degenerated GC"); |
| |
| { |
| ShenandoahGCPhase phase(concurrent ? |
| ShenandoahPhaseTimings::final_update_region_states : |
| ShenandoahPhaseTimings::degen_gc_final_update_region_states); |
| ShenandoahFinalMarkUpdateRegionStateClosure cl(complete_marking_context()); |
| |
| parallel_heap_region_iterate(&cl); |
| heap->assert_pinned_region_status(); |
| } |
| |
| { |
| // This doesn't actually choose a collection set, but prepares a list of |
| // regions as 'candidates' for inclusion in a mixed collection. |
| ShenandoahGCPhase phase(concurrent ? |
| ShenandoahPhaseTimings::choose_cset : |
| ShenandoahPhaseTimings::degen_gc_choose_cset); |
| ShenandoahHeapLocker locker(heap->lock()); |
| _old_heuristics->prepare_for_old_collections(); |
| } |
| |
| { |
| // Though we did not choose a collection set above, we still may have |
| // freed up immediate garbage regions so proceed with rebuilding the free set. |
| ShenandoahGCPhase phase(concurrent ? |
| ShenandoahPhaseTimings::final_rebuild_freeset : |
| ShenandoahPhaseTimings::degen_gc_final_rebuild_freeset); |
| ShenandoahHeapLocker locker(heap->lock()); |
| size_t cset_young_regions, cset_old_regions; |
| size_t first_old, last_old, num_old; |
| heap->free_set()->prepare_to_rebuild(cset_young_regions, cset_old_regions, first_old, last_old, num_old); |
| // This is just old-gen completion. No future budgeting required here. The only reason to rebuild the freeset here |
| // is in case there was any immediate old garbage identified. |
| heap->free_set()->finish_rebuild(cset_young_regions, cset_old_regions, num_old); |
| } |
| } |
| |
| const char* ShenandoahOldGeneration::state_name(State state) { |
| switch (state) { |
| case WAITING_FOR_BOOTSTRAP: return "Waiting for Bootstrap"; |
| case FILLING: return "Coalescing"; |
| case BOOTSTRAPPING: return "Bootstrapping"; |
| case MARKING: return "Marking"; |
| case EVACUATING: return "Evacuating"; |
| case EVACUATING_AFTER_GLOBAL: return "Evacuating (G)"; |
| default: |
| ShouldNotReachHere(); |
| return "Unknown"; |
| } |
| } |
| |
| void ShenandoahOldGeneration::transition_to(State new_state) { |
| if (_state != new_state) { |
| log_debug(gc, thread)("Old generation transition from %s to %s", state_name(_state), state_name(new_state)); |
| EventMark event("Old was %s, now is %s", state_name(_state), state_name(new_state)); |
| validate_transition(new_state); |
| _state = new_state; |
| } |
| } |
| |
| #ifdef ASSERT |
| // This diagram depicts the expected state transitions for marking the old generation |
| // and preparing for old collections. When a young generation cycle executes, the |
| // remembered set scan must visit objects in old regions. Visiting an object which |
| // has become dead on previous old cycles will result in crashes. To avoid visiting |
| // such objects, the remembered set scan will use the old generation mark bitmap when |
| // possible. It is _not_ possible to use the old generation bitmap when old marking |
| // is active (bitmap is not complete). For this reason, the old regions are made |
| // parsable _before_ the old generation bitmap is reset. The diagram does not depict |
| // cancellation of old collections by global or full collections. |
| // |
| // When a global collection supersedes an old collection, the global mark still |
| // "completes" the old mark bitmap. Subsequent remembered set scans may use the |
| // old generation mark bitmap, but any uncollected old regions must still be made parsable |
| // before the next old generation cycle begins. For this reason, a global collection may |
| // create mixed collection candidates and coalesce and fill candidates and will put |
| // the old generation in the respective states (EVACUATING or FILLING). After a Full GC, |
| // the mark bitmaps are all reset, all regions are parsable and the mark context will |
| // not be "complete". After a Full GC, remembered set scans will _not_ use the mark bitmap |
| // and we expect the old generation to be waiting for bootstrap. |
| // |
| // +-----------------+ |
| // +------------> | FILLING | <---+ |
| // | +--------> | | | |
| // | | +-----------------+ | |
| // | | | | |
| // | | | Filling Complete | <-> A global collection may |
| // | | v | move the old generation |
| // | | +-----------------+ | directly from waiting for |
| // +-- |-- |--------> | WAITING | | bootstrap to filling or |
| // | | | +---- | FOR BOOTSTRAP | ----+ evacuating. It may also |
| // | | | | +-----------------+ move from filling to waiting |
| // | | | | | for bootstrap. |
| // | | | | | Reset Bitmap |
| // | | | | v |
| // | | | | +-----------------+ +----------------------+ |
| // | | | | | BOOTSTRAP | <-> | YOUNG GC | |
| // | | | | | | | (RSet Parses Region) | |
| // | | | | +-----------------+ +----------------------+ |
| // | | | | | |
| // | | | | | Old Marking |
| // | | | | v |
| // | | | | +-----------------+ +----------------------+ |
| // | | | | | MARKING | <-> | YOUNG GC | |
| // | | +--------- | | | (RSet Parses Region) | |
| // | | | +-----------------+ +----------------------+ |
| // | | | | |
| // | | | | Has Evacuation Candidates |
| // | | | v |
| // | | | +-----------------+ +--------------------+ |
| // | | +---> | EVACUATING | <-> | YOUNG GC | |
| // | +------------- | | | (RSet Uses Bitmap) | |
| // | +-----------------+ +--------------------+ |
| // | | |
| // | | Global Cycle Coalesces and Fills Old Regions |
| // | v |
| // | +-----------------+ +--------------------+ |
| // +----------------- | EVACUATING | <-> | YOUNG GC | |
| // | AFTER GLOBAL | | (RSet Uses Bitmap) | |
| // +-----------------+ +--------------------+ |
| // |
| // |
| void ShenandoahOldGeneration::validate_transition(State new_state) { |
| ShenandoahGenerationalHeap* heap = ShenandoahGenerationalHeap::heap(); |
| switch (new_state) { |
| case FILLING: |
| assert(_state != BOOTSTRAPPING, "Cannot begin making old regions parsable after bootstrapping"); |
| assert(is_mark_complete(), "Cannot begin filling without first completing marking, state is '%s'", state_name(_state)); |
| assert(_old_heuristics->has_coalesce_and_fill_candidates(), "Cannot begin filling without something to fill."); |
| break; |
| case WAITING_FOR_BOOTSTRAP: |
| // GC cancellation can send us back here from any state. |
| validate_waiting_for_bootstrap(); |
| break; |
| case BOOTSTRAPPING: |
| assert(_state == WAITING_FOR_BOOTSTRAP, "Cannot reset bitmap without making old regions parsable, state is '%s'", state_name(_state)); |
| assert(_old_heuristics->unprocessed_old_collection_candidates() == 0, "Cannot bootstrap with mixed collection candidates"); |
| assert(!heap->is_prepare_for_old_mark_in_progress(), "Cannot still be making old regions parsable."); |
| break; |
| case MARKING: |
| assert(_state == BOOTSTRAPPING, "Must have finished bootstrapping before marking, state is '%s'", state_name(_state)); |
| assert(heap->young_generation()->old_gen_task_queues() != nullptr, "Young generation needs old mark queues."); |
| assert(heap->is_concurrent_old_mark_in_progress(), "Should be marking old now."); |
| break; |
| case EVACUATING_AFTER_GLOBAL: |
| assert(_state == EVACUATING, "Must have been evacuating, state is '%s'", state_name(_state)); |
| break; |
| case EVACUATING: |
| assert(_state == WAITING_FOR_BOOTSTRAP || _state == MARKING, "Cannot have old collection candidates without first marking, state is '%s'", state_name(_state)); |
| assert(_old_heuristics->unprocessed_old_collection_candidates() > 0, "Must have collection candidates here."); |
| break; |
| default: |
| fatal("Unknown new state"); |
| } |
| } |
| |
| bool ShenandoahOldGeneration::validate_waiting_for_bootstrap() { |
| ShenandoahHeap* heap = ShenandoahHeap::heap(); |
| assert(!heap->is_concurrent_old_mark_in_progress(), "Cannot become ready for bootstrap during old mark."); |
| assert(heap->young_generation()->old_gen_task_queues() == nullptr, "Cannot become ready for bootstrap when still setup for bootstrapping."); |
| assert(!is_concurrent_mark_in_progress(), "Cannot be marking in IDLE"); |
| assert(!heap->young_generation()->is_bootstrap_cycle(), "Cannot have old mark queues if IDLE"); |
| assert(!_old_heuristics->has_coalesce_and_fill_candidates(), "Cannot have coalesce and fill candidates in IDLE"); |
| assert(_old_heuristics->unprocessed_old_collection_candidates() == 0, "Cannot have mixed collection candidates in IDLE"); |
| return true; |
| } |
| #endif |
| |
| ShenandoahHeuristics* ShenandoahOldGeneration::initialize_heuristics(ShenandoahMode* gc_mode) { |
| _old_heuristics = new ShenandoahOldHeuristics(this, ShenandoahGenerationalHeap::heap()); |
| _old_heuristics->set_guaranteed_gc_interval(ShenandoahGuaranteedOldGCInterval); |
| _heuristics = _old_heuristics; |
| return _heuristics; |
| } |
| |
| void ShenandoahOldGeneration::record_success_concurrent(bool abbreviated) { |
| heuristics()->record_success_concurrent(); |
| ShenandoahHeap::heap()->shenandoah_policy()->record_success_old(); |
| } |
| |
| void ShenandoahOldGeneration::handle_failed_evacuation() { |
| if (_failed_evacuation.try_set()) { |
| log_debug(gc)("Old gen evac failure."); |
| } |
| } |
| |
| void ShenandoahOldGeneration::handle_failed_promotion(Thread* thread, size_t size) { |
| // We squelch excessive reports to reduce noise in logs. |
| const size_t MaxReportsPerEpoch = 4; |
| static size_t last_report_epoch = 0; |
| static size_t epoch_report_count = 0; |
| auto heap = ShenandoahGenerationalHeap::heap(); |
| |
| size_t promotion_reserve; |
| size_t promotion_expended; |
| |
| const size_t gc_id = heap->control_thread()->get_gc_id(); |
| |
| if ((gc_id != last_report_epoch) || (epoch_report_count++ < MaxReportsPerEpoch)) { |
| { |
| // Promotion failures should be very rare. Invest in providing useful diagnostic info. |
| ShenandoahHeapLocker locker(heap->lock()); |
| promotion_reserve = get_promoted_reserve(); |
| promotion_expended = get_promoted_expended(); |
| } |
| PLAB* const plab = ShenandoahThreadLocalData::plab(thread); |
| const size_t words_remaining = (plab == nullptr)? 0: plab->words_remaining(); |
| const char* promote_enabled = ShenandoahThreadLocalData::allow_plab_promotions(thread)? "enabled": "disabled"; |
| |
| log_info(gc, ergo)("Promotion failed, size %zu, has plab? %s, PLAB remaining: %zu" |
| ", plab promotions %s, promotion reserve: %zu, promotion expended: %zu" |
| ", old capacity: %zu, old_used: %zu, old unaffiliated regions: %zu", |
| size * HeapWordSize, plab == nullptr? "no": "yes", |
| words_remaining * HeapWordSize, promote_enabled, promotion_reserve, promotion_expended, |
| max_capacity(), used(), free_unaffiliated_regions()); |
| |
| if ((gc_id == last_report_epoch) && (epoch_report_count >= MaxReportsPerEpoch)) { |
| log_debug(gc, ergo)("Squelching additional promotion failure reports for current epoch"); |
| } else if (gc_id != last_report_epoch) { |
| last_report_epoch = gc_id; |
| epoch_report_count = 1; |
| } |
| } |
| } |
| |
| void ShenandoahOldGeneration::handle_evacuation(HeapWord* obj, size_t words, bool promotion) { |
| // Only register the copy of the object that won the evacuation race. |
| _card_scan->register_object_without_lock(obj); |
| |
| // Mark the entire range of the evacuated object as dirty. At next remembered set scan, |
| // we will clear dirty bits that do not hold interesting pointers. It's more efficient to |
| // do this in batch, in a background GC thread than to try to carefully dirty only cards |
| // that hold interesting pointers right now. |
| _card_scan->mark_range_as_dirty(obj, words); |
| |
| if (promotion) { |
| // This evacuation was a promotion, track this as allocation against old gen |
| increase_allocated(words * HeapWordSize); |
| } |
| } |
| |
| bool ShenandoahOldGeneration::has_unprocessed_collection_candidates() { |
| return _old_heuristics->unprocessed_old_collection_candidates() > 0; |
| } |
| |
| size_t ShenandoahOldGeneration::unprocessed_collection_candidates_live_memory() { |
| return _old_heuristics->unprocessed_old_collection_candidates_live_memory(); |
| } |
| |
| void ShenandoahOldGeneration::abandon_collection_candidates() { |
| _old_heuristics->abandon_collection_candidates(); |
| } |
| |
| void ShenandoahOldGeneration::prepare_for_mixed_collections_after_global_gc() { |
| assert(is_mark_complete(), "Expected old generation mark to be complete after global cycle."); |
| _old_heuristics->prepare_for_old_collections(); |
| log_info(gc, ergo)("After choosing global collection set, mixed candidates: " UINT32_FORMAT ", coalescing candidates: %zu", |
| _old_heuristics->unprocessed_old_collection_candidates(), |
| _old_heuristics->coalesce_and_fill_candidates_count()); |
| } |
| |
| void ShenandoahOldGeneration::parallel_heap_region_iterate_free(ShenandoahHeapRegionClosure* cl) { |
| // Iterate over old and free regions (exclude young). |
| ShenandoahExcludeRegionClosure<YOUNG_GENERATION> exclude_cl(cl); |
| ShenandoahGeneration::parallel_heap_region_iterate_free(&exclude_cl); |
| } |
| |
| void ShenandoahOldGeneration::set_parsable(bool parsable) { |
| _is_parsable = parsable; |
| if (_is_parsable) { |
| // The current state would have been chosen during final mark of the global |
| // collection, _before_ any decisions about class unloading have been made. |
| // |
| // After unloading classes, we have made the old generation regions parsable. |
| // We can skip filling or transition to a state that knows everything has |
| // already been filled. |
| switch (state()) { |
| case ShenandoahOldGeneration::EVACUATING: |
| transition_to(ShenandoahOldGeneration::EVACUATING_AFTER_GLOBAL); |
| break; |
| case ShenandoahOldGeneration::FILLING: |
| assert(_old_heuristics->unprocessed_old_collection_candidates() == 0, "Expected no mixed collection candidates"); |
| assert(_old_heuristics->coalesce_and_fill_candidates_count() > 0, "Expected coalesce and fill candidates"); |
| // When the heuristic put the old generation in this state, it didn't know |
| // that we would unload classes and make everything parsable. But, we know |
| // that now so we can override this state. |
| abandon_collection_candidates(); |
| transition_to(ShenandoahOldGeneration::WAITING_FOR_BOOTSTRAP); |
| break; |
| default: |
| // We can get here during a full GC. The full GC will cancel anything |
| // happening in the old generation and return it to the waiting for bootstrap |
| // state. The full GC will then record that the old regions are parsable |
| // after rebuilding the remembered set. |
| assert(is_idle(), "Unexpected state %s at end of global GC", state_name()); |
| break; |
| } |
| } |
| } |
| |
| void ShenandoahOldGeneration::complete_mixed_evacuations() { |
| assert(is_doing_mixed_evacuations(), "Mixed evacuations should be in progress"); |
| if (!_old_heuristics->has_coalesce_and_fill_candidates()) { |
| // No candidate regions to coalesce and fill |
| transition_to(ShenandoahOldGeneration::WAITING_FOR_BOOTSTRAP); |
| return; |
| } |
| |
| if (state() == ShenandoahOldGeneration::EVACUATING) { |
| transition_to(ShenandoahOldGeneration::FILLING); |
| return; |
| } |
| |
| // Here, we have no more candidates for mixed collections. The candidates for coalescing |
| // and filling have already been processed during the global cycle, so there is nothing |
| // more to do. |
| assert(state() == ShenandoahOldGeneration::EVACUATING_AFTER_GLOBAL, "Should be evacuating after a global cycle"); |
| abandon_collection_candidates(); |
| transition_to(ShenandoahOldGeneration::WAITING_FOR_BOOTSTRAP); |
| } |
| |
| void ShenandoahOldGeneration::abandon_mixed_evacuations() { |
| switch(state()) { |
| case ShenandoahOldGeneration::EVACUATING: |
| transition_to(ShenandoahOldGeneration::FILLING); |
| break; |
| case ShenandoahOldGeneration::EVACUATING_AFTER_GLOBAL: |
| abandon_collection_candidates(); |
| transition_to(ShenandoahOldGeneration::WAITING_FOR_BOOTSTRAP); |
| break; |
| default: |
| log_warning(gc)("Abandon mixed evacuations in unexpected state: %s", state_name(state())); |
| ShouldNotReachHere(); |
| break; |
| } |
| } |
| |
| void ShenandoahOldGeneration::clear_cards_for(ShenandoahHeapRegion* region) { |
| _card_scan->mark_range_as_empty(region->bottom(), pointer_delta(region->end(), region->bottom())); |
| } |
| |
| void ShenandoahOldGeneration::mark_card_as_dirty(void* location) { |
| _card_scan->mark_card_as_dirty((HeapWord*)location); |
| } |