blob: 3e8f1bbba6429a883434188ee3cf5e4c8bcfe3cb [file] [edit]
/*
* 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 "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/g1CollectorState.hpp"
#include "gc/g1/g1ConcurrentMark.inline.hpp"
#include "gc/g1/g1EvacFailureRegions.inline.hpp"
#include "gc/g1/g1EvacInfo.hpp"
#include "gc/g1/g1EvacStats.inline.hpp"
#include "gc/g1/g1HeapRegion.inline.hpp"
#include "gc/g1/g1HeapRegionPrinter.hpp"
#include "gc/g1/g1HeapRegionRemSet.inline.hpp"
#include "gc/g1/g1OopClosures.inline.hpp"
#include "gc/g1/g1ParScanThreadState.hpp"
#include "gc/g1/g1RemSet.hpp"
#include "gc/g1/g1YoungGCPostEvacuateTasks.hpp"
#include "gc/shared/bufferNode.hpp"
#include "gc/shared/partialArrayState.hpp"
#include "jfr/jfrEvents.hpp"
#include "oops/access.inline.hpp"
#include "oops/compressedOops.inline.hpp"
#include "oops/oop.inline.hpp"
#include "runtime/prefetch.hpp"
#include "runtime/threads.hpp"
#include "runtime/threadSMR.hpp"
#include "utilities/bitMap.inline.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;
bool _allocation_failed;
public:
RecalculateUsedTask(bool evacuation_failed, bool allocation_failed) :
G1AbstractSubTask(G1GCPhaseTimes::RecalculateUsed),
_evacuation_failed(evacuation_failed),
_allocation_failed(allocation_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);
if (_allocation_failed) {
// Reset the G1GCAllocationFailureALot counters and flags
G1CollectedHeap::heap()->allocation_failure_injector()->reset();
}
}
};
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 (G1CSetCandidateGroup* gr : candidates->from_marking_groups()) {
_total.add(gr->card_set_memory_stats());
}
for (G1CSetCandidateGroup* gr : candidates->retained_groups()) {
_total.add(gr->card_set_memory_stats());
}
g1h->set_collection_set_candidates_stats(_total);
}
};
class G1PostEvacuateCollectionSetCleanupTask1::RestoreEvacFailureRegionsTask : public G1AbstractSubTask {
G1CollectedHeap* _g1h;
G1ConcurrentMark* _cm;
G1EvacFailureRegions* _evac_failure_regions;
CHeapBitMap _chunk_bitmap;
uint _num_chunks_per_region;
uint _num_evac_fail_regions;
size_t _chunk_size;
class PhaseTimesStat {
static constexpr G1GCPhaseTimes::GCParPhases phase_name =
G1GCPhaseTimes::RemoveSelfForwards;
G1GCPhaseTimes* _phase_times;
uint _worker_id;
Ticks _start;
public:
PhaseTimesStat(G1GCPhaseTimes* phase_times, uint worker_id) :
_phase_times(phase_times),
_worker_id(worker_id),
_start(Ticks::now()) { }
~PhaseTimesStat() {
_phase_times->record_or_add_time_secs(phase_name,
_worker_id,
(Ticks::now() - _start).seconds());
}
void register_empty_chunk() {
_phase_times->record_or_add_thread_work_item(phase_name,
_worker_id,
1,
G1GCPhaseTimes::RemoveSelfForwardEmptyChunksNum);
}
void register_nonempty_chunk() {
_phase_times->record_or_add_thread_work_item(phase_name,
_worker_id,
1,
G1GCPhaseTimes::RemoveSelfForwardChunksNum);
}
void register_objects_count_and_size(size_t num_marked_obj, size_t marked_words) {
_phase_times->record_or_add_thread_work_item(phase_name,
_worker_id,
num_marked_obj,
G1GCPhaseTimes::RemoveSelfForwardObjectsNum);
size_t marked_bytes = marked_words * HeapWordSize;
_phase_times->record_or_add_thread_work_item(phase_name,
_worker_id,
marked_bytes,
G1GCPhaseTimes::RemoveSelfForwardObjectsBytes);
}
};
// Fill the memory area from start to end with filler objects, and update the BOT
// accordingly. Since we clear and use the bitmap for marking objects that failed
// evacuation, there is no other work to be done there.
static size_t zap_dead_objects(G1HeapRegion* hr, HeapWord* start, HeapWord* end) {
assert(start <= end, "precondition");
if (start == end) {
return 0;
}
hr->fill_range_with_dead_objects(start, end);
return pointer_delta(end, start);
}
static void update_garbage_words_in_hr(G1HeapRegion* hr, size_t garbage_words) {
if (garbage_words != 0) {
hr->note_self_forward_chunk_done(garbage_words * HeapWordSize);
}
}
static void prefetch_obj(HeapWord* obj_addr) {
Prefetch::write(obj_addr, PrefetchScanIntervalInBytes);
}
bool claim_chunk(uint chunk_idx) {
return _chunk_bitmap.par_set_bit(chunk_idx);
}
void process_chunk(uint worker_id, uint chunk_idx) {
PhaseTimesStat stat(_g1h->phase_times(), worker_id);
G1CMBitMap* bitmap = _cm->mark_bitmap();
const uint region_idx = _evac_failure_regions->get_region_idx(chunk_idx / _num_chunks_per_region);
G1HeapRegion* hr = _g1h->region_at(region_idx);
HeapWord* hr_bottom = hr->bottom();
HeapWord* hr_top = hr->top();
HeapWord* chunk_start = hr_bottom + (chunk_idx % _num_chunks_per_region) * _chunk_size;
assert(chunk_start < hr->end(), "inv");
if (chunk_start >= hr_top) {
return;
}
HeapWord* chunk_end = MIN2(chunk_start + _chunk_size, hr_top);
HeapWord* first_marked_addr = bitmap->get_next_marked_addr(chunk_start, hr_top);
size_t garbage_words = 0;
if (chunk_start == hr_bottom) {
// This is the bottom-most chunk in this region; zap [bottom, first_marked_addr).
garbage_words += zap_dead_objects(hr, hr_bottom, first_marked_addr);
}
if (first_marked_addr >= chunk_end) {
stat.register_empty_chunk();
update_garbage_words_in_hr(hr, garbage_words);
return;
}
stat.register_nonempty_chunk();
size_t num_marked_objs = 0;
size_t marked_words = 0;
HeapWord* obj_addr = first_marked_addr;
assert(chunk_start <= obj_addr && obj_addr < chunk_end,
"object " PTR_FORMAT " must be within chunk [" PTR_FORMAT ", " PTR_FORMAT "[",
p2i(obj_addr), p2i(chunk_start), p2i(chunk_end));
do {
assert(bitmap->is_marked(obj_addr), "inv");
prefetch_obj(obj_addr);
oop obj = cast_to_oop(obj_addr);
const size_t obj_size = obj->size();
HeapWord* const obj_end_addr = obj_addr + obj_size;
{
// Process marked object.
assert(obj->is_self_forwarded(), "must be self-forwarded");
obj->unset_self_forwarded();
hr->update_bot_for_block(obj_addr, obj_end_addr);
// Statistics
num_marked_objs++;
marked_words += obj_size;
}
assert(obj_end_addr <= hr_top, "inv");
// Use hr_top as the limit so that we zap dead ranges up to the next
// marked obj or hr_top.
HeapWord* next_marked_obj_addr = bitmap->get_next_marked_addr(obj_end_addr, hr_top);
garbage_words += zap_dead_objects(hr, obj_end_addr, next_marked_obj_addr);
obj_addr = next_marked_obj_addr;
} while (obj_addr < chunk_end);
assert(marked_words > 0 && num_marked_objs > 0, "inv");
stat.register_objects_count_and_size(num_marked_objs, marked_words);
update_garbage_words_in_hr(hr, garbage_words);
}
public:
RestoreEvacFailureRegionsTask(G1EvacFailureRegions* evac_failure_regions) :
G1AbstractSubTask(G1GCPhaseTimes::RestoreEvacuationFailedRegions),
_g1h(G1CollectedHeap::heap()),
_cm(_g1h->concurrent_mark()),
_evac_failure_regions(evac_failure_regions),
_chunk_bitmap(mtGC) {
_num_evac_fail_regions = _evac_failure_regions->num_regions_evac_failed();
_num_chunks_per_region = G1CollectedHeap::get_chunks_per_region();
_chunk_size = static_cast<uint>(G1HeapRegion::GrainWords / _num_chunks_per_region);
log_debug(gc, ergo)("Initializing removing self forwards with %u chunks per region",
_num_chunks_per_region);
_chunk_bitmap.resize(_num_chunks_per_region * _num_evac_fail_regions);
}
double worker_cost() const override {
assert(_evac_failure_regions->has_regions_evac_failed(), "Should not call this if there were no evacuation failures");
double workers_per_region = (double)G1CollectedHeap::get_chunks_per_region() / G1RestoreRetainedRegionChunksPerWorker;
return workers_per_region * _evac_failure_regions->num_regions_evac_failed();
}
void do_work(uint worker_id) override {
const uint total_workers = G1CollectedHeap::heap()->workers()->active_workers();
const uint total_chunks = _num_chunks_per_region * _num_evac_fail_regions;
const uint start_chunk_idx = worker_id * total_chunks / total_workers;
for (uint i = 0; i < total_chunks; i++) {
const uint chunk_idx = (start_chunk_idx + i) % total_chunks;
if (claim_chunk(chunk_idx)) {
process_chunk(worker_id, chunk_idx);
}
}
}
};
G1PostEvacuateCollectionSetCleanupTask1::G1PostEvacuateCollectionSetCleanupTask1(G1ParScanThreadStateSet* per_thread_states,
G1EvacFailureRegions* evac_failure_regions) :
G1BatchedTask("Post Evacuate Cleanup 1", G1CollectedHeap::heap()->phase_times())
{
bool evac_failed = evac_failure_regions->has_regions_evac_failed();
bool alloc_failed = evac_failure_regions->has_regions_alloc_failed();
add_serial_task(new MergePssTask(per_thread_states));
add_serial_task(new RecalculateUsedTask(evac_failed, alloc_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 (evac_failed) {
add_parallel_task(new RestoreEvacFailureRegionsTask(evac_failure_regions));
}
}
class G1FreeHumongousRegionClosure : public G1HeapRegionIndexClosure {
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;
}
G1HeapRegion* 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 %zu @ " 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 = [&] (G1HeapRegion* r) {
_freed_bytes += r->used();
r->set_containing_set(nullptr);
_humongous_regions_reclaimed++;
G1HeapRegionPrinter::eager_reclaim(r);
_g1h->free_humongous_region(r, nullptr);
};
_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 RedirtyLoggedCardTableEntryClosure : public G1CardTableEntryClosure {
size_t _num_dirtied;
G1CollectedHeap* _g1h;
G1CardTable* _g1_ct;
G1EvacFailureRegions* _evac_failure_regions;
G1HeapRegion* region_for_card(CardValue* card_ptr) const {
return _g1h->heap_region_containing(_g1_ct->addr_for(card_ptr));
}
bool will_become_free(G1HeapRegion* 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) override {
G1HeapRegion* 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::ProcessEvacuationFailedRegionsTask : public G1AbstractSubTask {
G1EvacFailureRegions* _evac_failure_regions;
G1HeapRegionClaimer _claimer;
class ProcessEvacuationFailedRegionsClosure : public G1HeapRegionClosure {
public:
bool do_heap_region(G1HeapRegion* r) override {
G1CollectedHeap* g1h = G1CollectedHeap::heap();
G1ConcurrentMark* cm = g1h->concurrent_mark();
HeapWord* top_at_mark_start = cm->top_at_mark_start(r);
assert(top_at_mark_start == r->bottom(), "TAMS must not have been set for region %u", r->hrm_index());
assert(cm->live_bytes(r->hrm_index()) == 0, "Marking live bytes must not be set for region %u", r->hrm_index());
// Concurrent mark does not mark through regions that we retain (they are root
// regions wrt to marking), so we must clear their mark data (tams, bitmap, ...)
// set eagerly or during evacuation failure.
bool clear_mark_data = !g1h->collector_state()->in_concurrent_start_gc() ||
g1h->policy()->should_retain_evac_failed_region(r);
if (clear_mark_data) {
g1h->clear_bitmap_for_region(r);
} else {
// This evacuation failed region is going to be marked through. Update mark data.
cm->update_top_at_mark_start(r);
cm->set_live_bytes(r->hrm_index(), r->live_bytes());
assert(cm->mark_bitmap()->get_next_marked_addr(r->bottom(), cm->top_at_mark_start(r)) != cm->top_at_mark_start(r),
"Marks must be on bitmap for region %u", r->hrm_index());
}
return false;
}
};
public:
ProcessEvacuationFailedRegionsTask(G1EvacFailureRegions* evac_failure_regions) :
G1AbstractSubTask(G1GCPhaseTimes::ProcessEvacuationFailedRegions),
_evac_failure_regions(evac_failure_regions),
_claimer(0) {
}
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_evac_failed();
}
void do_work(uint worker_id) override {
ProcessEvacuationFailedRegionsClosure cl;
_evac_failure_regions->par_iterate(&cl, &_claimer, worker_id);
}
};
class G1PostEvacuateCollectionSetCleanupTask2::RedirtyLoggedCardsTask : public G1AbstractSubTask {
BufferNodeList* _rdc_buffers;
uint _num_buffer_lists;
G1EvacFailureRegions* _evac_failure_regions;
public:
RedirtyLoggedCardsTask(G1EvacFailureRegions* evac_failure_regions, BufferNodeList* rdc_buffers, uint num_buffer_lists) :
G1AbstractSubTask(G1GCPhaseTimes::RedirtyCards),
_rdc_buffers(rdc_buffers),
_num_buffer_lists(num_buffer_lists),
_evac_failure_regions(evac_failure_regions) { }
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);
uint start = worker_id;
for (uint i = 0; i < _num_buffer_lists; i++) {
uint index = (start + i) % _num_buffer_lists;
BufferNode* next = Atomic::load(&_rdc_buffers[index]._head);
BufferNode* tail = Atomic::load(&_rdc_buffers[index]._tail);
while (next != nullptr) {
BufferNode* node = next;
next = Atomic::cmpxchg(&_rdc_buffers[index]._head, node, (node != tail ) ? node->next() : nullptr);
if (next == node) {
cl.apply_to_buffer(node, worker_id);
next = (node != tail ) ? node->next() : nullptr;
} else {
break; // If there is contention, move to the next BufferNodeList
}
}
}
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
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),
_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;
_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->cset_regions_freed();
}
void account_failed_region(G1HeapRegion* r) {
size_t used_words = r->live_bytes() / HeapWordSize;
_failure_used_words += used_words;
_failure_waste_words += G1HeapRegion::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 += G1HeapRegion::GrainBytes;
}
}
void account_evacuated_region(G1HeapRegion* 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;
}
};
// Closure applied to all regions in the collection set.
class FreeCSetClosure : public G1HeapRegionClosure {
// Helper to send JFR events for regions.
class JFREventForRegion {
EventGCPhaseParallel _event;
public:
JFREventForRegion(G1HeapRegion* 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;
uint _num_retained_regions;
void assert_tracks_surviving_words(G1HeapRegion* 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(G1HeapRegion* r) {
assert(!r->is_empty(), "Region %u is an empty region in the collection set.", r->hrm_index());
stats()->account_evacuated_region(r);
G1HeapRegionPrinter::evac_reclaim(r);
// Free the region and its remembered set.
_g1h->free_region(r, nullptr);
}
void handle_failed_region(G1HeapRegion* 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::RestoreEvacuationFailedRegions,
_worker_id,
1,
G1GCPhaseTimes::RestoreEvacFailureRegionsEvacFailedNum);
bool retain_region = _g1h->policy()->should_retain_evac_failed_region(r);
// Update the region state due to the failed evacuation.
r->handle_evacuation_failure(retain_region);
assert(r->is_old(), "must already be relabelled as old");
if (retain_region) {
_g1h->retain_region(r);
_num_retained_regions++;
}
assert(retain_region == r->rem_set()->is_tracked(), "When retaining a region, remembered set should be kept.");
// 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(G1HeapRegion* 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) :
G1HeapRegionClosure(),
_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),
_num_retained_regions(0) { }
virtual bool do_heap_region(G1HeapRegion* 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));
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());
}
}
bool num_retained_regions() const { return _num_retained_regions; }
};
class G1PostEvacuateCollectionSetCleanupTask2::FreeCollectionSetTask : public G1AbstractSubTask {
G1CollectedHeap* _g1h;
G1EvacInfo* _evacuation_info;
FreeCSetStats* _worker_stats;
G1HeapRegionClaimer _claimer;
const size_t* _surviving_young_words;
uint _active_workers;
G1EvacFailureRegions* _evac_failure_regions;
volatile uint _num_retained_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),
_num_retained_regions(0) {
_g1h->clear_eden();
}
virtual ~FreeCollectionSetTask() {
Ticks serial_time = Ticks::now();
bool has_new_retained_regions = Atomic::load(&_num_retained_regions) != 0;
if (has_new_retained_regions) {
G1CollectionSetCandidates* candidates = _g1h->collection_set()->candidates();
candidates->sort_by_efficiency();
}
report_statistics();
for (uint worker = 0; worker < _active_workers; worker++) {
_worker_stats[worker].~FreeCSetStats();
}
FREE_C_HEAP_ARRAY(FreeCSetStats, _worker_stats);
_g1h->clear_collection_set();
G1GCPhaseTimes* p = _g1h->phase_times();
p->record_serial_free_cset_time_ms((Ticks::now() - serial_time).seconds() * 1000.0);
}
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();
Atomic::add(&_num_retained_regions, cl.num_retained_regions(), memory_order_relaxed);
}
};
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;
}
};
class G1PostEvacuateCollectionSetCleanupTask2::ResetPartialArrayStateManagerTask
: public G1AbstractSubTask
{
public:
ResetPartialArrayStateManagerTask()
: G1AbstractSubTask(G1GCPhaseTimes::ResetPartialArrayStateManager)
{}
double worker_cost() const override {
return AlmostNoWork;
}
void do_work(uint worker_id) override {
// This must be in phase2 cleanup, after phase1 has destroyed all of the
// associated allocators.
G1CollectedHeap::heap()->partial_array_state_manager()->reset();
}
};
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());
}
add_serial_task(new ResetPartialArrayStateManagerTask());
if (evac_failure_regions->has_regions_evac_failed()) {
add_parallel_task(new ProcessEvacuationFailedRegionsTask(evac_failure_regions));
}
add_parallel_task(new RedirtyLoggedCardsTask(evac_failure_regions,
per_thread_states->rdc_buffers(),
per_thread_states->num_workers()));
if (UseTLAB && ResizeTLAB) {
add_parallel_task(new ResizeTLABsTask());
}
add_parallel_task(new FreeCollectionSetTask(evacuation_info,
per_thread_states->surviving_young_words(),
evac_failure_regions));
}