| /* |
| * Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| * |
| */ |
| |
| #include "precompiled.hpp" |
| |
| #include "compiler/oopMap.hpp" |
| #include "gc/g1/g1CardSetMemory.hpp" |
| #include "gc/g1/g1CardTableEntryClosure.hpp" |
| #include "gc/g1/g1CollectedHeap.inline.hpp" |
| #include "gc/g1/g1CollectionSetCandidates.inline.hpp" |
| #include "gc/g1/g1ConcurrentMark.inline.hpp" |
| #include "gc/g1/g1EvacStats.inline.hpp" |
| #include "gc/g1/g1EvacInfo.hpp" |
| #include "gc/g1/g1ParScanThreadState.hpp" |
| #include "gc/g1/g1RemSet.hpp" |
| #include "gc/g1/g1YoungGCPostEvacuateTasks.hpp" |
| #include "gc/shared/preservedMarks.inline.hpp" |
| #include "jfr/jfrEvents.hpp" |
| #include "runtime/threads.hpp" |
| #include "runtime/threadSMR.hpp" |
| #include "utilities/ticks.hpp" |
| |
| class G1PostEvacuateCollectionSetCleanupTask1::MergePssTask : public G1AbstractSubTask { |
| G1ParScanThreadStateSet* _per_thread_states; |
| |
| public: |
| MergePssTask(G1ParScanThreadStateSet* per_thread_states) : |
| G1AbstractSubTask(G1GCPhaseTimes::MergePSS), |
| _per_thread_states(per_thread_states) { } |
| |
| double worker_cost() const override { return 1.0; } |
| |
| void do_work(uint worker_id) override { _per_thread_states->flush_stats(); } |
| }; |
| |
| class G1PostEvacuateCollectionSetCleanupTask1::RecalculateUsedTask : public G1AbstractSubTask { |
| bool _evacuation_failed; |
| |
| public: |
| RecalculateUsedTask(bool evacuation_failed) : G1AbstractSubTask(G1GCPhaseTimes::RecalculateUsed), _evacuation_failed(evacuation_failed) { } |
| |
| double worker_cost() const override { |
| // If there is no evacuation failure, the work to perform is minimal. |
| return _evacuation_failed ? 1.0 : AlmostNoWork; |
| } |
| |
| void do_work(uint worker_id) override { G1CollectedHeap::heap()->update_used_after_gc(_evacuation_failed); } |
| }; |
| |
| class G1PostEvacuateCollectionSetCleanupTask1::SampleCollectionSetCandidatesTask : public G1AbstractSubTask { |
| public: |
| SampleCollectionSetCandidatesTask() : G1AbstractSubTask(G1GCPhaseTimes::SampleCollectionSetCandidates) { } |
| |
| static bool should_execute() { |
| return G1CollectedHeap::heap()->should_sample_collection_set_candidates(); |
| } |
| |
| double worker_cost() const override { |
| return should_execute() ? 1.0 : AlmostNoWork; |
| } |
| |
| void do_work(uint worker_id) override { |
| G1CollectedHeap* g1h = G1CollectedHeap::heap(); |
| |
| G1MonotonicArenaMemoryStats _total; |
| G1CollectionSetCandidates* candidates = g1h->collection_set()->candidates(); |
| for (HeapRegion* r : *candidates) { |
| _total.add(r->rem_set()->card_set_memory_stats()); |
| } |
| g1h->set_collection_set_candidates_stats(_total); |
| } |
| }; |
| |
| class G1PostEvacuateCollectionSetCleanupTask1::RestoreRetainedRegionsTask : public G1AbstractSubTask { |
| G1RemoveSelfForwardsTask _task; |
| G1EvacFailureRegions* _evac_failure_regions; |
| |
| public: |
| RestoreRetainedRegionsTask(G1EvacFailureRegions* evac_failure_regions) : |
| G1AbstractSubTask(G1GCPhaseTimes::RestoreRetainedRegions), |
| _task(evac_failure_regions), |
| _evac_failure_regions(evac_failure_regions) { |
| } |
| |
| double worker_cost() const override { |
| assert(_evac_failure_regions->evacuation_failed(), "Should not call this if not executed"); |
| |
| double workers_per_region = (double)G1CollectedHeap::get_chunks_per_region() / G1RestoreRetainedRegionChunksPerWorker; |
| return workers_per_region * _evac_failure_regions->num_regions_failed_evacuation(); |
| } |
| |
| void do_work(uint worker_id) override { |
| _task.work(worker_id); |
| } |
| }; |
| |
| G1PostEvacuateCollectionSetCleanupTask1::G1PostEvacuateCollectionSetCleanupTask1(G1ParScanThreadStateSet* per_thread_states, |
| G1EvacFailureRegions* evac_failure_regions) : |
| G1BatchedTask("Post Evacuate Cleanup 1", G1CollectedHeap::heap()->phase_times()) |
| { |
| bool evacuation_failed = evac_failure_regions->evacuation_failed(); |
| |
| add_serial_task(new MergePssTask(per_thread_states)); |
| add_serial_task(new RecalculateUsedTask(evacuation_failed)); |
| if (SampleCollectionSetCandidatesTask::should_execute()) { |
| add_serial_task(new SampleCollectionSetCandidatesTask()); |
| } |
| add_parallel_task(G1CollectedHeap::heap()->rem_set()->create_cleanup_after_scan_heap_roots_task()); |
| if (evacuation_failed) { |
| add_parallel_task(new RestoreRetainedRegionsTask(evac_failure_regions)); |
| } |
| } |
| |
| class G1FreeHumongousRegionClosure : public HeapRegionIndexClosure { |
| uint _humongous_objects_reclaimed; |
| uint _humongous_regions_reclaimed; |
| size_t _freed_bytes; |
| G1CollectedHeap* _g1h; |
| |
| // Returns whether the given humongous object defined by the start region index |
| // is reclaimable. |
| // |
| // At this point in the garbage collection, checking whether the humongous object |
| // is still a candidate is sufficient because: |
| // |
| // - if it has not been a candidate at the start of collection, it will never |
| // changed to be a candidate during the gc (and live). |
| // - any found outstanding (i.e. in the DCQ, or in its remembered set) |
| // references will set the candidate state to false. |
| // - there can be no references from within humongous starts regions referencing |
| // the object because we never allocate other objects into them. |
| // (I.e. there can be no intra-region references) |
| // |
| // It is not required to check whether the object has been found dead by marking |
| // or not, in fact it would prevent reclamation within a concurrent cycle, as |
| // all objects allocated during that time are considered live. |
| // SATB marking is even more conservative than the remembered set. |
| // So if at this point in the collection we did not find a reference during gc |
| // (or it had enough references to not be a candidate, having many remembered |
| // set entries), nobody has a reference to it. |
| // At the start of collection we flush all refinement logs, and remembered sets |
| // are completely up-to-date wrt to references to the humongous object. |
| // |
| // So there is no need to re-check remembered set size of the humongous region. |
| // |
| // Other implementation considerations: |
| // - never consider object arrays at this time because they would pose |
| // considerable effort for cleaning up the remembered sets. This is |
| // required because stale remembered sets might reference locations that |
| // are currently allocated into. |
| bool is_reclaimable(uint region_idx) const { |
| return G1CollectedHeap::heap()->is_humongous_reclaim_candidate(region_idx); |
| } |
| |
| public: |
| G1FreeHumongousRegionClosure() : |
| _humongous_objects_reclaimed(0), |
| _humongous_regions_reclaimed(0), |
| _freed_bytes(0), |
| _g1h(G1CollectedHeap::heap()) |
| {} |
| |
| bool do_heap_region_index(uint region_index) override { |
| if (!is_reclaimable(region_index)) { |
| return false; |
| } |
| |
| HeapRegion* r = _g1h->region_at(region_index); |
| |
| oop obj = cast_to_oop(r->bottom()); |
| guarantee(obj->is_typeArray(), |
| "Only eagerly reclaiming type arrays is supported, but the object " |
| PTR_FORMAT " is not.", p2i(r->bottom())); |
| |
| log_debug(gc, humongous)("Reclaimed humongous region %u (object size " SIZE_FORMAT " @ " PTR_FORMAT ")", |
| region_index, |
| obj->size() * HeapWordSize, |
| p2i(r->bottom()) |
| ); |
| |
| G1ConcurrentMark* const cm = _g1h->concurrent_mark(); |
| cm->humongous_object_eagerly_reclaimed(r); |
| assert(!cm->is_marked_in_bitmap(obj), |
| "Eagerly reclaimed humongous region %u should not be marked at all but is in bitmap %s", |
| region_index, |
| BOOL_TO_STR(cm->is_marked_in_bitmap(obj))); |
| _humongous_objects_reclaimed++; |
| |
| auto free_humongous_region = [&] (HeapRegion* r) { |
| _freed_bytes += r->used(); |
| r->set_containing_set(nullptr); |
| _humongous_regions_reclaimed++; |
| _g1h->free_humongous_region(r, nullptr); |
| _g1h->hr_printer()->cleanup(r); |
| }; |
| |
| _g1h->humongous_obj_regions_iterate(r, free_humongous_region); |
| |
| return false; |
| } |
| |
| uint humongous_objects_reclaimed() { |
| return _humongous_objects_reclaimed; |
| } |
| |
| uint humongous_regions_reclaimed() { |
| return _humongous_regions_reclaimed; |
| } |
| |
| size_t bytes_freed() const { |
| return _freed_bytes; |
| } |
| }; |
| |
| #if COMPILER2_OR_JVMCI |
| class G1PostEvacuateCollectionSetCleanupTask2::UpdateDerivedPointersTask : public G1AbstractSubTask { |
| public: |
| UpdateDerivedPointersTask() : G1AbstractSubTask(G1GCPhaseTimes::UpdateDerivedPointers) { } |
| |
| double worker_cost() const override { return 1.0; } |
| void do_work(uint worker_id) override { DerivedPointerTable::update_pointers(); } |
| }; |
| #endif |
| |
| class G1PostEvacuateCollectionSetCleanupTask2::EagerlyReclaimHumongousObjectsTask : public G1AbstractSubTask { |
| uint _humongous_regions_reclaimed; |
| size_t _bytes_freed; |
| |
| public: |
| EagerlyReclaimHumongousObjectsTask() : |
| G1AbstractSubTask(G1GCPhaseTimes::EagerlyReclaimHumongousObjects), |
| _humongous_regions_reclaimed(0), |
| _bytes_freed(0) { } |
| |
| virtual ~EagerlyReclaimHumongousObjectsTask() { |
| G1CollectedHeap* g1h = G1CollectedHeap::heap(); |
| |
| g1h->remove_from_old_gen_sets(0, _humongous_regions_reclaimed); |
| g1h->decrement_summary_bytes(_bytes_freed); |
| } |
| |
| double worker_cost() const override { return 1.0; } |
| void do_work(uint worker_id) override { |
| G1CollectedHeap* g1h = G1CollectedHeap::heap(); |
| |
| G1FreeHumongousRegionClosure cl; |
| g1h->heap_region_iterate(&cl); |
| |
| record_work_item(worker_id, G1GCPhaseTimes::EagerlyReclaimNumTotal, g1h->num_humongous_objects()); |
| record_work_item(worker_id, G1GCPhaseTimes::EagerlyReclaimNumCandidates, g1h->num_humongous_reclaim_candidates()); |
| record_work_item(worker_id, G1GCPhaseTimes::EagerlyReclaimNumReclaimed, cl.humongous_objects_reclaimed()); |
| |
| _humongous_regions_reclaimed = cl.humongous_regions_reclaimed(); |
| _bytes_freed = cl.bytes_freed(); |
| } |
| }; |
| |
| class G1PostEvacuateCollectionSetCleanupTask2::RestorePreservedMarksTask : public G1AbstractSubTask { |
| PreservedMarksSet* _preserved_marks; |
| WorkerTask* _task; |
| |
| public: |
| RestorePreservedMarksTask(PreservedMarksSet* preserved_marks) : |
| G1AbstractSubTask(G1GCPhaseTimes::RestorePreservedMarks), |
| _preserved_marks(preserved_marks), |
| _task(preserved_marks->create_task()) { } |
| |
| virtual ~RestorePreservedMarksTask() { |
| delete _task; |
| } |
| |
| double worker_cost() const override { |
| return _preserved_marks->num(); |
| } |
| |
| void do_work(uint worker_id) override { _task->work(worker_id); } |
| }; |
| |
| class RedirtyLoggedCardTableEntryClosure : public G1CardTableEntryClosure { |
| size_t _num_dirtied; |
| G1CollectedHeap* _g1h; |
| G1CardTable* _g1_ct; |
| G1EvacFailureRegions* _evac_failure_regions; |
| |
| HeapRegion* region_for_card(CardValue* card_ptr) const { |
| return _g1h->heap_region_containing(_g1_ct->addr_for(card_ptr)); |
| } |
| |
| bool will_become_free(HeapRegion* hr) const { |
| // A region will be freed by during the FreeCollectionSet phase if the region is in the |
| // collection set and has not had an evacuation failure. |
| return _g1h->is_in_cset(hr) && !_evac_failure_regions->contains(hr->hrm_index()); |
| } |
| |
| public: |
| RedirtyLoggedCardTableEntryClosure(G1CollectedHeap* g1h, G1EvacFailureRegions* evac_failure_regions) : |
| G1CardTableEntryClosure(), |
| _num_dirtied(0), |
| _g1h(g1h), |
| _g1_ct(g1h->card_table()), |
| _evac_failure_regions(evac_failure_regions) { } |
| |
| void do_card_ptr(CardValue* card_ptr, uint worker_id) { |
| HeapRegion* hr = region_for_card(card_ptr); |
| |
| // Should only dirty cards in regions that won't be freed. |
| if (!will_become_free(hr)) { |
| *card_ptr = G1CardTable::dirty_card_val(); |
| _num_dirtied++; |
| } |
| } |
| |
| size_t num_dirtied() const { return _num_dirtied; } |
| }; |
| |
| class G1PostEvacuateCollectionSetCleanupTask2::ClearRetainedRegionBitmaps : public G1AbstractSubTask { |
| G1EvacFailureRegions* _evac_failure_regions; |
| HeapRegionClaimer _claimer; |
| |
| class ClearRetainedRegionBitmapsClosure : public HeapRegionClosure { |
| public: |
| |
| bool do_heap_region(HeapRegion* r) override { |
| assert(r->bottom() == r->top_at_mark_start(), |
| "TAMS should have been reset for region %u", r->hrm_index()); |
| G1CollectedHeap::heap()->clear_bitmap_for_region(r); |
| return false; |
| } |
| }; |
| |
| public: |
| ClearRetainedRegionBitmaps(G1EvacFailureRegions* evac_failure_regions) : |
| G1AbstractSubTask(G1GCPhaseTimes::ClearRetainedRegionBitmaps), |
| _evac_failure_regions(evac_failure_regions), |
| _claimer(0) { |
| assert(!G1CollectedHeap::heap()->collector_state()->in_concurrent_start_gc(), |
| "Should not clear bitmaps of retained regions during concurrent start"); |
| } |
| |
| void set_max_workers(uint max_workers) override { |
| _claimer.set_n_workers(max_workers); |
| } |
| |
| double worker_cost() const override { |
| return _evac_failure_regions->num_regions_failed_evacuation(); |
| } |
| |
| void do_work(uint worker_id) override { |
| ClearRetainedRegionBitmapsClosure cl; |
| _evac_failure_regions->par_iterate(&cl, &_claimer, worker_id); |
| } |
| }; |
| |
| class G1PostEvacuateCollectionSetCleanupTask2::RedirtyLoggedCardsTask : public G1AbstractSubTask { |
| G1RedirtyCardsQueueSet* _rdcqs; |
| BufferNode* volatile _nodes; |
| G1EvacFailureRegions* _evac_failure_regions; |
| |
| public: |
| RedirtyLoggedCardsTask(G1RedirtyCardsQueueSet* rdcqs, G1EvacFailureRegions* evac_failure_regions) : |
| G1AbstractSubTask(G1GCPhaseTimes::RedirtyCards), |
| _rdcqs(rdcqs), |
| _nodes(rdcqs->all_completed_buffers()), |
| _evac_failure_regions(evac_failure_regions) { } |
| |
| virtual ~RedirtyLoggedCardsTask() { |
| G1DirtyCardQueueSet& dcq = G1BarrierSet::dirty_card_queue_set(); |
| dcq.merge_bufferlists(_rdcqs); |
| _rdcqs->verify_empty(); |
| } |
| |
| double worker_cost() const override { |
| // Needs more investigation. |
| return G1CollectedHeap::heap()->workers()->active_workers(); |
| } |
| |
| void do_work(uint worker_id) override { |
| RedirtyLoggedCardTableEntryClosure cl(G1CollectedHeap::heap(), _evac_failure_regions); |
| const size_t buffer_size = _rdcqs->buffer_size(); |
| BufferNode* next = Atomic::load(&_nodes); |
| while (next != nullptr) { |
| BufferNode* node = next; |
| next = Atomic::cmpxchg(&_nodes, node, node->next()); |
| if (next == node) { |
| cl.apply_to_buffer(node, buffer_size, worker_id); |
| next = node->next(); |
| } |
| } |
| record_work_item(worker_id, 0, cl.num_dirtied()); |
| } |
| }; |
| |
| // Helper class to keep statistics for the collection set freeing |
| class FreeCSetStats { |
| size_t _before_used_bytes; // Usage in regions successfully evacuate |
| size_t _after_used_bytes; // Usage in regions failing evacuation |
| size_t _bytes_allocated_in_old_since_last_gc; // Size of young regions turned into old |
| size_t _failure_used_words; // Live size in failed regions |
| size_t _failure_waste_words; // Wasted size in failed regions |
| size_t _rs_length; // Remembered set size |
| uint _regions_freed; // Number of regions freed |
| |
| public: |
| FreeCSetStats() : |
| _before_used_bytes(0), |
| _after_used_bytes(0), |
| _bytes_allocated_in_old_since_last_gc(0), |
| _failure_used_words(0), |
| _failure_waste_words(0), |
| _rs_length(0), |
| _regions_freed(0) { } |
| |
| void merge_stats(FreeCSetStats* other) { |
| assert(other != nullptr, "invariant"); |
| _before_used_bytes += other->_before_used_bytes; |
| _after_used_bytes += other->_after_used_bytes; |
| _bytes_allocated_in_old_since_last_gc += other->_bytes_allocated_in_old_since_last_gc; |
| _failure_used_words += other->_failure_used_words; |
| _failure_waste_words += other->_failure_waste_words; |
| _rs_length += other->_rs_length; |
| _regions_freed += other->_regions_freed; |
| } |
| |
| void report(G1CollectedHeap* g1h, G1EvacInfo* evacuation_info) { |
| evacuation_info->set_regions_freed(_regions_freed); |
| evacuation_info->set_collection_set_used_before(_before_used_bytes + _after_used_bytes); |
| evacuation_info->increment_collection_set_used_after(_after_used_bytes); |
| |
| g1h->decrement_summary_bytes(_before_used_bytes); |
| g1h->alloc_buffer_stats(G1HeapRegionAttr::Old)->add_failure_used_and_waste(_failure_used_words, _failure_waste_words); |
| |
| G1Policy *policy = g1h->policy(); |
| policy->old_gen_alloc_tracker()->add_allocated_bytes_since_last_gc(_bytes_allocated_in_old_since_last_gc); |
| policy->record_rs_length(_rs_length); |
| policy->cset_regions_freed(); |
| } |
| |
| void account_failed_region(HeapRegion* r) { |
| size_t used_words = r->live_bytes() / HeapWordSize; |
| _failure_used_words += used_words; |
| _failure_waste_words += HeapRegion::GrainWords - used_words; |
| _after_used_bytes += r->used(); |
| |
| // When moving a young gen region to old gen, we "allocate" that whole |
| // region there. This is in addition to any already evacuated objects. |
| // Notify the policy about that. Old gen regions do not cause an |
| // additional allocation: both the objects still in the region and the |
| // ones already moved are accounted for elsewhere. |
| if (r->is_young()) { |
| _bytes_allocated_in_old_since_last_gc += HeapRegion::GrainBytes; |
| } |
| } |
| |
| void account_evacuated_region(HeapRegion* r) { |
| size_t used = r->used(); |
| assert(used > 0, "region %u %s zero used", r->hrm_index(), r->get_short_type_str()); |
| _before_used_bytes += used; |
| _regions_freed += 1; |
| } |
| |
| void account_rs_length(HeapRegion* r) { |
| _rs_length += r->rem_set()->occupied(); |
| } |
| }; |
| |
| // Closure applied to all regions in the collection set. |
| class FreeCSetClosure : public HeapRegionClosure { |
| // Helper to send JFR events for regions. |
| class JFREventForRegion { |
| EventGCPhaseParallel _event; |
| |
| public: |
| JFREventForRegion(HeapRegion* region, uint worker_id) : _event() { |
| _event.set_gcId(GCId::current()); |
| _event.set_gcWorkerId(worker_id); |
| if (region->is_young()) { |
| _event.set_name(G1GCPhaseTimes::phase_name(G1GCPhaseTimes::YoungFreeCSet)); |
| } else { |
| _event.set_name(G1GCPhaseTimes::phase_name(G1GCPhaseTimes::NonYoungFreeCSet)); |
| } |
| } |
| |
| ~JFREventForRegion() { |
| _event.commit(); |
| } |
| }; |
| |
| // Helper to do timing for region work. |
| class TimerForRegion { |
| Tickspan& _time; |
| Ticks _start_time; |
| public: |
| TimerForRegion(Tickspan& time) : _time(time), _start_time(Ticks::now()) { } |
| ~TimerForRegion() { |
| _time += Ticks::now() - _start_time; |
| } |
| }; |
| |
| // FreeCSetClosure members |
| G1CollectedHeap* _g1h; |
| const size_t* _surviving_young_words; |
| uint _worker_id; |
| Tickspan _young_time; |
| Tickspan _non_young_time; |
| FreeCSetStats* _stats; |
| G1EvacFailureRegions* _evac_failure_regions; |
| |
| void assert_tracks_surviving_words(HeapRegion* r) { |
| assert(r->young_index_in_cset() != 0 && |
| (uint)r->young_index_in_cset() <= _g1h->collection_set()->young_region_length(), |
| "Young index %u is wrong for region %u of type %s with %u young regions", |
| r->young_index_in_cset(), r->hrm_index(), r->get_type_str(), _g1h->collection_set()->young_region_length()); |
| } |
| |
| void handle_evacuated_region(HeapRegion* r) { |
| assert(!r->is_empty(), "Region %u is an empty region in the collection set.", r->hrm_index()); |
| stats()->account_evacuated_region(r); |
| |
| // Free the region and its remembered set. |
| _g1h->free_region(r, nullptr); |
| _g1h->hr_printer()->cleanup(r); |
| } |
| |
| void handle_failed_region(HeapRegion* r) { |
| // Do some allocation statistics accounting. Regions that failed evacuation |
| // are always made old, so there is no need to update anything in the young |
| // gen statistics, but we need to update old gen statistics. |
| stats()->account_failed_region(r); |
| |
| G1GCPhaseTimes* p = _g1h->phase_times(); |
| assert(r->in_collection_set(), "Failed evacuation of region %u not in collection set", r->hrm_index()); |
| |
| p->record_or_add_thread_work_item(G1GCPhaseTimes::RestoreRetainedRegions, |
| _worker_id, |
| 1, |
| G1GCPhaseTimes::RestoreRetainedRegionsNum); |
| |
| // Update the region state due to the failed evacuation. |
| r->handle_evacuation_failure(); |
| |
| // Add region to old set, need to hold lock. |
| MutexLocker x(OldSets_lock, Mutex::_no_safepoint_check_flag); |
| _g1h->old_set_add(r); |
| } |
| |
| Tickspan& timer_for_region(HeapRegion* r) { |
| return r->is_young() ? _young_time : _non_young_time; |
| } |
| |
| FreeCSetStats* stats() { |
| return _stats; |
| } |
| |
| public: |
| FreeCSetClosure(const size_t* surviving_young_words, |
| uint worker_id, |
| FreeCSetStats* stats, |
| G1EvacFailureRegions* evac_failure_regions) : |
| HeapRegionClosure(), |
| _g1h(G1CollectedHeap::heap()), |
| _surviving_young_words(surviving_young_words), |
| _worker_id(worker_id), |
| _young_time(), |
| _non_young_time(), |
| _stats(stats), |
| _evac_failure_regions(evac_failure_regions) { } |
| |
| virtual bool do_heap_region(HeapRegion* r) { |
| assert(r->in_collection_set(), "Invariant: %u missing from CSet", r->hrm_index()); |
| JFREventForRegion event(r, _worker_id); |
| TimerForRegion timer(timer_for_region(r)); |
| |
| stats()->account_rs_length(r); |
| |
| if (r->is_young()) { |
| assert_tracks_surviving_words(r); |
| r->record_surv_words_in_group(_surviving_young_words[r->young_index_in_cset()]); |
| } |
| |
| if (_evac_failure_regions->contains(r->hrm_index())) { |
| handle_failed_region(r); |
| } else { |
| handle_evacuated_region(r); |
| } |
| assert(!_g1h->is_on_master_free_list(r), "sanity"); |
| |
| return false; |
| } |
| |
| void report_timing() { |
| G1GCPhaseTimes* pt = _g1h->phase_times(); |
| if (_young_time.value() > 0) { |
| pt->record_time_secs(G1GCPhaseTimes::YoungFreeCSet, _worker_id, _young_time.seconds()); |
| } |
| if (_non_young_time.value() > 0) { |
| pt->record_time_secs(G1GCPhaseTimes::NonYoungFreeCSet, _worker_id, _non_young_time.seconds()); |
| } |
| } |
| }; |
| |
| class G1PostEvacuateCollectionSetCleanupTask2::FreeCollectionSetTask : public G1AbstractSubTask { |
| G1CollectedHeap* _g1h; |
| G1EvacInfo* _evacuation_info; |
| FreeCSetStats* _worker_stats; |
| HeapRegionClaimer _claimer; |
| const size_t* _surviving_young_words; |
| uint _active_workers; |
| G1EvacFailureRegions* _evac_failure_regions; |
| |
| FreeCSetStats* worker_stats(uint worker) { |
| return &_worker_stats[worker]; |
| } |
| |
| void report_statistics() { |
| // Merge the accounting |
| FreeCSetStats total_stats; |
| for (uint worker = 0; worker < _active_workers; worker++) { |
| total_stats.merge_stats(worker_stats(worker)); |
| } |
| total_stats.report(_g1h, _evacuation_info); |
| } |
| |
| public: |
| FreeCollectionSetTask(G1EvacInfo* evacuation_info, |
| const size_t* surviving_young_words, |
| G1EvacFailureRegions* evac_failure_regions) : |
| G1AbstractSubTask(G1GCPhaseTimes::FreeCollectionSet), |
| _g1h(G1CollectedHeap::heap()), |
| _evacuation_info(evacuation_info), |
| _worker_stats(nullptr), |
| _claimer(0), |
| _surviving_young_words(surviving_young_words), |
| _active_workers(0), |
| _evac_failure_regions(evac_failure_regions) { |
| |
| _g1h->clear_eden(); |
| } |
| |
| virtual ~FreeCollectionSetTask() { |
| Ticks serial_time = Ticks::now(); |
| report_statistics(); |
| for (uint worker = 0; worker < _active_workers; worker++) { |
| _worker_stats[worker].~FreeCSetStats(); |
| } |
| FREE_C_HEAP_ARRAY(FreeCSetStats, _worker_stats); |
| _g1h->phase_times()->record_serial_free_cset_time_ms((Ticks::now() - serial_time).seconds() * 1000.0); |
| _g1h->clear_collection_set(); |
| } |
| |
| double worker_cost() const override { return G1CollectedHeap::heap()->collection_set()->region_length(); } |
| |
| void set_max_workers(uint max_workers) override { |
| _active_workers = max_workers; |
| _worker_stats = NEW_C_HEAP_ARRAY(FreeCSetStats, max_workers, mtGC); |
| for (uint worker = 0; worker < _active_workers; worker++) { |
| ::new (&_worker_stats[worker]) FreeCSetStats(); |
| } |
| _claimer.set_n_workers(_active_workers); |
| } |
| |
| void do_work(uint worker_id) override { |
| FreeCSetClosure cl(_surviving_young_words, worker_id, worker_stats(worker_id), _evac_failure_regions); |
| _g1h->collection_set_par_iterate_all(&cl, &_claimer, worker_id); |
| // Report per-region type timings. |
| cl.report_timing(); |
| } |
| }; |
| |
| class G1PostEvacuateCollectionSetCleanupTask2::ResizeTLABsTask : public G1AbstractSubTask { |
| G1JavaThreadsListClaimer _claimer; |
| |
| // There is not much work per thread so the number of threads per worker is high. |
| static const uint ThreadsPerWorker = 250; |
| |
| public: |
| ResizeTLABsTask() : G1AbstractSubTask(G1GCPhaseTimes::ResizeThreadLABs), _claimer(ThreadsPerWorker) { } |
| |
| void do_work(uint worker_id) override { |
| class ResizeClosure : public ThreadClosure { |
| public: |
| |
| void do_thread(Thread* thread) { |
| static_cast<JavaThread*>(thread)->tlab().resize(); |
| } |
| } cl; |
| _claimer.apply(&cl); |
| } |
| |
| double worker_cost() const override { |
| return (double)_claimer.length() / ThreadsPerWorker; |
| } |
| }; |
| |
| G1PostEvacuateCollectionSetCleanupTask2::G1PostEvacuateCollectionSetCleanupTask2(G1ParScanThreadStateSet* per_thread_states, |
| G1EvacInfo* evacuation_info, |
| G1EvacFailureRegions* evac_failure_regions) : |
| G1BatchedTask("Post Evacuate Cleanup 2", G1CollectedHeap::heap()->phase_times()) |
| { |
| #if COMPILER2_OR_JVMCI |
| add_serial_task(new UpdateDerivedPointersTask()); |
| #endif |
| if (G1CollectedHeap::heap()->has_humongous_reclaim_candidates()) { |
| add_serial_task(new EagerlyReclaimHumongousObjectsTask()); |
| } |
| |
| if (evac_failure_regions->evacuation_failed()) { |
| add_parallel_task(new RestorePreservedMarksTask(per_thread_states->preserved_marks_set())); |
| // Keep marks on bitmaps in retained regions during concurrent start - they will all be old. |
| if (!G1CollectedHeap::heap()->collector_state()->in_concurrent_start_gc()) { |
| add_parallel_task(new ClearRetainedRegionBitmaps(evac_failure_regions)); |
| } |
| } |
| add_parallel_task(new RedirtyLoggedCardsTask(per_thread_states->rdcqs(), evac_failure_regions)); |
| if (UseTLAB && ResizeTLAB) { |
| add_parallel_task(new ResizeTLABsTask()); |
| } |
| add_parallel_task(new FreeCollectionSetTask(evacuation_info, |
| per_thread_states->surviving_young_words(), |
| evac_failure_regions)); |
| } |