blob: 6c13c015effe1791df817b6cc4623df67a0fc0e7 [file]
/*
* Copyright (c) 2019, 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/g1/g1CollectionSetCandidates.inline.hpp"
#include "gc/g1/g1CollectionSetChooser.hpp"
#include "gc/g1/g1HeapRegion.inline.hpp"
#include "utilities/growableArray.hpp"
uint G1CSetCandidateGroup::_next_group_id = 2;
G1CSetCandidateGroup::G1CSetCandidateGroup(G1CardSetConfiguration* config, G1MonotonicArenaFreePool* card_set_freelist_pool, uint group_id) :
_candidates(4, mtGCCardSet),
_card_set_mm(config, card_set_freelist_pool),
_card_set(config, &_card_set_mm),
_reclaimable_bytes(size_t(0)),
_gc_efficiency(0.0),
_group_id(group_id)
{ }
G1CSetCandidateGroup::G1CSetCandidateGroup() :
G1CSetCandidateGroup(G1CollectedHeap::heap()->card_set_config(), G1CollectedHeap::heap()->card_set_freelist_pool(), _next_group_id++)
{ }
void G1CSetCandidateGroup::add(G1HeapRegion* hr) {
G1CollectionSetCandidateInfo c(hr);
add(c);
}
void G1CSetCandidateGroup::add(G1CollectionSetCandidateInfo& hr_info) {
G1HeapRegion* hr = hr_info._r;
_candidates.append(hr_info);
hr->install_cset_group(this);
}
void G1CSetCandidateGroup::calculate_efficiency() {
_reclaimable_bytes = 0;
uint num_candidates = _candidates.length();
for (uint i = 0; i < num_candidates; i++) {
G1HeapRegion* hr = region_at(i);
_reclaimable_bytes += hr->reclaimable_bytes();
}
_gc_efficiency = _reclaimable_bytes / predict_group_total_time_ms();
}
size_t G1CSetCandidateGroup::liveness() const {
size_t capacity = length() * G1HeapRegion::GrainBytes;
return (size_t) ceil(((capacity - _reclaimable_bytes) * 100.0) / capacity);
}
void G1CSetCandidateGroup::clear(bool uninstall_group_cardset) {
if (uninstall_group_cardset) {
for (G1CollectionSetCandidateInfo ci : _candidates) {
G1HeapRegion* r = ci._r;
r->uninstall_cset_group();
r->rem_set()->clear(true /* only_cardset */);
}
}
_card_set.clear();
_candidates.clear();
}
double G1CSetCandidateGroup::predict_group_total_time_ms() const {
G1Policy* p = G1CollectedHeap::heap()->policy();
double predicted_copy_time_ms = 0.0;
double predict_code_root_scan_time_ms = 0.0;
size_t predict_bytes_to_copy = 0.0;
for (G1CollectionSetCandidateInfo ci : _candidates) {
G1HeapRegion* r = ci._r;
assert(r->rem_set()->cset_group() == this, "Must be!");
predict_bytes_to_copy += p->predict_bytes_to_copy(r);
predicted_copy_time_ms += p->predict_region_copy_time_ms(r, false /* for_young_only_phase */);
predict_code_root_scan_time_ms += p->predict_region_code_root_scan_time(r, false /* for_young_only_phase */);
}
size_t card_rs_length = _card_set.occupied();
double merge_scan_time_ms = p->predict_merge_scan_time(card_rs_length);
double non_young_other_time_ms = p->predict_non_young_other_time_ms(length());
double total_time_ms = merge_scan_time_ms +
predict_code_root_scan_time_ms +
predicted_copy_time_ms +
non_young_other_time_ms;
log_trace(gc, ergo, cset) ("Prediction for group %u (%u regions): total_time %.2fms card_rs_length %zu merge_scan_time %.2fms code_root_scan_time_ms %.2fms evac_time_ms %.2fms other_time %.2fms bytes_to_copy %zu",
group_id(),
length(),
total_time_ms,
card_rs_length,
merge_scan_time_ms,
predict_code_root_scan_time_ms,
predicted_copy_time_ms,
non_young_other_time_ms,
predict_bytes_to_copy);
return total_time_ms;
}
int G1CSetCandidateGroup::compare_gc_efficiency(G1CSetCandidateGroup** gr1, G1CSetCandidateGroup** gr2) {
double gc_eff1 = (*gr1)->gc_efficiency();
double gc_eff2 = (*gr2)->gc_efficiency();
if (gc_eff1 > gc_eff2) {
return -1;
} else if (gc_eff1 < gc_eff2) {
return 1;
} else {
return 0;
}
}
int G1CollectionSetCandidateInfo::compare_region_gc_efficiency(G1CollectionSetCandidateInfo* ci1, G1CollectionSetCandidateInfo* ci2) {
// Make sure that null entries are moved to the end.
if (ci1->_r == nullptr) {
if (ci2->_r == nullptr) {
return 0;
} else {
return 1;
}
} else if (ci2->_r == nullptr) {
return -1;
}
G1Policy* p = G1CollectedHeap::heap()->policy();
double gc_efficiency1 = p->predict_gc_efficiency(ci1->_r);
double gc_efficiency2 = p->predict_gc_efficiency(ci2->_r);
if (gc_efficiency1 > gc_efficiency2) {
return -1;
} else if (gc_efficiency1 < gc_efficiency2) {
return 1;
} else {
return 0;
}
}
G1CSetCandidateGroupList::G1CSetCandidateGroupList() : _groups(8, mtGC), _num_regions(0) { }
void G1CSetCandidateGroupList::append(G1CSetCandidateGroup* group) {
assert(group->length() > 0, "Do not add empty groups");
assert(!_groups.contains(group), "Already added to list");
_groups.append(group);
_num_regions += group->length();
}
G1CSetCandidateGroup* G1CSetCandidateGroupList::at(uint index) {
return _groups.at(index);
}
void G1CSetCandidateGroupList::clear(bool uninstall_group_cardset) {
for (G1CSetCandidateGroup* gr : _groups) {
gr->clear(uninstall_group_cardset);
delete gr;
}
_groups.clear();
_num_regions = 0;
}
void G1CSetCandidateGroupList::prepare_for_scan() {
for (G1CSetCandidateGroup* gr : _groups) {
gr->card_set()->reset_table_scanner_for_groups();
}
}
void G1CSetCandidateGroupList::remove_selected(uint count, uint num_regions) {
_groups.remove_till(count);
_num_regions -= num_regions;
}
void G1CSetCandidateGroupList::remove(G1CSetCandidateGroupList* other) {
guarantee((uint)_groups.length() >= other->length(), "Other should be a subset of this list");
if (other->length() == 0) {
// Nothing to remove or nothing in the original set.
return;
}
// Create a list from scratch, copying over the elements from the candidate
// list not in the other list. Finally deallocate and overwrite the old list.
int new_length = _groups.length() - other->length();
_num_regions = num_regions() - other->num_regions();
GrowableArray<G1CSetCandidateGroup*> new_list(new_length, mtGC);
uint other_idx = 0;
for (G1CSetCandidateGroup* gr : _groups) {
if (other_idx == other->length() || gr != other->at(other_idx)) {
new_list.append(gr);
} else {
other_idx++;
}
}
_groups.swap(&new_list);
verify();
assert(_groups.length() == new_length, "Must be");
}
void G1CSetCandidateGroupList::sort_by_efficiency() {
_groups.sort(G1CSetCandidateGroup::compare_gc_efficiency);
}
#ifndef PRODUCT
void G1CSetCandidateGroupList::verify() const {
G1CSetCandidateGroup* prev = nullptr;
for (G1CSetCandidateGroup* gr : _groups) {
assert(prev == nullptr || prev->gc_efficiency() >= gr->gc_efficiency(),
"Stored gc efficiency must be descending");
prev = gr;
}
}
#endif
G1CollectionSetCandidates::G1CollectionSetCandidates() :
_contains_map(nullptr),
_from_marking_groups(),
_retained_groups(),
_max_regions(0),
_last_marking_candidates_length(0)
{ }
G1CollectionSetCandidates::~G1CollectionSetCandidates() {
FREE_C_HEAP_ARRAY(CandidateOrigin, _contains_map);
_from_marking_groups.clear();
_retained_groups.clear();
}
bool G1CollectionSetCandidates::is_from_marking(G1HeapRegion* r) const {
assert(contains(r), "must be");
return _contains_map[r->hrm_index()] == CandidateOrigin::Marking;
}
void G1CollectionSetCandidates::initialize(uint max_regions) {
assert(_contains_map == nullptr, "already initialized");
_max_regions = max_regions;
_contains_map = NEW_C_HEAP_ARRAY(CandidateOrigin, max_regions, mtGC);
clear();
}
void G1CollectionSetCandidates::clear() {
_retained_groups.clear(true /* uninstall_group_cardset */);
_from_marking_groups.clear(true /* uninstall_group_cardset */);
for (uint i = 0; i < _max_regions; i++) {
_contains_map[i] = CandidateOrigin::Invalid;
}
_last_marking_candidates_length = 0;
}
void G1CollectionSetCandidates::sort_marking_by_efficiency() {
for (G1CSetCandidateGroup* gr : _from_marking_groups) {
gr->calculate_efficiency();
}
_from_marking_groups.sort_by_efficiency();
_from_marking_groups.verify();
}
void G1CollectionSetCandidates::set_candidates_from_marking(G1CollectionSetCandidateInfo* candidate_infos,
uint num_infos) {
if (num_infos == 0) {
log_debug(gc, ergo, cset) ("No regions selected from marking.");
return;
}
assert(_from_marking_groups.length() == 0, "must be empty at the start of a cycle");
verify();
G1Policy* p = G1CollectedHeap::heap()->policy();
// During each Mixed GC, we must collect at least G1Policy::calc_min_old_cset_length regions to meet
// the G1MixedGCCountTarget. For the first collection in a Mixed GC cycle, we can add all regions
// required to meet this threshold to the same remset group. We are certain these will be collected in
// the same MixedGC.
uint group_limit = p->calc_min_old_cset_length(num_infos);
uint num_added_to_group = 0;
G1CSetCandidateGroup::reset_next_group_id();
G1CSetCandidateGroup* current = nullptr;
current = new G1CSetCandidateGroup();
for (uint i = 0; i < num_infos; i++) {
G1HeapRegion* r = candidate_infos[i]._r;
assert(!contains(r), "must not contain region %u", r->hrm_index());
_contains_map[r->hrm_index()] = CandidateOrigin::Marking;
if (num_added_to_group == group_limit) {
if (group_limit != G1OldCSetGroupSize) {
group_limit = G1OldCSetGroupSize;
}
_from_marking_groups.append(current);
current = new G1CSetCandidateGroup();
num_added_to_group = 0;
}
current->add(candidate_infos[i]);
num_added_to_group++;
}
_from_marking_groups.append(current);
assert(_from_marking_groups.num_regions() == num_infos, "Must be!");
log_debug(gc, ergo, cset) ("Finished creating %u collection groups from %u regions", _from_marking_groups.length(), num_infos);
_last_marking_candidates_length = num_infos;
verify();
}
void G1CollectionSetCandidates::sort_by_efficiency() {
// From marking regions must always be sorted so no reason to actually sort
// them.
_from_marking_groups.verify();
_retained_groups.sort_by_efficiency();
_retained_groups.verify();
}
void G1CollectionSetCandidates::remove(G1CSetCandidateGroupList* other) {
// During removal, we exploit the fact that elements in the marking_regions,
// retained_regions and other list are sorted by gc_efficiency. Furthermore,
// all regions in the passed other list are in one of the two other lists.
//
// Split original list into elements for the marking list and elements from the
// retained list.
G1CSetCandidateGroupList other_marking_groups;
G1CSetCandidateGroupList other_retained_groups;
for (G1CSetCandidateGroup* group : *other) {
assert(group->length() > 0, "Should not have empty groups");
// Regions in the same group have the same source (i.e from_marking or retained).
G1HeapRegion* r = group->region_at(0);
if (is_from_marking(r)) {
other_marking_groups.append(group);
} else {
other_retained_groups.append(group);
}
}
_from_marking_groups.remove(&other_marking_groups);
_retained_groups.remove(&other_retained_groups);
other->iterate([&] (G1HeapRegion* r) {
assert(contains(r), "Must contain region %u", r->hrm_index());
_contains_map[r->hrm_index()] = CandidateOrigin::Invalid;
});
verify();
}
void G1CollectionSetCandidates::add_retained_region_unsorted(G1HeapRegion* r) {
assert(!contains(r), "Must not already contain region %u", r->hrm_index());
_contains_map[r->hrm_index()] = CandidateOrigin::Retained;
G1CSetCandidateGroup* gr = new G1CSetCandidateGroup();
gr->add(r);
_retained_groups.append(gr);
}
bool G1CollectionSetCandidates::is_empty() const {
return length() == 0;
}
bool G1CollectionSetCandidates::has_more_marking_candidates() const {
return marking_regions_length() != 0;
}
uint G1CollectionSetCandidates::marking_regions_length() const {
return _from_marking_groups.num_regions();
}
uint G1CollectionSetCandidates::retained_regions_length() const {
return _retained_groups.num_regions();
}
#ifndef PRODUCT
void G1CollectionSetCandidates::verify_helper(G1CSetCandidateGroupList* list, uint& from_marking, CandidateOrigin* verify_map) {
list->verify();
for (G1CSetCandidateGroup* gr : *list) {
for (G1CollectionSetCandidateInfo ci : *gr) {
G1HeapRegion* r = ci._r;
if (is_from_marking(r)) {
from_marking++;
}
const uint hrm_index = r->hrm_index();
assert(_contains_map[hrm_index] == CandidateOrigin::Marking || _contains_map[hrm_index] == CandidateOrigin::Retained,
"must be %u is %u", hrm_index, (uint)_contains_map[hrm_index]);
assert(verify_map[hrm_index] == CandidateOrigin::Invalid, "already added");
verify_map[hrm_index] = CandidateOrigin::Verify;
}
}
}
void G1CollectionSetCandidates::verify() {
uint from_marking = 0;
CandidateOrigin* verify_map = NEW_C_HEAP_ARRAY(CandidateOrigin, _max_regions, mtGC);
for (uint i = 0; i < _max_regions; i++) {
verify_map[i] = CandidateOrigin::Invalid;
}
verify_helper(&_from_marking_groups, from_marking, verify_map);
assert(from_marking == marking_regions_length(), "must be");
uint from_marking_retained = 0;
verify_helper(&_retained_groups, from_marking_retained, verify_map);
assert(from_marking_retained == 0, "must be");
assert(length() >= marking_regions_length(), "must be");
// Check whether the _contains_map is consistent with the list.
for (uint i = 0; i < _max_regions; i++) {
assert(_contains_map[i] == verify_map[i] ||
(_contains_map[i] != CandidateOrigin::Invalid && verify_map[i] == CandidateOrigin::Verify),
"Candidate origin does not match for region %u, is %u but should be %u",
i,
static_cast<std::underlying_type<CandidateOrigin>::type>(_contains_map[i]),
static_cast<std::underlying_type<CandidateOrigin>::type>(verify_map[i]));
}
FREE_C_HEAP_ARRAY(CandidateOrigin, verify_map);
}
#endif
bool G1CollectionSetCandidates::contains(const G1HeapRegion* r) const {
const uint index = r->hrm_index();
assert(index < _max_regions, "must be");
return _contains_map[index] != CandidateOrigin::Invalid;
}
const char* G1CollectionSetCandidates::get_short_type_str(const G1HeapRegion* r) const {
static const char* type_strings[] = {
"Ci", // Invalid
"Cm", // Marking
"Cr", // Retained
"Cv" // Verification
};
uint8_t kind = static_cast<std::underlying_type<CandidateOrigin>::type>(_contains_map[r->hrm_index()]);
return type_strings[kind];
}