8017163: G1: Refactor remembered sets

8048504: G1: Investigate replacing the coarse and fine grained data structures in the remembered sets
6949259: G1: Merge sparse and fine remembered set hash tables

Co-authored-by: Ivan Walulya <iwalulya@openjdk.org>
Co-authored-by: Thomas Schatzl <tschatzl@openjdk.org>
Reviewed-by: sjohanss, iwalulya
This commit is contained in:
Thomas Schatzl 2021-06-21 10:06:05 +00:00
parent 0b8a0e2b58
commit 1692fd2eba
64 changed files with 4792 additions and 1574 deletions

View File

@ -25,6 +25,8 @@
#include "precompiled.hpp"
#include "gc/g1/g1Arguments.hpp"
#include "gc/g1/g1CardSet.hpp"
#include "gc/g1/g1CardSetContainers.inline.hpp"
#include "gc/g1/g1CollectedHeap.inline.hpp"
#include "gc/g1/g1HeapVerifier.hpp"
#include "gc/g1/heapRegion.hpp"
@ -50,16 +52,17 @@ void G1Arguments::initialize_alignments() {
// around this we use the unaligned values for the heap.
HeapRegion::setup_heap_region_size(MaxHeapSize);
// The remembered set needs the heap regions set up.
HeapRegionRemSet::setup_remset_size();
SpaceAlignment = HeapRegion::GrainBytes;
HeapAlignment = calculate_heap_alignment(SpaceAlignment);
// We need to initialize card set configuration as soon as heap region size is
// known as it depends on it and is used really early.
initialize_card_set_configuration();
// Needs remembered set initialization as the ergonomics are based
// on it.
if (FLAG_IS_DEFAULT(G1EagerReclaimRemSetThreshold)) {
FLAG_SET_ERGO(G1EagerReclaimRemSetThreshold, G1RSetSparseRegionEntries);
FLAG_SET_ERGO(G1EagerReclaimRemSetThreshold, G1RemSetArrayOfCardsEntries);
}
SpaceAlignment = HeapRegion::GrainBytes;
HeapAlignment = calculate_heap_alignment(SpaceAlignment);
}
size_t G1Arguments::conservative_max_heap_alignment() {
@ -119,6 +122,40 @@ void G1Arguments::initialize_mark_stack_size() {
log_trace(gc)("MarkStackSize: %uk MarkStackSizeMax: %uk", (uint)(MarkStackSize / K), (uint)(MarkStackSizeMax / K));
}
void G1Arguments::initialize_card_set_configuration() {
assert(HeapRegion::LogOfHRGrainBytes != 0, "not initialized");
// Array of Cards card set container globals.
const int LOG_M = 20;
uint region_size_log_mb = (uint)MAX2(HeapRegion::LogOfHRGrainBytes - LOG_M, 0);
if (FLAG_IS_DEFAULT(G1RemSetArrayOfCardsEntries)) {
uint num_cards_in_inline_ptr = G1CardSetConfiguration::num_cards_in_inline_ptr(HeapRegion::LogOfHRGrainBytes - CardTable::card_shift);
FLAG_SET_ERGO(G1RemSetArrayOfCardsEntries, MAX2(num_cards_in_inline_ptr * 2,
G1RemSetArrayOfCardsEntriesBase * (1u << (region_size_log_mb + 1))));
}
// Round to next 8 byte boundary for array to maximize space usage.
size_t const cur_size = G1CardSetArray::size_in_bytes(G1RemSetArrayOfCardsEntries);
FLAG_SET_ERGO(G1RemSetArrayOfCardsEntries,
G1RemSetArrayOfCardsEntries + (uint)(align_up(cur_size, G1CardSetAllocOptions::BufferAlignment) - cur_size) / sizeof(G1CardSetArray::EntryDataType));
// Howl card set container globals.
if (FLAG_IS_DEFAULT(G1RemSetHowlNumBuckets)) {
FLAG_SET_ERGO(G1RemSetHowlNumBuckets, G1CardSetHowl::num_buckets(HeapRegion::CardsPerRegion,
G1RemSetArrayOfCardsEntries,
G1RemSetHowlMaxNumBuckets));
}
if (FLAG_IS_DEFAULT(G1RemSetHowlMaxNumBuckets)) {
FLAG_SET_ERGO(G1RemSetHowlMaxNumBuckets, MAX2(G1RemSetHowlMaxNumBuckets, G1RemSetHowlNumBuckets));
} else if (G1RemSetHowlMaxNumBuckets < G1RemSetHowlNumBuckets) {
FormatBuffer<> buf("Maximum Howl card set container bucket size %u smaller than requested bucket size %u",
G1RemSetHowlMaxNumBuckets, G1RemSetHowlNumBuckets);
vm_exit_during_initialization(buf);
}
}
void G1Arguments::initialize() {
GCArguments::initialize();
assert(UseG1GC, "Error");
@ -196,6 +233,14 @@ void G1Arguments::initialize() {
initialize_mark_stack_size();
initialize_verification_types();
// Verify that the maximum parallelism isn't too high to eventually overflow
// the refcount in G1CardSetContainer.
uint max_parallel_refinement_threads = G1ConcRefinementThreads + G1DirtyCardQueueSet::num_par_ids();
uint const divisor = 3; // Safe divisor; we increment by 2 for each claim, but there is a small initial value.
if (max_parallel_refinement_threads > UINTPTR_MAX / divisor) {
vm_exit_during_initialization("Too large parallelism for remembered sets.");
}
}
void G1Arguments::initialize_heap_flags_and_sizes() {

View File

@ -34,6 +34,7 @@ class G1Arguments : public GCArguments {
friend class G1HeapVerifierTest;
static void initialize_mark_stack_size();
static void initialize_card_set_configuration();
static void initialize_verification_types();
static void parse_verification_type(const char* type);

View File

@ -0,0 +1,887 @@
/*
* Copyright (c) 2021, 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 "gc/g1/g1CardSet.inline.hpp"
#include "gc/g1/g1CardSetContainers.inline.hpp"
#include "gc/g1/g1CardSetMemory.inline.hpp"
#include "gc/g1/g1FromCardCache.hpp"
#include "gc/g1/heapRegion.inline.hpp"
#include "memory/allocation.inline.hpp"
#include "runtime/atomic.hpp"
#include "runtime/globals_extension.hpp"
#include "runtime/mutex.hpp"
#include "utilities/bitMap.inline.hpp"
#include "utilities/concurrentHashTable.inline.hpp"
#include "utilities/globalDefinitions.hpp"
#include "utilities/lockFreeStack.hpp"
#include "utilities/spinYield.hpp"
#include "gc/shared/gcLogPrecious.hpp"
#include "gc/shared/gcTraceTime.inline.hpp"
#include "runtime/java.hpp"
G1CardSet::CardSetPtr G1CardSet::FullCardSet = (G1CardSet::CardSetPtr)-1;
G1CardSetConfiguration::G1CardSetConfiguration() :
_inline_ptr_bits_per_card(HeapRegion::LogOfHRGrainBytes - CardTable::card_shift) {
// Array of Cards card set container size calculation
_num_cards_in_array = G1RemSetArrayOfCardsEntries;
// Full card set container size calculation
_max_cards_in_card_set = (uint)HeapRegion::CardsPerRegion;
assert(is_power_of_2(_max_cards_in_card_set),
"max_cards_in_card_set must be a power of 2: %u", _max_cards_in_card_set);
_cards_in_howl_threshold = _max_cards_in_card_set * (double)G1RemSetCoarsenHowlToFullPercent / 100;
// Howl card set container size calculation.
_num_buckets_in_howl = G1RemSetHowlNumBuckets;
// Howl Bitmap card set container size calculation.
_num_cards_in_howl_bitmap = G1CardSetHowl::bitmap_size(_max_cards_in_card_set, _num_buckets_in_howl);
_log2_num_cards_in_howl_bitmap = log2i_exact(_num_cards_in_howl_bitmap);
_cards_in_howl_bitmap_threshold = _num_cards_in_howl_bitmap * (double)G1RemSetCoarsenHowlBitmapToHowlFullPercent / 100;
_bitmap_hash_mask = ~(~(0) << _log2_num_cards_in_howl_bitmap);
log_configuration();
}
G1CardSetConfiguration::G1CardSetConfiguration(uint inline_ptr_bits_per_card,
uint num_cards_in_array,
double cards_in_bitmap_threshold,
uint max_buckets_in_howl,
double cards_in_howl_threshold,
uint max_cards_in_cardset) :
_inline_ptr_bits_per_card(inline_ptr_bits_per_card),
_num_cards_in_array(num_cards_in_array),
_max_cards_in_card_set(max_cards_in_cardset),
_cards_in_howl_threshold(max_cards_in_cardset * cards_in_howl_threshold) {
assert(is_power_of_2(_max_cards_in_card_set),
"max_cards_in_card_set must be a power of 2: %u", _max_cards_in_card_set);
_num_buckets_in_howl = G1CardSetHowl::num_buckets(_max_cards_in_card_set, _num_cards_in_array, max_buckets_in_howl);
_num_cards_in_howl_bitmap = G1CardSetHowl::bitmap_size(_max_cards_in_card_set, _num_buckets_in_howl);
_cards_in_howl_bitmap_threshold = _num_cards_in_howl_bitmap * cards_in_bitmap_threshold;
_log2_num_cards_in_howl_bitmap = log2i_exact(_num_cards_in_howl_bitmap);
_bitmap_hash_mask = ~(~(0) << _log2_num_cards_in_howl_bitmap);
log_configuration();
}
void G1CardSetConfiguration::log_configuration() {
log_debug_p(gc, remset)("Card Set container configuration: "
"InlinePtr #elems %u size %zu "
"Array Of Cards #elems %u size %zu "
"Howl #buckets %u coarsen threshold %u "
"Howl Bitmap #elems %u size %zu coarsen threshold %u",
num_cards_in_inline_ptr(), sizeof(void*),
num_cards_in_array(), G1CardSetArray::size_in_bytes(num_cards_in_array()),
num_buckets_in_howl(), cards_in_howl_threshold(),
num_cards_in_howl_bitmap(), G1CardSetBitMap::size_in_bytes(num_cards_in_howl_bitmap()), cards_in_howl_bitmap_threshold());
}
uint G1CardSetConfiguration::num_cards_in_inline_ptr() const {
return num_cards_in_inline_ptr(_inline_ptr_bits_per_card);
}
uint G1CardSetConfiguration::num_cards_in_inline_ptr(uint bits_per_card) {
return G1CardSetInlinePtr::max_cards_in_inline_ptr(bits_per_card);
}
G1CardSetAllocOptions* G1CardSetConfiguration::mem_object_alloc_options() {
G1CardSetAllocOptions* result = NEW_C_HEAP_ARRAY(G1CardSetAllocOptions, num_mem_object_types(), mtGC);
result[0] = { (uint)CardSetHash::get_node_size() };
result[1] = { (uint)G1CardSetArray::size_in_bytes(num_cards_in_array()), 2, 256 };
result[2] = { (uint)G1CardSetBitMap::size_in_bytes(num_cards_in_howl_bitmap()), 2, 256 };
result[3] = { (uint)G1CardSetHowl::size_in_bytes(num_buckets_in_howl()), 2, 256 };
return result;
}
const char* G1CardSetConfiguration::mem_object_type_name_str(uint index) {
const char* names[] = { "Node", "Array", "Bitmap", "Howl" };
return names[index];
}
void G1CardSetCoarsenStats::reset() {
STATIC_ASSERT(ARRAY_SIZE(_coarsen_from) == ARRAY_SIZE(_coarsen_collision));
for (uint i = 0; i < ARRAY_SIZE(_coarsen_from); i++) {
_coarsen_from[i] = 0;
_coarsen_collision[i] = 0;
}
}
void G1CardSetCoarsenStats::subtract_from(G1CardSetCoarsenStats& other) {
STATIC_ASSERT(ARRAY_SIZE(_coarsen_from) == ARRAY_SIZE(_coarsen_collision));
for (uint i = 0; i < ARRAY_SIZE(_coarsen_from); i++) {
_coarsen_from[i] = other._coarsen_from[i] - _coarsen_from[i];
_coarsen_collision[i] = other._coarsen_collision[i] - _coarsen_collision[i];
}
}
void G1CardSetCoarsenStats::record_coarsening(uint tag, bool collision) {
assert(tag < ARRAY_SIZE(_coarsen_from), "tag %u out of bounds", tag);
Atomic::inc(&_coarsen_from[tag], memory_order_relaxed);
if (collision) {
Atomic::inc(&_coarsen_collision[tag], memory_order_relaxed);
}
}
void G1CardSetCoarsenStats::print_on(outputStream* out) {
out->print_cr("Inline->AoC %zu (%zu) "
"AoC->Howl %zu (%zu) "
"Howl->Full %zu (%zu) "
"Inline->AoC %zu (%zu) "
"AoC->BitMap %zu (%zu) "
"BitMap->Full %zu (%zu) ",
_coarsen_from[0], _coarsen_collision[0],
_coarsen_from[1], _coarsen_collision[1],
// There is no BitMap at the first level so we can't .
_coarsen_from[3], _coarsen_collision[3],
_coarsen_from[4], _coarsen_collision[4],
_coarsen_from[5], _coarsen_collision[5],
_coarsen_from[6], _coarsen_collision[6]
);
}
class G1CardSetHashTable : public CHeapObj<mtGCCardSet> {
using CardSetPtr = G1CardSet::CardSetPtr;
// Did we insert at least one element in the table?
bool volatile _inserted_elem;
G1CardSetMemoryManager* _mm;
CardSetHash _table;
class G1CardSetHashTableLookUp : public StackObj {
uint _region_idx;
public:
explicit G1CardSetHashTableLookUp(uint region_idx) : _region_idx(region_idx) { }
uintx get_hash() const { return _region_idx; }
bool equals(G1CardSetHashTableValue* value, bool* is_dead) {
*is_dead = false;
return value->_region_idx == _region_idx;
}
};
class G1CardSetHashTableFound : public StackObj {
G1CardSetHashTableValue* _value;
public:
void operator()(G1CardSetHashTableValue* value) {
_value = value;
}
G1CardSetHashTableValue* value() const { return _value; }
};
class G1CardSetHashTableScan : public StackObj {
G1CardSet::G1CardSetPtrIterator* _scan_f;
public:
explicit G1CardSetHashTableScan(G1CardSet::G1CardSetPtrIterator* f) : _scan_f(f) { }
bool operator()(G1CardSetHashTableValue* value) {
_scan_f->do_cardsetptr(value->_region_idx, value->_num_occupied, value->_card_set);
return true;
}
};
public:
static const size_t InitialLogTableSize = 2;
G1CardSetHashTable(G1CardSetMemoryManager* mm,
size_t initial_log_table_size = InitialLogTableSize) :
_inserted_elem(false),
_mm(mm),
_table(mm, initial_log_table_size) {
}
~G1CardSetHashTable() {
reset();
}
G1CardSetHashTableValue* get_or_add(uint region_idx, bool* should_grow) {
G1CardSetHashTableLookUp lookup(region_idx);
G1CardSetHashTableFound found;
if (_table.get(Thread::current(), lookup, found)) {
return found.value();
}
G1CardSetHashTableValue value(region_idx, G1CardSetInlinePtr());
bool inserted = _table.insert_get(Thread::current(), lookup, value, found, should_grow);
if (!_inserted_elem && inserted) {
// It does not matter to us who is setting the flag so a regular atomic store
// is sufficient.
Atomic::store(&_inserted_elem, true);
}
return found.value();
}
CardSetPtr get(uint region_idx) {
G1CardSetHashTableLookUp lookup(region_idx);
G1CardSetHashTableFound found;
if (_table.get(Thread::current(), lookup, found)) {
return found.value()->_card_set;
}
return nullptr;
}
void iterate_safepoint(G1CardSet::G1CardSetPtrIterator* cl2) {
G1CardSetHashTableScan cl(cl2);
_table.do_safepoint_scan(cl);
}
void iterate(G1CardSet::G1CardSetPtrIterator* cl2) {
G1CardSetHashTableScan cl(cl2);
_table.do_scan(Thread::current(), cl);
}
void reset() {
if (Atomic::load(&_inserted_elem)) {
_table.unsafe_reset(InitialLogTableSize);
Atomic::store(&_inserted_elem, false);
}
}
void print(outputStream* os) {
os->print("TBL " PTR_FORMAT " size %zu mem %zu ", p2i(&_table), _table.get_size_log2(Thread::current()), _table.get_mem_size(Thread::current()));
}
void grow() {
size_t new_limit = _table.get_size_log2(Thread::current()) + 1;
_table.grow(Thread::current(), new_limit);
}
size_t mem_size() {
return sizeof(*this) +
_table.get_mem_size(Thread::current()) - sizeof(_table);
}
size_t log_table_size() { return _table.get_size_log2(Thread::current()); }
};
void* G1CardSetHashTableConfig::allocate_node(void* context, size_t size, Value const& value) {
G1CardSetMemoryManager* mm = (G1CardSetMemoryManager*)context;
return mm->allocate_node();
}
void G1CardSetHashTableConfig::free_node(void* context, void* memory, Value const& value) {
G1CardSetMemoryManager* mm = (G1CardSetMemoryManager*)context;
mm->free_node(memory);
}
G1CardSetCoarsenStats G1CardSet::_coarsen_stats;
G1CardSetCoarsenStats G1CardSet::_last_coarsen_stats;
G1CardSet::G1CardSet(G1CardSetConfiguration* config, G1CardSetMemoryManager* mm) :
_mm(mm),
_config(config),
_table(new G1CardSetHashTable(mm)),
_num_occupied(0) {
}
G1CardSet::~G1CardSet() {
delete _table;
_mm->flush();
}
uint G1CardSet::card_set_type_to_mem_object_type(uintptr_t type) const {
assert(type == G1CardSet::CardSetArrayOfCards ||
type == G1CardSet::CardSetBitMap ||
type == G1CardSet::CardSetHowl, "should not allocate card set type %zu", type);
return (uint)type;
}
uint8_t* G1CardSet::allocate_mem_object(uintptr_t type) {
return _mm->allocate(card_set_type_to_mem_object_type(type));
}
void G1CardSet::free_mem_object(CardSetPtr card_set) {
assert(card_set != G1CardSet::FreeCardSet, "should not free Free card set");
assert(card_set != G1CardSet::FullCardSet, "should not free Full card set");
uintptr_t type = card_set_type(card_set);
void* value = strip_card_set_type(card_set);
assert(type == G1CardSet::CardSetArrayOfCards ||
type == G1CardSet::CardSetBitMap ||
type == G1CardSet::CardSetHowl, "should not free card set type %zu", type);
#ifdef ASSERT
if (type == G1CardSet::CardSetArrayOfCards ||
type == G1CardSet::CardSetBitMap ||
type == G1CardSet::CardSetHowl) {
G1CardSetContainer* card_set = (G1CardSetContainer*)value;
assert((card_set->refcount() == 1), "must be");
}
#endif
_mm->free(card_set_type_to_mem_object_type(type), value);
}
G1CardSet::CardSetPtr G1CardSet::acquire_card_set(CardSetPtr volatile* card_set_addr) {
// Update reference counts under RCU critical section to avoid a
// use-after-cleapup bug where we increment a reference count for
// an object whose memory has already been cleaned up and reused.
GlobalCounter::CriticalSection cs(Thread::current());
while (true) {
// Get cardsetptr and increment refcount atomically wrt to memory reuse.
CardSetPtr card_set = Atomic::load_acquire(card_set_addr);
uint cs_type = card_set_type(card_set);
if (card_set == FullCardSet || cs_type == CardSetInlinePtr) {
return card_set;
}
G1CardSetContainer* card_set_on_heap = (G1CardSetContainer*)strip_card_set_type(card_set);
if (card_set_on_heap->try_increment_refcount()) {
assert(card_set_on_heap->refcount() >= 3, "Smallest value is 3");
return card_set;
}
}
}
bool G1CardSet::release_card_set(CardSetPtr card_set) {
uint cs_type = card_set_type(card_set);
if (card_set == FullCardSet || cs_type == CardSetInlinePtr) {
return false;
}
G1CardSetContainer* card_set_on_heap = (G1CardSetContainer*)strip_card_set_type(card_set);
return card_set_on_heap->decrement_refcount() == 1;
}
void G1CardSet::release_and_maybe_free_card_set(CardSetPtr card_set) {
if (release_card_set(card_set)) {
free_mem_object(card_set);
}
}
void G1CardSet::release_and_must_free_card_set(CardSetPtr card_set) {
bool should_free = release_card_set(card_set);
assert(should_free, "should have been the only one having a reference");
free_mem_object(card_set);
}
class G1ReleaseCardsets : public StackObj {
G1CardSet* _card_set;
using CardSetPtr = G1CardSet::CardSetPtr;
void coarsen_to_full(CardSetPtr* card_set_addr) {
while (true) {
CardSetPtr cur_card_set = Atomic::load_acquire(card_set_addr);
uint cs_type = G1CardSet::card_set_type(cur_card_set);
if (cur_card_set == G1CardSet::FullCardSet) {
return;
}
CardSetPtr old_value = Atomic::cmpxchg(card_set_addr, cur_card_set, G1CardSet::FullCardSet);
if (old_value == cur_card_set) {
_card_set->release_and_maybe_free_card_set(cur_card_set);
return;
}
}
}
public:
explicit G1ReleaseCardsets(G1CardSet* card_set) : _card_set(card_set) { }
void operator ()(CardSetPtr* card_set_addr) {
coarsen_to_full(card_set_addr);
}
};
G1AddCardResult G1CardSet::add_to_array(CardSetPtr card_set, uint card_in_region) {
G1CardSetArray* array = card_set_ptr<G1CardSetArray>(card_set);
return array->add(card_in_region);
}
G1AddCardResult G1CardSet::add_to_howl(CardSetPtr parent_card_set,
uint card_region,
uint card_in_region,
bool increment_total) {
G1CardSetHowl* howl = card_set_ptr<G1CardSetHowl>(parent_card_set);
G1AddCardResult add_result;
CardSetPtr to_transfer = nullptr;
CardSetPtr card_set;
uint bucket = _config->howl_bucket_index(card_in_region);
volatile CardSetPtr* bucket_entry = howl->get_card_set_addr(bucket);
while (true) {
if (Atomic::load(&howl->_num_entries) >= _config->cards_in_howl_threshold()) {
return Overflow;
}
card_set = acquire_card_set(bucket_entry);
add_result = add_to_card_set(bucket_entry, card_set, card_region, card_in_region);
if (add_result != Overflow) {
break;
}
// Card set has overflown. Coarsen or retry.
bool coarsened = coarsen_card_set(bucket_entry, card_set, card_in_region, true /* within_howl */);
_coarsen_stats.record_coarsening(card_set_type(card_set) + G1CardSetCoarsenStats::CoarsenHowlOffset, !coarsened);
if (coarsened) {
// We have been the one coarsening this card set (and in the process added that card).
add_result = Added;
to_transfer = card_set;
break;
}
// Somebody else beat us to coarsening. Retry.
release_and_maybe_free_card_set(card_set);
}
if (increment_total && add_result == Added) {
Atomic::inc(&howl->_num_entries, memory_order_relaxed);
}
if (to_transfer != nullptr) {
transfer_cards_in_howl(parent_card_set, to_transfer, card_region);
}
release_and_maybe_free_card_set(card_set);
return add_result;
}
G1AddCardResult G1CardSet::add_to_bitmap(CardSetPtr card_set, uint card_in_region) {
G1CardSetBitMap* bitmap = card_set_ptr<G1CardSetBitMap>(card_set);
uint card_offset = _config->howl_bitmap_offset(card_in_region);
return bitmap->add(card_offset, _config->cards_in_howl_bitmap_threshold(), _config->num_cards_in_howl_bitmap());
}
G1AddCardResult G1CardSet::add_to_inline_ptr(CardSetPtr volatile* card_set_addr, CardSetPtr card_set, uint card_in_region) {
G1CardSetInlinePtr value(card_set_addr, card_set);
return value.add(card_in_region, _config->inline_ptr_bits_per_card(), _config->num_cards_in_inline_ptr());
}
G1CardSet::CardSetPtr G1CardSet::create_coarsened_array_of_cards(uint card_in_region, bool within_howl) {
uint8_t* data = nullptr;
CardSetPtr new_card_set;
if (within_howl) {
uint const size_in_bits = _config->num_cards_in_howl_bitmap();
uint card_offset = _config->howl_bitmap_offset(card_in_region);
data = allocate_mem_object(CardSetBitMap);
new (data) G1CardSetBitMap(card_offset, size_in_bits);
new_card_set = make_card_set_ptr(data, CardSetBitMap);
} else {
data = allocate_mem_object(CardSetHowl);
new (data) G1CardSetHowl(card_in_region, _config);
new_card_set = make_card_set_ptr(data, CardSetHowl);
}
return new_card_set;
}
bool G1CardSet::coarsen_card_set(volatile CardSetPtr* card_set_addr,
CardSetPtr cur_card_set,
uint card_in_region,
bool within_howl) {
CardSetPtr new_card_set = nullptr;
switch (card_set_type(cur_card_set)) {
case CardSetArrayOfCards : {
new_card_set = create_coarsened_array_of_cards(card_in_region, within_howl);
break;
}
case CardSetBitMap: {
new_card_set = FullCardSet;
break;
}
case CardSetInlinePtr: {
uint const size = _config->num_cards_in_array();
uint8_t* data = allocate_mem_object(CardSetArrayOfCards);
new (data) G1CardSetArray(card_in_region, size);
new_card_set = make_card_set_ptr(data, CardSetArrayOfCards);
break;
}
case CardSetHowl: {
new_card_set = FullCardSet; // anything will do at this point.
break;
}
default:
ShouldNotReachHere();
}
CardSetPtr old_value = Atomic::cmpxchg(card_set_addr, cur_card_set, new_card_set); // Memory order?
if (old_value == cur_card_set) {
// Success. Indicate that the cards from the current card set must be transferred
// by this caller.
// Release the hash table reference to the card. The caller still holds the
// reference to this card set, so it can never be released (and we do not need to
// check its result).
bool should_free = release_card_set(cur_card_set);
assert(!should_free, "must have had more than one reference");
// Free containers if cur_card_set is CardSetHowl
if (card_set_type(cur_card_set) == CardSetHowl) {
G1ReleaseCardsets rel(this);
card_set_ptr<G1CardSetHowl>(cur_card_set)->iterate(rel, _config->num_buckets_in_howl());
}
return true;
} else {
// Somebody else beat us to coarsening that card set. Exit, but clean up first.
if (new_card_set != FullCardSet) {
assert(new_card_set != nullptr, "must not be");
release_and_must_free_card_set(new_card_set);
}
return false;
}
}
class G1TransferCard : public StackObj {
G1CardSet* _card_set;
uint _region_idx;
public:
G1TransferCard(G1CardSet* card_set, uint region_idx) : _card_set(card_set), _region_idx(region_idx) { }
void operator ()(uint card_idx) {
_card_set->add_card(_region_idx, card_idx, false);
}
};
void G1CardSet::transfer_cards(G1CardSetHashTableValue* table_entry, CardSetPtr source_card_set, uint card_region) {
assert(source_card_set != FullCardSet, "Should not need to transfer from full");
// Need to transfer old entries unless there is a Full card set in place now, i.e.
// the old type has been CardSetBitMap. "Full" contains all elements anyway.
if (card_set_type(source_card_set) != CardSetHowl) {
G1TransferCard iter(this, card_region);
iterate_cards_during_transfer(source_card_set, iter);
} else {
assert(card_set_type(source_card_set) == CardSetHowl, "must be");
// Need to correct for that the Full remembered set occupies more cards than the
// AoCS before.
Atomic::add(&_num_occupied, _config->max_cards_in_region() - table_entry->_num_occupied, memory_order_relaxed);
}
}
void G1CardSet::transfer_cards_in_howl(CardSetPtr parent_card_set,
CardSetPtr source_card_set,
uint card_region) {
assert(card_set_type(parent_card_set) == CardSetHowl, "must be");
assert(source_card_set != FullCardSet, "Should not need to transfer from full");
// Need to transfer old entries unless there is a Full card set in place now, i.e.
// the old type has been CardSetBitMap.
if (card_set_type(source_card_set) != CardSetBitMap) {
// We only need to transfer from anything below CardSetBitMap.
G1TransferCard iter(this, card_region);
iterate_cards_during_transfer(source_card_set, iter);
} else {
uint diff = _config->num_cards_in_howl_bitmap() - card_set_ptr<G1CardSetBitMap>(source_card_set)->num_bits_set();
// Need to correct for that the Full remembered set occupies more cards than the
// bitmap before.
// We add 1 element less because the values will be incremented
// in G1CardSet::add_card for the current addition or where already incremented in
// G1CardSet::add_to_howl after coarsening.
diff -= 1;
G1CardSetHowl* howling_array = card_set_ptr<G1CardSetHowl>(parent_card_set);
Atomic::add(&howling_array->_num_entries, diff, memory_order_relaxed);
bool should_grow_table = false;
G1CardSetHashTableValue* table_entry = get_or_add_card_set(card_region, &should_grow_table);
Atomic::add(&table_entry->_num_occupied, diff, memory_order_relaxed);
Atomic::add(&_num_occupied, diff, memory_order_relaxed);
}
}
G1AddCardResult G1CardSet::add_to_card_set(volatile CardSetPtr* card_set_addr, CardSetPtr card_set, uint card_region, uint card_in_region, bool increment_total) {
assert(card_set_addr != nullptr, "Cannot add to empty cardset");
G1AddCardResult add_result;
switch (card_set_type(card_set)) {
case CardSetInlinePtr: {
add_result = add_to_inline_ptr(card_set_addr, card_set, card_in_region);
break;
}
case CardSetArrayOfCards : {
add_result = add_to_array(card_set, card_in_region);
break;
}
case CardSetBitMap: {
add_result = add_to_bitmap(card_set, card_in_region);
break;
}
case CardSetHowl: {
assert(CardSetHowl == card_set_type(FullCardSet), "must be");
if (card_set == FullCardSet) {
return Found;
}
add_result = add_to_howl(card_set, card_region, card_in_region, increment_total);
break;
}
default:
ShouldNotReachHere();
}
return add_result;
}
G1CardSetHashTableValue* G1CardSet::get_or_add_card_set(uint card_region, bool* should_grow_table) {
return _table->get_or_add(card_region, should_grow_table);
}
G1CardSet::CardSetPtr G1CardSet::get_card_set(uint card_region) {
return _table->get(card_region);
}
G1AddCardResult G1CardSet::add_card(uint card_region, uint card_in_region, bool increment_total) {
G1AddCardResult add_result;
CardSetPtr to_transfer = nullptr;
CardSetPtr card_set;
bool should_grow_table = false;
G1CardSetHashTableValue* table_entry = get_or_add_card_set(card_region, &should_grow_table);
while (true) {
card_set = acquire_card_set(&table_entry->_card_set);
add_result = add_to_card_set(&table_entry->_card_set, card_set, card_region, card_in_region, increment_total);
if (add_result != Overflow) {
break;
}
// Card set has overflown. Coarsen or retry.
bool coarsened = coarsen_card_set(&table_entry->_card_set, card_set, card_in_region);
_coarsen_stats.record_coarsening(card_set_type(card_set), !coarsened);
if (coarsened) {
// We have been the one coarsening this card set (and in the process added that card).
add_result = Added;
to_transfer = card_set;
break;
}
// Somebody else beat us to coarsening. Retry.
release_and_maybe_free_card_set(card_set);
}
if (increment_total && add_result == Added) {
Atomic::inc(&table_entry->_num_occupied, memory_order_relaxed);
Atomic::inc(&_num_occupied, memory_order_relaxed);
}
if (should_grow_table) {
_table->grow();
}
if (to_transfer != nullptr) {
transfer_cards(table_entry, to_transfer, card_region);
}
release_and_maybe_free_card_set(card_set);
return add_result;
}
bool G1CardSet::contains_card(uint card_region, uint card_in_region) {
assert(card_in_region < _config->max_cards_in_region(),
"Card %u is beyond max %u", card_in_region, _config->max_cards_in_region());
// Protect the card set from reclamation.
GlobalCounter::CriticalSection cs(Thread::current());
CardSetPtr card_set = get_card_set(card_region);
if (card_set == nullptr) {
return false;
} else if (card_set == FullCardSet) {
// contains_card() is not a performance critical method so we do not hide that
// case in the switch below.
return true;
}
switch (card_set_type(card_set)) {
case CardSetInlinePtr: {
G1CardSetInlinePtr ptr(card_set);
return ptr.contains(card_in_region, _config->inline_ptr_bits_per_card());
}
case CardSetArrayOfCards : return card_set_ptr<G1CardSetArray>(card_set)->contains(card_in_region);
case CardSetBitMap: return card_set_ptr<G1CardSetBitMap>(card_set)->contains(card_in_region, _config->num_cards_in_howl_bitmap());
case CardSetHowl: {
G1CardSetHowl* howling_array = card_set_ptr<G1CardSetHowl>(card_set);
return howling_array->contains(card_in_region, _config);
}
}
ShouldNotReachHere();
return false;
}
void G1CardSet::print_info(outputStream* st, uint card_region, uint card_in_region) {
CardSetPtr card_set = get_card_set(card_region);
if (card_set == nullptr) {
st->print("NULL card set");
return;
} else if (card_set == FullCardSet) {
st->print("FULL card set)");
return;
}
switch (card_set_type(card_set)) {
case CardSetInlinePtr: {
st->print("InlinePtr not containing %u", card_in_region);
break;
}
case CardSetArrayOfCards : {
st->print("AoC not containing %u", card_in_region);
break;
}
case CardSetBitMap: {
st->print("BitMap not containing %u", card_in_region);
break;
}
case CardSetHowl: {
st->print("CardSetHowl not containing %u", card_in_region);
break;
}
default: st->print("Unknown card set type %u", card_set_type(card_set)); ShouldNotReachHere(); break;
}
}
template <class CardVisitor>
void G1CardSet::iterate_cards_during_transfer(CardSetPtr const card_set, CardVisitor& found) {
uint type = card_set_type(card_set);
assert(type == CardSetInlinePtr || type == CardSetArrayOfCards,
"invalid card set type %d to transfer from",
card_set_type(card_set));
switch (type) {
case CardSetInlinePtr: {
G1CardSetInlinePtr ptr(card_set);
ptr.iterate(found, _config->inline_ptr_bits_per_card());
return;
}
case CardSetArrayOfCards : {
card_set_ptr<G1CardSetArray>(card_set)->iterate(found);
return;
}
default:
ShouldNotReachHere();
}
}
void G1CardSet::iterate_containers(G1CardSetPtrIterator* found, bool at_safepoint) {
if (at_safepoint) {
_table->iterate_safepoint(found);
} else {
_table->iterate(found);
}
}
template <typename Closure>
class G1ContainerCards {
Closure& _iter;
uint _region_idx;
public:
G1ContainerCards(Closure& iter, uint region_idx) : _iter(iter), _region_idx(region_idx) { }
bool start_iterate(uint tag) { return true; }
void operator()(uint card_idx) {
_iter.do_card(_region_idx, card_idx);
}
void operator()(uint card_idx, uint length) {
for (uint i = 0; i < length; i++) {
_iter.do_card(_region_idx, card_idx);
}
}
};
void G1CardSet::iterate_cards(G1CardSetCardIterator& iter) {
G1CardSetMergeCardIterator<G1CardSetCardIterator, G1ContainerCards> cl(this, iter);
iterate_containers(&cl);
}
bool G1CardSet::occupancy_less_or_equal_to(size_t limit) const {
return occupied() <= limit;
}
bool G1CardSet::is_empty() const {
return _num_occupied == 0;
}
size_t G1CardSet::occupied() const {
return _num_occupied;
}
size_t G1CardSet::num_containers() {
class GetNumberOfContainers : public G1CardSetPtrIterator {
public:
size_t _count;
GetNumberOfContainers() : G1CardSetPtrIterator(), _count(0) { }
void do_cardsetptr(uint region_idx, size_t num_occupied, CardSetPtr card_set) override {
_count++;
}
} cl;
iterate_containers(&cl);
return cl._count;
}
G1CardSetCoarsenStats G1CardSet::coarsen_stats() {
return _coarsen_stats;
}
void G1CardSet::print_coarsen_stats(outputStream* out) {
_last_coarsen_stats.subtract_from(_coarsen_stats);
out->print("Coarsening (recent): ");
_last_coarsen_stats.print_on(out);
out->print("Coarsening (all): ");
_coarsen_stats.print_on(out);
}
size_t G1CardSet::mem_size() const {
return sizeof(*this) +
_table->mem_size() +
_mm->mem_size();
}
size_t G1CardSet::wasted_mem_size() const {
return _mm->wasted_mem_size();
}
size_t G1CardSet::static_mem_size() {
return sizeof(FullCardSet) + sizeof(_coarsen_stats);
}
void G1CardSet::clear() {
_table->reset();
_num_occupied = 0;
_mm->flush();
}
void G1CardSet::print(outputStream* os) {
_table->print(os);
_mm->print(os);
}

View File

@ -0,0 +1,376 @@
/*
* Copyright (c) 2021, 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.
*
*/
#ifndef SHARE_GC_G1_G1CARDSET_HPP
#define SHARE_GC_G1_G1CARDSET_HPP
#include "memory/allocation.hpp"
#include "memory/padded.hpp"
#include "oops/oopsHierarchy.hpp"
#include "utilities/concurrentHashTable.hpp"
#include "utilities/lockFreeStack.hpp"
class G1CardSetAllocOptions;
class G1CardSetBufferList;
class G1CardSetHashTable;
class G1CardSetHashTableValue;
class G1CardSetMemoryManager;
class Mutex;
// The result of an attempt to add a card to that card set.
enum G1AddCardResult {
Overflow, // The card set is more than full. The entry may have been added. Need
// Coarsen and retry.
Found, // The card is already in the set.
Added // The card has been added to the set by this attempt.
};
class G1CardSetConfiguration {
uint _inline_ptr_bits_per_card;
uint _num_cards_in_array;
uint _num_cards_in_howl_bitmap;
uint _num_buckets_in_howl;
uint _max_cards_in_card_set;
uint _cards_in_howl_threshold;
uint _cards_in_howl_bitmap_threshold;
uint _log2_num_cards_in_howl_bitmap;
size_t _bitmap_hash_mask;
void log_configuration();
public:
// Initialize card set configuration from globals.
G1CardSetConfiguration();
// Initialize card set configuration from parameters.
G1CardSetConfiguration(uint inline_ptr_bits_per_card,
uint num_cards_in_array,
double cards_in_bitmap_threshold,
uint max_buckets_in_howl,
double cards_in_howl_threshold,
uint max_cards_in_cardset);
// Inline pointer configuration
uint inline_ptr_bits_per_card() const { return _inline_ptr_bits_per_card; }
uint num_cards_in_inline_ptr() const;
static uint num_cards_in_inline_ptr(uint bits_per_card);
// Array of Cards configuration
bool use_cards_in_array() const { return _num_cards_in_array != 0; } // Unused for now
// Number of cards in "Array of Cards" set; 0 to disable.
// Always coarsen to next level if full, so no specific threshold.
uint num_cards_in_array() const { return _num_cards_in_array; }
// Bitmap within Howl card set container configuration
bool use_cards_in_howl_bitmap() const { return _num_cards_in_howl_bitmap != 0; } // Unused for now
uint num_cards_in_howl_bitmap() const { return _num_cards_in_howl_bitmap; }
// (Approximate) Number of cards in bitmap to coarsen Howl Bitmap to Howl Full.
uint cards_in_howl_bitmap_threshold() const { return _cards_in_howl_bitmap_threshold; }
uint log2_num_cards_in_howl_bitmap() const {return _log2_num_cards_in_howl_bitmap;}
// Howl card set container configuration
uint num_buckets_in_howl() const { return _num_buckets_in_howl; }
// Threshold at which to turn howling arrays into Full.
uint cards_in_howl_threshold() const { return _cards_in_howl_threshold; }
uint howl_bitmap_offset(uint card_idx) const { return card_idx & _bitmap_hash_mask; }
// Given a card index, return the bucket in the array of card sets.
uint howl_bucket_index(uint card_idx) { return card_idx >> _log2_num_cards_in_howl_bitmap; }
// Full card configuration
// Maximum number of cards in a non-full card set for a single region. Card sets
// with more entries per region are coarsened to Full.
uint max_cards_in_region() const { return _max_cards_in_card_set; }
// Memory object types configuration
// Number of distinctly sized memory objects on the card set heap.
// Currently contains CHT-Nodes, ArrayOfCards, BitMaps, Howl
static constexpr uint num_mem_object_types() { return 4; }
// Returns the memory allocation options for the memory objects on the card set heap. The returned
// array must be freed by the caller.
G1CardSetAllocOptions* mem_object_alloc_options();
// For a given memory object, get a descriptive name.
static const char* mem_object_type_name_str(uint index);
};
// Collects coarsening statistics: how many attempts of each kind and how many
// failed due to a competing thread doing the coarsening first.
class G1CardSetCoarsenStats {
public:
// Number of entries in the statistics tables: since we index with the source
// cardset of the coarsening, this is the total number of combinations of
// card sets - 1.
static constexpr size_t NumCoarsenCategories = 7;
// Coarsening statistics for the possible CardSetPtr in the Howl card set
// start from this offset.
static constexpr size_t CoarsenHowlOffset = 4;
private:
// Indices are "from" indices.
size_t _coarsen_from[NumCoarsenCategories];
size_t _coarsen_collision[NumCoarsenCategories];
public:
G1CardSetCoarsenStats() { reset(); }
void reset();
void subtract_from(G1CardSetCoarsenStats& other);
// Record a coarsening for the given tag/category. Collision should be true if
// this coarsening lost the race to do the coarsening of that category.
void record_coarsening(uint tag, bool collision);
void print_on(outputStream* out);
};
// Sparse set of card indexes comprising a remembered set on the Java heap. Card
// size is assumed to be card table card size.
//
// Technically it is implemented using a ConcurrentHashTable that stores a card
// set container for every region containing at least one card.
//
// There are in total five different containers, encoded in the ConcurrentHashTable
// node as CardSetPtr. A CardSetPtr may cover the whole region or just a part of
// it.
// See its description below for more information.
class G1CardSet : public CHeapObj<mtGCCardSet> {
friend class G1CardSetTest;
friend class G1CardSetMtTestTask;
template <typename Closure, template <typename> class CardorRanges>
friend class G1CardSetMergeCardIterator;
friend class G1TransferCard;
friend class G1ReleaseCardsets;
static G1CardSetCoarsenStats _coarsen_stats; // Coarsening statistics since VM start.
static G1CardSetCoarsenStats _last_coarsen_stats; // Coarsening statistics at last GC.
public:
// Two lower bits are used to encode the card storage types
static const uintptr_t CardSetPtrHeaderSize = 2;
// CardSetPtr represents the card storage type of a given covered area. It encodes
// a type in the LSBs, in addition to having a few significant values.
//
// Possible encodings:
//
// 0...00000 free (Empty, should never happen)
// 1...11111 full All card indexes in the whole area this CardSetPtr covers are part of this container.
// X...XXX00 inline-ptr-cards A handful of card indexes covered by this CardSetPtr are encoded within the CardSetPtr.
// X...XXX01 array of cards The container is a contiguous array of card indexes.
// X...XXX10 bitmap The container uses a bitmap to determine whether a given index is part of this set.
// X...XXX11 howl This is a card set container containing an array of CardSetPtr, with each CardSetPtr
// limited to a sub-range of the original range. Currently only one level of this
// container is supported.
typedef void* CardSetPtr;
// Coarsening happens in the order below:
// CardSetInlinePtr -> CardSetArrayOfCards -> CardSetHowl -> Full
// Corsening of containers inside the CardSetHowl happens in the order:
// CardSetInlinePtr -> CardSetArrayOfCards -> CardSetBitMap -> Full
static const uintptr_t CardSetInlinePtr = 0x0;
static const uintptr_t CardSetArrayOfCards = 0x1;
static const uintptr_t CardSetBitMap = 0x2;
static const uintptr_t CardSetHowl = 0x3;
// The special sentinel values
static constexpr CardSetPtr FreeCardSet = nullptr;
// Unfortunately we can't make (G1CardSet::CardSetPtr)-1 constexpr because
// reinterpret_casts are forbidden in constexprs. Use a regular static instead.
static CardSetPtr FullCardSet;
static const uintptr_t CardSetPtrTypeMask = ((uintptr_t)1 << CardSetPtrHeaderSize) - 1;
static CardSetPtr strip_card_set_type(CardSetPtr ptr) { return (CardSetPtr)((uintptr_t)ptr & ~CardSetPtrTypeMask); }
static uint card_set_type(CardSetPtr ptr) { return (uintptr_t)ptr & CardSetPtrTypeMask; }
template <class T>
static T* card_set_ptr(CardSetPtr ptr);
private:
G1CardSetMemoryManager* _mm;
G1CardSetConfiguration* _config;
G1CardSetHashTable* _table;
// Total number of cards in this card set. This is a best-effort value, i.e. there may
// be (slightly) more cards in the card set than this value in reality.
size_t _num_occupied;
CardSetPtr make_card_set_ptr(void* value, uintptr_t type);
CardSetPtr acquire_card_set(CardSetPtr volatile* card_set_addr);
// Returns true if the card set should be released
bool release_card_set(CardSetPtr card_set);
// Release card set and free if needed.
void release_and_maybe_free_card_set(CardSetPtr card_set);
// Release card set and free (and it must be freeable).
void release_and_must_free_card_set(CardSetPtr card_set);
// Coarsens the CardSet cur_card_set to the next level; tries to replace the
// previous CardSet with a new one which includes the given card_in_region.
// coarsen_card_set does not transfer cards from cur_card_set
// to the new card_set. Transfer is achieved by transfer_cards.
// Returns true if this was the thread that coarsened the CardSet (and added the card).
bool coarsen_card_set(CardSetPtr volatile* card_set_addr,
CardSetPtr cur_card_set,
uint card_in_region, bool within_howl = false);
CardSetPtr create_coarsened_array_of_cards(uint card_in_region, bool within_howl);
// Transfer entries from source_card_set to a recently installed coarser storage type
// We only need to transfer anything finer than CardSetBitMap. "Full" contains
// all elements anyway.
void transfer_cards(G1CardSetHashTableValue* table_entry, CardSetPtr source_card_set, uint card_region);
void transfer_cards_in_howl(CardSetPtr parent_card_set, CardSetPtr source_card_set, uint card_region);
G1AddCardResult add_to_card_set(CardSetPtr volatile* card_set_addr, CardSetPtr card_set, uint card_region, uint card, bool increment_total = true);
G1AddCardResult add_to_inline_ptr(CardSetPtr volatile* card_set_addr, CardSetPtr card_set, uint card_in_region);
G1AddCardResult add_to_array(CardSetPtr card_set, uint card_in_region);
G1AddCardResult add_to_bitmap(CardSetPtr card_set, uint card_in_region);
G1AddCardResult add_to_howl(CardSetPtr parent_card_set, uint card_region, uint card_in_region, bool increment_total = true);
G1CardSetHashTableValue* get_or_add_card_set(uint card_region, bool* should_grow_table);
CardSetPtr get_card_set(uint card_region);
// Iterate over cards of a card set container during transfer of the cards from
// one container to another. Executes
//
// void operator ()(uint card_idx)
//
// on the given class.
template <class CardVisitor>
void iterate_cards_during_transfer(CardSetPtr const card_set, CardVisitor& found);
// Iterate over the container, calling a method on every card or card range contained
// in the card container.
// For every container, first calls
//
// void start_iterate(uint tag, uint region_idx);
//
// Then for every card or card range it calls
//
// void do_card(uint card_idx);
// void do_card_range(uint card_idx, uint length);
//
// where card_idx is the card index within that region_idx passed before in
// start_iterate().
//
template <class CardOrRangeVisitor>
void iterate_cards_or_ranges_in_container(CardSetPtr const card_set, CardOrRangeVisitor& found);
uint card_set_type_to_mem_object_type(uintptr_t type) const;
uint8_t* allocate_mem_object(uintptr_t type);
void free_mem_object(CardSetPtr card_set);
public:
G1CardSetConfiguration* config() const { return _config; }
// Create a new remembered set for a particular heap region.
G1CardSet(G1CardSetConfiguration* config, G1CardSetMemoryManager* mm);
virtual ~G1CardSet();
// Adds the given card to this set, returning an appropriate result. If added,
// updates the total count.
G1AddCardResult add_card(uint card_region, uint card_in_region, bool increment_total = true);
bool contains_card(uint card_region, uint card_in_region);
void print_info(outputStream* st, uint card_region, uint card_in_region);
// Returns whether this remembered set (and all sub-sets) have an occupancy
// that is less or equal to the given occupancy.
bool occupancy_less_or_equal_to(size_t limit) const;
// Returns whether this remembered set (and all sub-sets) does not contain any entry.
bool is_empty() const;
// Returns the number of cards contained in this remembered set.
size_t occupied() const;
size_t num_containers();
static G1CardSetCoarsenStats coarsen_stats();
static void print_coarsen_stats(outputStream* out);
// Returns size of the actual remembered set containers in bytes.
size_t mem_size() const;
size_t wasted_mem_size() const;
// Returns the size of static data in bytes.
static size_t static_mem_size();
// Clear the entire contents of this remembered set.
void clear();
void print(outputStream* os);
// Various iterators - should be made inlineable somehow.
class G1CardSetPtrIterator {
public:
virtual void do_cardsetptr(uint region_idx, size_t num_occupied, CardSetPtr card_set) = 0;
};
void iterate_containers(G1CardSetPtrIterator* iter, bool safepoint = false);
class G1CardSetCardIterator {
public:
virtual void do_card(uint region_idx, uint card_idx) = 0;
};
void iterate_cards(G1CardSetCardIterator& iter);
// Iterate all cards for card set merging. Must be a CardOrRangeVisitor as
// explained above.
template <class CardOrRangeVisitor>
void iterate_for_merge(CardOrRangeVisitor& cl);
};
class G1CardSetHashTableValue {
public:
using CardSetPtr = G1CardSet::CardSetPtr;
const uint _region_idx;
uint volatile _num_occupied;
CardSetPtr volatile _card_set;
G1CardSetHashTableValue(uint region_idx, CardSetPtr card_set) : _region_idx(region_idx), _num_occupied(0), _card_set(card_set) { }
};
class G1CardSetHashTableConfig : public StackObj {
public:
using Value = G1CardSetHashTableValue;
static uintx get_hash(Value const& value, bool* is_dead) {
*is_dead = false;
return value._region_idx;
}
static void* allocate_node(void* context, size_t size, Value const& value);
static void free_node(void* context, void* memory, Value const& value);
};
typedef ConcurrentHashTable<G1CardSetHashTableConfig, mtGCCardSet> CardSetHash;
#endif // SHARE_GC_G1_G1CARDSET_HPP

View File

@ -0,0 +1,125 @@
/*
* Copyright (c) 2021, 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.
*
*/
#ifndef SHARE_GC_G1_G1CARDSET_INLINE_HPP
#define SHARE_GC_G1_G1CARDSET_INLINE_HPP
#include "gc/g1/g1CardSet.hpp"
#include "gc/g1/g1CardSetContainers.inline.hpp"
#include "gc/g1/g1GCPhaseTimes.hpp"
#include "runtime/atomic.hpp"
#include "logging/log.hpp"
template <class T>
inline T* G1CardSet::card_set_ptr(CardSetPtr ptr) {
return (T*)strip_card_set_type(ptr);
}
inline G1CardSet::CardSetPtr G1CardSet::make_card_set_ptr(void* value, uintptr_t type) {
assert(card_set_type(value) == 0, "Given ptr " PTR_FORMAT " already has type bits set", p2i(value));
return (CardSetPtr)((uintptr_t)value | type);
}
template <class CardOrRangeVisitor>
inline void G1CardSet::iterate_cards_or_ranges_in_container(CardSetPtr const card_set, CardOrRangeVisitor& found) {
switch (card_set_type(card_set)) {
case CardSetInlinePtr: {
if (found.start_iterate(G1GCPhaseTimes::MergeRSMergedInline)) {
G1CardSetInlinePtr ptr(card_set);
ptr.iterate(found, _config->inline_ptr_bits_per_card());
}
return;
}
case CardSetArrayOfCards : {
if (found.start_iterate(G1GCPhaseTimes::MergeRSMergedArrayOfCards)) {
card_set_ptr<G1CardSetArray>(card_set)->iterate(found);
}
return;
}
case CardSetBitMap: {
// There is no first-level bitmap spanning the whole area.
ShouldNotReachHere();
return;
}
case CardSetHowl: {
assert(card_set_type(FullCardSet) == CardSetHowl, "Must be");
if (card_set == FullCardSet) {
if (found.start_iterate(G1GCPhaseTimes::MergeRSMergedFull)) {
found(0, _config->max_cards_in_region());
}
return;
}
if (found.start_iterate(G1GCPhaseTimes::MergeRSMergedHowl)) {
card_set_ptr<G1CardSetHowl>(card_set)->iterate(found, _config);
}
return;
}
}
log_error(gc)("Unkown card set type %u", card_set_type(card_set));
ShouldNotReachHere();
}
template <typename Closure>
class G1ContainerCardsOrRanges {
Closure& _iter;
uint _region_idx;
public:
G1ContainerCardsOrRanges(Closure& iter, uint region_idx) : _iter(iter), _region_idx(region_idx) { }
bool start_iterate(uint tag) {
return _iter.start_iterate(tag, _region_idx);
}
void operator()(uint card_idx) {
_iter.do_card(card_idx);
}
void operator()(uint card_idx, uint length) {
_iter.do_card_range(card_idx, length);
}
};
template <typename Closure, template <typename> class CardOrRanges>
class G1CardSetMergeCardIterator : public G1CardSet::G1CardSetPtrIterator {
G1CardSet* _card_set;
Closure& _iter;
public:
G1CardSetMergeCardIterator(G1CardSet* card_set, Closure& iter) : _card_set(card_set), _iter(iter) { }
void do_cardsetptr(uint region_idx, size_t num_occupied, G1CardSet::CardSetPtr card_set) override {
CardOrRanges<Closure> cl(_iter, region_idx);
_card_set->iterate_cards_or_ranges_in_container(card_set, cl);
}
};
template <class CardOrRangeVisitor>
inline void G1CardSet::iterate_for_merge(CardOrRangeVisitor& cl) {
G1CardSetMergeCardIterator<CardOrRangeVisitor, G1ContainerCardsOrRanges> cl2(this, cl);
iterate_containers(&cl2, true /* at_safepoint */);
}
#endif // SHARE_GC_G1_G1CARDSET_INLINE_HPP

View File

@ -0,0 +1,294 @@
/*
* Copyright (c) 2021, 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.
*
*/
#ifndef SHARE_GC_G1_G1CARDSETCONTAINERS_HPP
#define SHARE_GC_G1_G1CARDSETCONTAINERS_HPP
#include "gc/g1/g1CardSet.hpp"
#include "memory/allocation.hpp"
#include "runtime/atomic.hpp"
#include "utilities/bitMap.inline.hpp"
#include "utilities/globalDefinitions.hpp"
#include "utilities/spinYield.hpp"
#include "logging/log.hpp"
#include "runtime/thread.inline.hpp"
class G1CardSetInlinePtr : public StackObj {
friend class G1CardSetContainersTest;
typedef G1CardSet::CardSetPtr CardSetPtr;
CardSetPtr volatile * _value_addr;
CardSetPtr _value;
static const uint SizeFieldLen = 3;
static const uint SizeFieldPos = 2;
static const uint HeaderSize = G1CardSet::CardSetPtrHeaderSize + SizeFieldLen;
static const uint BitsInValue = sizeof(CardSetPtr) * BitsPerByte;
static const uintptr_t SizeFieldMask = (((uint)1 << SizeFieldLen) - 1) << SizeFieldPos;
static uint8_t card_pos_for(uint const idx, uint const bits_per_card) {
return (idx * bits_per_card + HeaderSize);
}
static CardSetPtr merge(CardSetPtr orig_value, uint card_in_region, uint idx, uint bits_per_card);
static uint card_at(CardSetPtr value, uint const idx, uint const bits_per_card) {
uint8_t card_pos = card_pos_for(idx, bits_per_card);
uint result = ((uintptr_t)value >> card_pos) & (((uintptr_t)1 << bits_per_card) - 1);
return result;
}
public:
G1CardSetInlinePtr() : _value_addr(nullptr), _value((CardSetPtr)G1CardSet::CardSetInlinePtr) { }
G1CardSetInlinePtr(CardSetPtr value) : _value_addr(nullptr), _value(value) {
assert(((uintptr_t)_value & G1CardSet::CardSetInlinePtr) == G1CardSet::CardSetInlinePtr, "Value " PTR_FORMAT " is not a valid G1CardSetInPtr.", p2i(_value));
}
G1CardSetInlinePtr(CardSetPtr volatile* value_addr, CardSetPtr value) : _value_addr(value_addr), _value(value) {
assert(((uintptr_t)_value & G1CardSet::CardSetInlinePtr) == G1CardSet::CardSetInlinePtr, "Value " PTR_FORMAT " is not a valid G1CardSetInPtr.", p2i(_value));
}
G1AddCardResult add(uint const card_idx, uint const bits_per_card, uint const max_cards_in_inline_ptr);
bool contains(uint const card_idx, uint const bits_per_card);
template <class CardVisitor>
void iterate(CardVisitor& found, uint const bits_per_card);
operator CardSetPtr () { return _value; }
static uint max_cards_in_inline_ptr(uint bits_per_card) {
return (BitsInValue - HeaderSize) / bits_per_card;
}
static uint num_cards_in(CardSetPtr value) {
return ((uintptr_t)value & SizeFieldMask) >> SizeFieldPos;
}
};
// Common base class for card set containers where the memory for the entries is
// managed on the (C-)heap. Depending on the current use, one of the two overlapping
// members are used:
//
// While such an object is assigned to a card set container, we utilize the
// reference count for memory management.
//
// In this case the object is one of three states:
// 1: Live: The object is visible to other threads, thus can
// safely be accessed by other threads (_ref_count >= 3).
// 2: Dead: The object is visible to only a single thread and may be
// safely reclaimed (_ref_count == 1).
// 3: Reclaimed: The object's memory has been reclaimed ((_ref_count & 0x1) == 0).
// To maintain these constraints, live objects should have ((_ref_count & 0x1) == 1),
// which requires that we increment the reference counts by 2 starting at _ref_count = 3.
//
// When such an object is on a free list, we reuse the same field for linking
// together those free objects.
//
// All but inline pointers are of this kind. For those, card entries are stored
// directly in the CardSetPtr of the ConcurrentHashTable node.
class G1CardSetContainer {
private:
union {
G1CardSetContainer* _next;
uintptr_t _ref_count;
};
public:
G1CardSetContainer() : _ref_count(3) { }
uintptr_t refcount() const { return Atomic::load_acquire(&_ref_count); }
bool try_increment_refcount();
// Decrement refcount potentially while racing increment, so we need
// to check the value after attempting to decrement.
uintptr_t decrement_refcount();
G1CardSetContainer* next() {
return _next;
}
G1CardSetContainer** next_addr() {
return &_next;
}
void set_next(G1CardSetContainer* next) {
_next = next;
}
};
class G1CardSetArray : public G1CardSetContainer {
public:
typedef uint16_t EntryDataType;
typedef uint EntryCountType;
using CardSetPtr = G1CardSet::CardSetPtr;
private:
EntryCountType _size;
EntryCountType volatile _num_entries;
EntryDataType _data[2];
static const EntryCountType LockBitMask = (EntryCountType)1 << (sizeof(EntryCountType) * BitsPerByte - 1);
static const EntryCountType EntryMask = LockBitMask - 1;
class G1CardSetArrayLocker : public StackObj {
EntryCountType volatile* _value;
EntryCountType volatile _original_value;
bool _success;
public:
G1CardSetArrayLocker(EntryCountType volatile* value);
EntryCountType num_entries() const { return _original_value; }
void inc_num_entries() { _success = true; }
~G1CardSetArrayLocker() {
assert(((_original_value + _success) & EntryMask) == (EntryCountType)(_original_value + _success), "precondition!" );
Atomic::release_store(_value, (EntryCountType)(_original_value + _success));
}
};
template<typename Derived>
static size_t header_size_in_bytes_internal() {
return offset_of(Derived, _data);
}
public:
G1CardSetArray(uint const card_in_region, EntryCountType num_elems);
G1AddCardResult add(uint card_idx);
bool contains(uint card_idx);
template <class CardVisitor>
void iterate(CardVisitor& found);
size_t num_entries() const { return _num_entries & EntryMask; }
size_t max_entries() const { return _size; }
static size_t header_size_in_bytes() { return header_size_in_bytes_internal<G1CardSetArray>(); }
static size_t size_in_bytes(size_t num_cards) {
return header_size_in_bytes() + sizeof(EntryDataType) * num_cards;
}
};
class G1CardSetBitMap : public G1CardSetContainer {
size_t _num_bits_set;
BitMap::bm_word_t _bits[1];
using CardSetPtr = G1CardSet::CardSetPtr;
template<typename Derived>
static size_t header_size_in_bytes_internal() {
return offset_of(Derived, _bits);
}
public:
G1CardSetBitMap(uint const card_in_region, uint const size_in_bits);
G1AddCardResult add(uint card_idx, size_t threshold, size_t size_in_bits);
bool contains(uint card_idx, size_t size_in_bits) {
BitMapView bm(_bits, size_in_bits);
return bm.at(card_idx);
}
uint num_bits_set() const { return (uint)_num_bits_set; }
template <class CardVisitor>
void iterate(CardVisitor& found, size_t const size_in_bits, uint offset);
uint next(uint const idx, size_t const size_in_bits) {
BitMapView bm(_bits, size_in_bits);
return static_cast<uint>(bm.get_next_one_offset(idx));
}
static size_t header_size_in_bytes() { return header_size_in_bytes_internal<G1CardSetBitMap>(); }
static size_t size_in_bytes(size_t size_in_bits) { return header_size_in_bytes() + BitMap::calc_size_in_words(size_in_bits) * BytesPerWord; }
};
class G1CardSetHowl : public G1CardSetContainer {
public:
typedef uint EntryCountType;
using CardSetPtr = G1CardSet::CardSetPtr;
EntryCountType volatile _num_entries;
private:
CardSetPtr _buckets[2];
// Do not add class member variables beyond this point
template<typename Derived>
static size_t header_size_in_bytes_internal() {
return offset_of(Derived, _buckets);
}
// Iterates over the given CardSetPtr with at index in this Howl card set,
// applying a CardOrRangeVisitor on it.
template <class CardOrRangeVisitor>
void iterate_cardset(CardSetPtr const card_set, uint index, CardOrRangeVisitor& found, G1CardSetConfiguration* config);
public:
G1CardSetHowl(EntryCountType card_in_region, G1CardSetConfiguration* config);
CardSetPtr* get_card_set_addr(EntryCountType index) {
return &_buckets[index];
}
bool contains(uint card_idx, G1CardSetConfiguration* config);
// Iterates over all CardSetPtrs in this Howl card set, applying a CardOrRangeVisitor
// on it.
template <class CardOrRangeVisitor>
void iterate(CardOrRangeVisitor& found, G1CardSetConfiguration* config);
// Iterates over all CardSetPtrs in this Howl card set. Calls
//
// void operator ()(CardSetPtr* card_set_addr);
//
// on all of them.
template <class CardSetPtrVisitor>
void iterate(CardSetPtrVisitor& found, uint num_card_sets);
static EntryCountType num_buckets(size_t size_in_bits, size_t num_cards_in_array, size_t max_buckets);
static EntryCountType bitmap_size(size_t size_in_bits, uint num_buckets) {
EntryCountType num_cards = (EntryCountType)size_in_bits / num_buckets;
return round_up_power_of_2(num_cards);
}
static size_t header_size_in_bytes() { return header_size_in_bytes_internal<G1CardSetHowl>(); }
static size_t size_in_bytes(size_t num_arrays) {
return header_size_in_bytes() + sizeof(CardSetPtr) * num_arrays;
}
};
#endif // SHARE_GC_G1_G1CARDSETCONTAINERS_HPP

View File

@ -0,0 +1,341 @@
/*
* Copyright (c) 2021, 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.
*
*/
#ifndef SHARE_GC_G1_G1CARDSETCONTAINERS_INLINE_HPP
#define SHARE_GC_G1_G1CARDSETCONTAINERS_INLINE_HPP
#include "gc/g1/g1CardSetContainers.hpp"
#include "gc/g1/g1GCPhaseTimes.hpp"
inline G1CardSetInlinePtr::CardSetPtr G1CardSetInlinePtr::merge(CardSetPtr orig_value, uint card_in_region, uint idx, uint bits_per_card) {
assert((idx & (SizeFieldMask >> SizeFieldPos)) == idx, "Index %u too large to fit into size field", idx);
assert(card_in_region < ((uint)1 << bits_per_card), "Card %u too large to fit into card value field", card_in_region);
uint8_t card_pos = card_pos_for(idx, bits_per_card);
assert(card_pos + bits_per_card < BitsInValue, "Putting card at pos %u with %u bits would extend beyond pointer", card_pos, bits_per_card);
// Check that we do not touch any fields we do not own.
uintptr_t mask = ((((uintptr_t)1 << bits_per_card) - 1) << card_pos);
assert(((uintptr_t)orig_value & mask) == 0, "The bits in the new range should be empty; orig_value " PTR_FORMAT " mask " PTR_FORMAT, p2i(orig_value), mask);
uintptr_t value = ((uintptr_t)(idx + 1) << SizeFieldPos) | ((uintptr_t)card_in_region << card_pos);
uintptr_t res = (((uintptr_t)orig_value & ~SizeFieldMask) | value);
return (CardSetPtr)res;
}
inline G1AddCardResult G1CardSetInlinePtr::add(uint card_idx, uint bits_per_card, uint max_cards_in_inline_ptr) {
assert(_value_addr != nullptr, "No value address available, cannot add to set.");
while (true) {
uint num_elems = num_cards_in(_value);
// Check if the card is already stored in the pointer.
if (contains(card_idx, bits_per_card)) {
return Found;
}
// Check if there is actually enough space.
if (num_elems >= max_cards_in_inline_ptr) {
return Overflow;
}
CardSetPtr new_value = merge(_value, card_idx, num_elems, bits_per_card);
CardSetPtr old_value = Atomic::cmpxchg(_value_addr, _value, new_value, memory_order_relaxed);
if (_value == old_value) {
return Added;
}
// Update values and retry.
_value = old_value;
// The value of the pointer may have changed to something different than
// an inline card set. Exit then instead of overwriting.
if (G1CardSet::card_set_type(_value) != G1CardSet::CardSetInlinePtr) {
return Overflow;
}
}
}
inline bool G1CardSetInlinePtr::contains(uint card_idx, uint bits_per_card) {
uint num_elems = num_cards_in(_value);
uintptr_t const card_mask = (1 << bits_per_card) - 1;
uintptr_t value = ((uintptr_t)_value) >> card_pos_for(0, bits_per_card);
// Check if the card is already stored in the pointer.
for (uint cur_idx = 0; cur_idx < num_elems; cur_idx++) {
if ((value & card_mask) == card_idx) {
return true;
}
value >>= bits_per_card;
}
return false;
}
template <class CardVisitor>
inline void G1CardSetInlinePtr::iterate(CardVisitor& found, uint bits_per_card) {
uint const num_elems = num_cards_in(_value);
uintptr_t const card_mask = (1 << bits_per_card) - 1;
uintptr_t value = ((uintptr_t)_value) >> card_pos_for(0, bits_per_card);
for (uint cur_idx = 0; cur_idx < num_elems; cur_idx++) {
found(value & card_mask);
value >>= bits_per_card;
}
}
inline bool G1CardSetContainer::try_increment_refcount() {
uintptr_t old_value = refcount();
while (true) {
if (old_value < 3 || (old_value & 0x1) == 0) { // reclaimed, reference counts are odd numbers starting at 3
return false; // dead, can't revive.
}
uintptr_t new_value = old_value + 2;
uintptr_t ref_count = Atomic::cmpxchg(&_ref_count, old_value, new_value);
if (ref_count == old_value) {
return true;
}
old_value = ref_count;
}
}
inline uintptr_t G1CardSetContainer::decrement_refcount() {
uintptr_t old_value = refcount();
assert((old_value & 0x1) != 0 && old_value >= 3, "precondition");
return Atomic::sub(&_ref_count, 2u);
}
inline G1CardSetArray::G1CardSetArray(uint card_in_region, EntryCountType num_elems) :
G1CardSetContainer(),
_size(num_elems),
_num_entries(1) {
assert(_size > 0, "CardSetArray of size 0 not supported.");
assert(_size < LockBitMask, "Only support CardSetArray of size %u or smaller.", LockBitMask - 1);
_data[0] = card_in_region;
}
inline G1CardSetArray::G1CardSetArrayLocker::G1CardSetArrayLocker(EntryCountType volatile* value) :
_value(value),
_success(false) {
SpinYield s;
EntryCountType original_value = (*_value) & EntryMask;
while (true) {
EntryCountType old_value = Atomic::cmpxchg(_value,
original_value,
(EntryCountType)(original_value | LockBitMask));
if (old_value == original_value) {
// Succeeded locking the array.
_original_value = original_value;
break;
}
// Failed. Retry (with the lock bit stripped again).
original_value = old_value & EntryMask;
s.wait();
}
}
inline G1AddCardResult G1CardSetArray::add(uint card_idx) {
assert(card_idx < (1u << (sizeof(_data[0]) * BitsPerByte)),
"Card index %u does not fit card element.", card_idx);
EntryCountType num_entries = Atomic::load_acquire(&_num_entries) & EntryMask;
EntryCountType idx = 0;
for (; idx < num_entries; idx++) {
if (_data[idx] == card_idx) {
return Found;
}
}
// Since we did not find the card, lock.
G1CardSetArrayLocker x(&_num_entries);
// Reload number of entries from the G1CardSetArrayLocker as it might have changed.
// It already read the actual value with the necessary synchronization.
num_entries = x.num_entries();
// Look if the elements added while waiting for the lock are the same as our card.
for (; idx < num_entries; idx++) {
if (_data[idx] == card_idx) {
return Found;
}
}
// Check if there is space left.
if (num_entries == _size) {
return Overflow;
}
_data[num_entries] = card_idx;
x.inc_num_entries();
return Added;
}
inline bool G1CardSetArray::contains(uint card_idx) {
EntryCountType num_entries = Atomic::load_acquire(&_num_entries) & EntryMask;
for (EntryCountType idx = 0; idx < num_entries; idx++) {
if (_data[idx] == card_idx) {
return true;
}
}
return false;
}
template <class CardVisitor>
void G1CardSetArray::iterate(CardVisitor& found) {
EntryCountType num_entries = Atomic::load_acquire(&_num_entries) & EntryMask;
for (EntryCountType idx = 0; idx < num_entries; idx++) {
found(_data[idx]);
}
}
inline G1CardSetBitMap::G1CardSetBitMap(uint card_in_region, uint size_in_bits) :
G1CardSetContainer(), _num_bits_set(1) {
assert(size_in_bits % (sizeof(_bits[0]) * BitsPerByte) == 0,
"Size %u should be aligned to bitmap word size.", size_in_bits);
BitMapView bm(_bits, size_in_bits);
bm.clear();
bm.set_bit(card_in_region);
}
inline G1AddCardResult G1CardSetBitMap::add(uint card_idx, size_t threshold, size_t size_in_bits) {
BitMapView bm(_bits, size_in_bits);
if (_num_bits_set >= threshold) {
return bm.at(card_idx) ? Found : Overflow;
}
if (bm.par_set_bit(card_idx)) {
Atomic::inc(&_num_bits_set, memory_order_relaxed);
return Added;
}
return Found;
}
template <class CardVisitor>
inline void G1CardSetBitMap::iterate(CardVisitor& found, size_t size_in_bits, uint offset) {
BitMapView bm(_bits, size_in_bits);
BitMap::idx_t idx = bm.get_next_one_offset(0);
while (idx != size_in_bits) {
found((offset | (uint)idx));
idx = bm.get_next_one_offset(idx + 1);
}
}
inline G1CardSetHowl::G1CardSetHowl(EntryCountType card_in_region, G1CardSetConfiguration* config) :
G1CardSetContainer(),
_num_entries((config->num_cards_in_array() + 1)) /* Card Transfer will not increment _num_entries */ {
EntryCountType num_buckets = config->num_buckets_in_howl();
EntryCountType bucket = config->howl_bucket_index(card_in_region);
for (uint i = 0; i < num_buckets; ++i) {
_buckets[i] = G1CardSetInlinePtr();
if (i == bucket) {
G1CardSetInlinePtr value(&_buckets[i], _buckets[i]);
value.add(card_in_region, config->inline_ptr_bits_per_card(), config->num_cards_in_inline_ptr());
}
}
}
inline bool G1CardSetHowl::contains(uint card_idx, G1CardSetConfiguration* config) {
EntryCountType bucket = config->howl_bucket_index(card_idx);
CardSetPtr* array_entry = get_card_set_addr(bucket);
CardSetPtr card_set = Atomic::load_acquire(array_entry);
switch (G1CardSet::card_set_type(card_set)) {
case G1CardSet::CardSetArrayOfCards : {
return G1CardSet::card_set_ptr<G1CardSetArray>(card_set)->contains(card_idx);
}
case G1CardSet::CardSetBitMap: {
uint card_offset = config->howl_bitmap_offset(card_idx);
return G1CardSet::card_set_ptr<G1CardSetBitMap>(card_set)->contains(card_offset, config->num_cards_in_howl_bitmap());
}
case G1CardSet::CardSetInlinePtr: {
G1CardSetInlinePtr ptr(card_set);
return ptr.contains(card_idx, config->inline_ptr_bits_per_card());
}
case G1CardSet::CardSetHowl: {// Fullcard set entry
assert(card_set == G1CardSet::FullCardSet, "Must be");
return true;
}
}
return false;
}
template <class CardOrRangeVisitor>
inline void G1CardSetHowl::iterate(CardOrRangeVisitor& found, G1CardSetConfiguration* config) {
for (uint i = 0; i < config->num_buckets_in_howl(); ++i) {
iterate_cardset(_buckets[i], i, found, config);
}
}
template <class CardSetPtrVisitor>
inline void G1CardSetHowl::iterate(CardSetPtrVisitor& found, uint num_card_sets) {
for (uint i = 0; i < num_card_sets; ++i) {
found(&_buckets[i]);
}
}
template <class CardOrRangeVisitor>
inline void G1CardSetHowl::iterate_cardset(CardSetPtr const card_set, uint index, CardOrRangeVisitor& found, G1CardSetConfiguration* config) {
switch (G1CardSet::card_set_type(card_set)) {
case G1CardSet::CardSetInlinePtr: {
if (found.start_iterate(G1GCPhaseTimes::MergeRSHowlInline)) {
G1CardSetInlinePtr ptr(card_set);
ptr.iterate(found, config->inline_ptr_bits_per_card());
}
return;
}
case G1CardSet::CardSetArrayOfCards : {
if (found.start_iterate(G1GCPhaseTimes::MergeRSHowlArrayOfCards)) {
G1CardSet::card_set_ptr<G1CardSetArray>(card_set)->iterate(found);
}
return;
}
case G1CardSet::CardSetBitMap: {
if (found.start_iterate(G1GCPhaseTimes::MergeRSHowlBitmap)) {
uint offset = index << config->log2_num_cards_in_howl_bitmap();
G1CardSet::card_set_ptr<G1CardSetBitMap>(card_set)->iterate(found, config->num_cards_in_howl_bitmap(), offset);
}
return;
}
case G1CardSet::CardSetHowl: { // actually FullCardSet
if (found.start_iterate(G1GCPhaseTimes::MergeRSHowlFull)) {
assert(card_set == G1CardSet::FullCardSet, "Must be");
uint offset = index << config->log2_num_cards_in_howl_bitmap();
for (uint i = 0; i < config->max_cards_in_region(); i++) {
found((offset | (uint)i));
}
}
return;
}
}
}
inline G1CardSetHowl::EntryCountType G1CardSetHowl::num_buckets(size_t size_in_bits, size_t num_cards_in_array, size_t max_num_buckets) {
size_t size_bitmap_bytes = BitMap::calc_size_in_words(size_in_bits) * BytesPerWord;
// Ensure that in the worst case arrays consume half the memory size
// of storing the entire bitmap
size_t max_size_arrays_bytes = size_bitmap_bytes / 2;
size_t size_array_bytes = num_cards_in_array * sizeof(G1CardSetArray::EntryDataType);
size_t num_arrays = max_size_arrays_bytes / size_array_bytes;
// We use shifts and masks for indexing the array. So round down to the next
// power of two to not use more than expected memory.
num_arrays = round_down_power_of_2(MAX2((size_t)1, MIN2(num_arrays, max_num_buckets)));
return (EntryCountType)num_arrays;
}
#endif // SHARE_GC_G1_G1CARDSETCONTAINERS_INLINE_HPP

View File

@ -0,0 +1,203 @@
/*
* Copyright (c) 2021, 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 "gc/g1/g1CardSetFreeMemoryTask.hpp"
#include "gc/g1/g1CardSetMemory.inline.hpp"
#include "gc/g1/g1CollectedHeap.hpp"
#include "gc/g1/g1_globals.hpp"
#include "gc/shared/gcTraceTime.inline.hpp"
#include "gc/shared/suspendibleThreadSet.hpp"
#include "heapRegionRemSet.hpp"
#include "ci/ciUtilities.hpp"
constexpr const char* G1CardSetFreeMemoryTask::_state_names[];
const char* G1CardSetFreeMemoryTask::get_state_name(State value) const {
return _state_names[static_cast<std::underlying_type_t<State>>(value)];
}
bool G1CardSetFreeMemoryTask::deadline_exceeded(jlong deadline) {
return os::elapsed_counter() >= deadline;
}
static size_t keep_size(size_t free, size_t used, double percent) {
size_t to_keep = used * percent;
return MIN2(free, to_keep);
}
bool G1CardSetFreeMemoryTask::calculate_return_infos(jlong deadline) {
// Ignore the deadline in this step as it is very short.
G1CardSetMemoryStats used = _total_used;
G1CardSetMemoryStats free = G1CardSetFreePool::free_list_sizes();
_return_info = new G1ReturnMemoryProcessorSet(used.num_pools());
for (uint i = 0; i < used.num_pools(); i++) {
size_t return_to_vm_size = keep_size(free._num_mem_sizes[i],
used._num_mem_sizes[i],
G1RemSetFreeMemoryKeepExcessRatio);
log_trace(gc, task)("Card Set Free Memory: Type %s: Free: %zu (%zu) "
"Used: %zu Keep: %zu",
G1CardSetConfiguration::mem_object_type_name_str(i),
free._num_mem_sizes[i], free._num_buffers[i],
used._num_mem_sizes[i], return_to_vm_size);
_return_info->append(new G1ReturnMemoryProcessor(return_to_vm_size));
}
G1CardSetFreePool::update_unlink_processors(_return_info);
return false;
}
bool G1CardSetFreeMemoryTask::return_memory_to_vm(jlong deadline) {
for (int i = 0; i < _return_info->length(); i++) {
G1ReturnMemoryProcessor* info = _return_info->at(i);
if (!info->finished_return_to_vm()) {
if (info->return_to_vm(deadline)) {
return true;
}
}
}
return false;
}
bool G1CardSetFreeMemoryTask::return_memory_to_os(jlong deadline) {
for (int i = 0; i < _return_info->length(); i++) {
G1ReturnMemoryProcessor* info = _return_info->at(i);
if (!info->finished_return_to_os()) {
if (info->return_to_os(deadline)) {
return true;
}
}
}
return false;
}
bool G1CardSetFreeMemoryTask::cleanup_return_infos() {
for (int i = 0; i < _return_info->length(); i++) {
G1ReturnMemoryProcessor* info = _return_info->at(i);
delete info;
}
delete _return_info;
_return_info = nullptr;
return false;
}
bool G1CardSetFreeMemoryTask::free_excess_card_set_memory() {
jlong start = os::elapsed_counter();
jlong end = start +
(os::elapsed_frequency() / 1000) * G1RemSetFreeMemoryStepDurationMillis;
log_trace(gc, task)("Card Set Free Memory: Step start %1.3f end %1.3f",
TimeHelper::counter_to_millis(start), TimeHelper::counter_to_millis(end));
State next_state;
do {
switch (_state) {
case State::CalculateUsed: {
if (calculate_return_infos(end)) {
next_state = _state;
return true;
}
next_state = State::ReturnToVM;
break;
}
case State::ReturnToVM: {
if (return_memory_to_vm(end)) {
next_state = _state;
return true;
}
next_state = State::ReturnToOS;
break;
}
case State::ReturnToOS: {
if (return_memory_to_os(end)) {
next_state = _state;
return true;
}
next_state = State::Cleanup;
break;
}
case State::Cleanup: {
cleanup_return_infos();
next_state = State::Inactive;
break;
}
default:
log_error(gc, task)("Should not try to free excess card set memory in %s state", get_state_name(_state));
ShouldNotReachHere();
break;
}
set_state(next_state);
} while (_state != State::Inactive && !deadline_exceeded(end));
log_trace(gc, task)("Card Set Free Memory: Step took %1.3fms, done %s",
TimeHelper::counter_to_millis(os::elapsed_counter() - start),
bool_to_str(_state == State::CalculateUsed));
return is_active();
}
void G1CardSetFreeMemoryTask::set_state(State new_state) {
log_trace(gc, task)("Card Set Free Memory: State change from %s to %s",
get_state_name(_state),
get_state_name(new_state));
_state = new_state;
}
bool G1CardSetFreeMemoryTask::is_active() const {
return _state != State::Inactive;
}
jlong G1CardSetFreeMemoryTask::reschedule_delay_ms() const {
return G1RemSetFreeMemoryRescheduleDelayMillis;
}
G1CardSetFreeMemoryTask::G1CardSetFreeMemoryTask(const char* name) :
G1ServiceTask(name), _state(State::CalculateUsed), _return_info(nullptr) { }
void G1CardSetFreeMemoryTask::execute() {
SuspendibleThreadSetJoiner sts;
if (free_excess_card_set_memory()) {
schedule(reschedule_delay_ms());
}
}
void G1CardSetFreeMemoryTask::notify_new_stats(G1CardSetMemoryStats* young_gen_stats,
G1CardSetMemoryStats* collection_set_candidate_stats) {
assert_at_safepoint_on_vm_thread();
_total_used = *young_gen_stats;
_total_used.add(*collection_set_candidate_stats);
if (!is_active()) {
set_state(State::CalculateUsed);
G1CollectedHeap::heap()->service_thread()->schedule_task(this, 0);
}
}

View File

@ -0,0 +1,97 @@
/*
* Copyright (c) 2021, 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.
*
*/
#ifndef SHARE_GC_G1_G1CARDSETFREEMEMORYTASK_HPP
#define SHARE_GC_G1_G1CARDSETFREEMEMORYTASK_HPP
#include "gc/g1/g1ServiceThread.hpp"
#include "gc/g1/g1CardSetMemory.hpp"
#include "gc/g1/heapRegionRemSet.hpp"
#include "utilities/growableArray.hpp"
#include "utilities/ticks.hpp"
class G1CardSetBuffer;
// Task handling deallocation of free card set memory.
class G1CardSetFreeMemoryTask : public G1ServiceTask {
enum class State : uint {
Inactive,
CalculateUsed,
ReturnToVM,
ReturnToOS,
Cleanup
};
static constexpr const char* _state_names[] = { "Invalid",
"CalculateUsed",
"ReturnToVM",
"ReturnToOS",
"Cleanup" };
const char* get_state_name(State value) const;
State _state;
// Current total card set memory usage.
G1CardSetMemoryStats _total_used;
typedef G1CardSetFreePool::G1ReturnMemoryProcessor G1ReturnMemoryProcessor;
typedef G1CardSetFreePool::G1ReturnMemoryProcessorSet G1ReturnMemoryProcessorSet;
G1ReturnMemoryProcessorSet* _return_info;
// Returns whether the given deadline has passed.
bool deadline_exceeded(jlong deadline);
// Methods for the tasks to be done. They all return true if that step has
// completed.
bool calculate_return_infos(jlong deadline);
bool return_memory_to_vm(jlong deadline);
bool return_memory_to_os(jlong deadline);
bool cleanup_return_infos();
// Free excess card set memory, main method. Returns true if there is more work
// to do.
bool free_excess_card_set_memory();
void set_state(State new_state);
// Returns whether we are currently processing a recent request.
bool is_active() const;
// The delay used to reschedule this task if not all work has been completed.
jlong reschedule_delay_ms() const;
public:
explicit G1CardSetFreeMemoryTask(const char* name);
void execute() override;
// Notify the task of new used remembered set memory statistics for the young
// generation and the collection set candidate sets.
void notify_new_stats(G1CardSetMemoryStats* young_gen_stats,
G1CardSetMemoryStats* collection_set_candidate_stats);
};
#endif // SHARE_GC_G1_G1CARDSETFREEMEMORYTASK_HPP

View File

@ -0,0 +1,478 @@
/*
* Copyright (c) 2021, 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 "gc/g1/g1CardSetMemory.inline.hpp"
#include "logging/log.hpp"
#include "runtime/atomic.hpp"
#include "utilities/formatBuffer.hpp"
#include "utilities/ostream.hpp"
G1CardSetBuffer::G1CardSetBuffer(uint elem_size, uint num_instances, G1CardSetBuffer* next) :
_elem_size(elem_size), _num_elems(num_instances), _next(next), _next_allocate(0) {
_buffer = NEW_C_HEAP_ARRAY(char, (size_t)_num_elems * elem_size, mtGCCardSet);
}
G1CardSetBuffer::~G1CardSetBuffer() {
FREE_C_HEAP_ARRAY(mtGCCardSet, _buffer);
}
void* G1CardSetBuffer::get_new_buffer_elem() {
if (_next_allocate >= _num_elems) {
return nullptr;
}
uint result = Atomic::fetch_and_add(&_next_allocate, 1u, memory_order_relaxed);
if (result >= _num_elems) {
return nullptr;
}
void* r = _buffer + (uint)result * _elem_size;
return r;
}
void G1CardSetBufferList::bulk_add(G1CardSetBuffer& first, G1CardSetBuffer& last, size_t num, size_t mem_size) {
_list.prepend(first, last);
Atomic::add(&_num_buffers, num, memory_order_relaxed);
Atomic::add(&_mem_size, mem_size, memory_order_relaxed);
}
void G1CardSetBufferList::print_on(outputStream* out, const char* prefix) {
out->print_cr("%s: buffers %zu size %zu", prefix, Atomic::load(&_num_buffers), Atomic::load(&_mem_size));
}
G1CardSetBuffer* G1CardSetBufferList::get() {
GlobalCounter::CriticalSection cs(Thread::current());
G1CardSetBuffer* result = _list.pop();
if (result != nullptr) {
Atomic::dec(&_num_buffers, memory_order_relaxed);
Atomic::sub(&_mem_size, result->mem_size(), memory_order_relaxed);
}
return result;
}
G1CardSetBuffer* G1CardSetBufferList::get_all(size_t& num_buffers, size_t& mem_size) {
GlobalCounter::CriticalSection cs(Thread::current());
G1CardSetBuffer* result = _list.pop_all();
num_buffers = Atomic::load(&_num_buffers);
mem_size = Atomic::load(&_mem_size);
if (result != nullptr) {
Atomic::sub(&_num_buffers, num_buffers, memory_order_relaxed);
Atomic::sub(&_mem_size, mem_size, memory_order_relaxed);
}
return result;
}
void G1CardSetBufferList::free_all() {
size_t num_freed = 0;
size_t mem_size_freed = 0;
G1CardSetBuffer* cur;
while ((cur = _list.pop()) != nullptr) {
mem_size_freed += cur->mem_size();
num_freed++;
delete cur;
}
Atomic::sub(&_num_buffers, num_freed, memory_order_relaxed);
Atomic::sub(&_mem_size, mem_size_freed, memory_order_relaxed);
}
template <class Elem>
G1CardSetAllocator<Elem>::G1CardSetAllocator(const char* name,
const G1CardSetAllocOptions& buffer_options,
G1CardSetBufferList* free_buffer_list) :
_alloc_options(buffer_options),
_first(nullptr),
_last(nullptr),
_num_buffers(0),
_mem_size(0),
_free_buffer_list(free_buffer_list),
_transfer_lock(false),
_free_nodes_list(),
_pending_nodes_list(),
_num_pending_nodes(0),
_num_free_nodes(0),
_num_allocated_nodes(0),
_num_available_nodes(0)
{
assert(elem_size() >= sizeof(G1CardSetContainer), "Element instance size %u for allocator %s too small",
elem_size(), name);
assert(_free_buffer_list != nullptr, "precondition!");
}
template <class Elem>
bool G1CardSetAllocator<Elem>::try_transfer_pending() {
// Attempt to claim the lock.
if (Atomic::load_acquire(&_transfer_lock) || // Skip CAS if likely to fail.
Atomic::cmpxchg(&_transfer_lock, false, true)) {
return false;
}
// Have the lock; perform the transfer.
// Claim all the pending nodes.
G1CardSetContainer* first = _pending_nodes_list.pop_all();
if (first != nullptr) {
// Prepare to add the claimed nodes, and update _num_pending_nodes.
G1CardSetContainer* last = first;
Atomic::load_acquire(&_num_pending_nodes);
uint count = 1;
for (G1CardSetContainer* next = first->next(); next != nullptr; next = next->next()) {
last = next;
++count;
}
Atomic::sub(&_num_pending_nodes, count);
// Wait for any in-progress pops to avoid ABA for them.
GlobalCounter::write_synchronize();
// Add synchronized nodes to _free_node_list.
// Update count first so there can be no underflow in allocate().
Atomic::add(&_num_free_nodes, count);
_free_nodes_list.prepend(*first, *last);
}
Atomic::release_store(&_transfer_lock, false);
return true;
}
template <class Elem>
void G1CardSetAllocator<Elem>::free(Elem* elem) {
assert(elem != nullptr, "precondition");
assert(elem_size() >= sizeof(G1CardSetContainer), "size mismatch");
// Desired minimum transfer batch size. There is relatively little
// importance to the specific number. It shouldn't be too big, else
// we're wasting space when the release rate is low. If the release
// rate is high, we might accumulate more than this before being
// able to start a new transfer, but that's okay. Also note that
// the allocation rate and the release rate are going to be fairly
// similar, due to how the buffers are used. - kbarret
uint const trigger_transfer = 10;
uint pending_count = Atomic::add(&_num_pending_nodes, 1u, memory_order_relaxed);
G1CardSetContainer* node = reinterpret_cast<G1CardSetContainer*>(reinterpret_cast<char*>(elem));
node->set_next(nullptr);
assert(node->next() == nullptr, "precondition");
_pending_nodes_list.push(*node);
if (pending_count > trigger_transfer) {
try_transfer_pending();
}
}
template <class Elem>
void G1CardSetAllocator<Elem>::drop_all() {
_free_nodes_list.pop_all();
_pending_nodes_list.pop_all();
G1CardSetBuffer* cur = Atomic::load_acquire(&_first);
if (cur != nullptr) {
assert(_last != nullptr, "If there is at least one element, there must be a last one.");
G1CardSetBuffer* first = cur;
#ifdef ASSERT
// Check list consistency.
G1CardSetBuffer* last = cur;
uint num_buffers = 0;
size_t mem_size = 0;
while (cur != nullptr) {
mem_size += cur->mem_size();
num_buffers++;
G1CardSetBuffer* next = cur->next();
last = cur;
cur = next;
}
#endif
assert(num_buffers == _num_buffers, "Buffer count inconsistent %u %u", num_buffers, _num_buffers);
assert(mem_size == _mem_size, "Memory size inconsistent");
assert(last == _last, "Inconsistent last element");
_free_buffer_list->bulk_add(*first, *_last, _num_buffers, _mem_size);
}
_first = nullptr;
_last = nullptr;
_num_available_nodes = 0;
_num_allocated_nodes = 0;
_num_pending_nodes = 0;
_num_buffers = 0;
_mem_size = 0;
_num_free_nodes = 0;
}
template <class Elem>
void G1CardSetAllocator<Elem>::print(outputStream* os) {
os->print("MA " PTR_FORMAT ": %u elems pending (allocated %u available %u) used %.3f highest %u buffers %u size %zu ",
p2i(this), _num_pending_nodes, _num_allocated_nodes, _num_available_nodes, percent_of(_num_allocated_nodes - _num_pending_nodes, _num_available_nodes), _first != nullptr ? _first->num_elems() : 0, _num_buffers, mem_size());
}
G1CardSetMemoryStats::G1CardSetMemoryStats() {
clear();
}
G1CardSetMemoryStats::G1CardSetMemoryStats(void(*fn)(const void*,uint,size_t&,size_t&), const void* context) {
clear();
for (uint i = 0; i < num_pools(); i++) {
fn(context, i, _num_mem_sizes[i], _num_buffers[i]);
}
}
void G1CardSetMemoryStats::clear() {
for (uint i = 0; i < num_pools(); i++) {
_num_mem_sizes[i] = 0;
_num_buffers[i] = 0;
}
}
void G1CardSetFreePool::update_unlink_processors(G1ReturnMemoryProcessorSet* unlink_processor) {
uint num_free_lists = _freelist_pool.num_free_lists();
for (uint i = 0; i < num_free_lists; i++) {
unlink_processor->at(i)->visit_free_list(_freelist_pool.free_list(i));
}
}
void G1CardSetFreePool::G1ReturnMemoryProcessor::visit_free_list(G1CardSetBufferList* source) {
assert(_source == nullptr, "already visited");
if (_return_to_vm_size > 0) {
_source = source;
} else {
assert(_source == nullptr, "must be");
}
if (source->mem_size() > _return_to_vm_size) {
_first = source->get_all(_num_unlinked, _unlinked_bytes);
} else {
assert(_first == nullptr, "must be");
}
// Above we were racing with other threads getting the contents of the free list,
// so while we might have been asked to return something to the OS initially,
// the free list might be empty anyway. In this case just reset internal values
// used for checking whether there is work available.
if (_first == nullptr) {
_source = nullptr;
_return_to_vm_size = 0;
}
}
bool G1CardSetFreePool::G1ReturnMemoryProcessor::return_to_vm(jlong deadline) {
assert(!finished_return_to_vm(), "already returned everything to the VM");
assert(_first != nullptr, "must have element to return");
size_t keep_size = 0;
size_t keep_num = 0;
G1CardSetBuffer* cur = _first;
G1CardSetBuffer* last = nullptr;
while (cur != nullptr && _return_to_vm_size > 0) {
size_t cur_size = cur->mem_size();
_return_to_vm_size -= MIN2(_return_to_vm_size, cur_size);
keep_size += cur_size;
keep_num++;
last = cur;
cur = cur->next();
// To ensure progress, perform the deadline check here.
if (os::elapsed_counter() > deadline) {
break;
}
}
assert(_first != nullptr, "must be");
assert(last != nullptr, "must be");
last->set_next(nullptr);
// Wait for any in-progress pops to avoid ABA for them.
GlobalCounter::write_synchronize();
_source->bulk_add(*_first, *last, keep_num, keep_size);
_first = cur;
log_trace(gc, task)("Card Set Free Memory: Returned to VM %zu buffers size %zu", keep_num, keep_size);
// _return_to_vm_size may be larger than what is available in the list at the
// time we actually get the list. I.e. the list and _return_to_vm_size may be
// inconsistent.
// So also check if we actually already at the end of the list for the exit
// condition.
if (_return_to_vm_size == 0 || _first == nullptr) {
_source = nullptr;
_return_to_vm_size = 0;
}
return _source != nullptr;
}
bool G1CardSetFreePool::G1ReturnMemoryProcessor::return_to_os(jlong deadline) {
assert(finished_return_to_vm(), "not finished returning to VM");
assert(!finished_return_to_os(), "already returned everything to the OS");
// Now delete the rest.
size_t num_delete = 0;
size_t mem_size_deleted = 0;
while (_first != nullptr) {
G1CardSetBuffer* next = _first->next();
num_delete++;
mem_size_deleted += _first->mem_size();
delete _first;
_first = next;
// To ensure progress, perform the deadline check here.
if (os::elapsed_counter() > deadline) {
break;
}
}
log_trace(gc, task)("Card Set Free Memory: Return to OS %zu buffers size %zu", num_delete, mem_size_deleted);
return _first != nullptr;
}
G1CardSetFreePool G1CardSetFreePool::_freelist_pool(G1CardSetConfiguration::num_mem_object_types());
G1CardSetFreePool::G1CardSetFreePool(uint num_free_lists) :
_num_free_lists(num_free_lists) {
_free_lists = NEW_C_HEAP_ARRAY(G1CardSetBufferList, _num_free_lists, mtGC);
for (uint i = 0; i < _num_free_lists; i++) {
new (&_free_lists[i]) G1CardSetBufferList();
}
}
G1CardSetFreePool::~G1CardSetFreePool() {
for (uint i = 0; i < _num_free_lists; i++) {
_free_lists[i].~G1CardSetBufferList();
}
FREE_C_HEAP_ARRAY(mtGC, _free_lists);
}
static void collect_mem_sizes(const void* context, uint i, size_t& mem_size, size_t& num_buffers) {
((G1CardSetFreePool*)context)->get_size(i, mem_size, num_buffers);
}
void G1CardSetFreePool::get_size(uint i, size_t& mem_size, size_t& num_buffers) const {
mem_size = _free_lists[i].mem_size();
num_buffers = _free_lists[i].num_buffers();
}
G1CardSetMemoryStats G1CardSetFreePool::memory_sizes() const {
return G1CardSetMemoryStats(collect_mem_sizes, this);
}
size_t G1CardSetFreePool::mem_size() const {
size_t result = 0;
for (uint i = 0; i < _num_free_lists; i++) {
result += _free_lists[i].mem_size();
}
return result;
}
void G1CardSetFreePool::print_on(outputStream* out) {
out->print_cr(" Free Pool: size %zu", free_list_pool()->mem_size());
for (uint i = 0; i < _num_free_lists; i++) {
FormatBuffer<> fmt(" %s", G1CardSetConfiguration::mem_object_type_name_str(i));
_free_lists[i].print_on(out, fmt);
}
}
G1CardSetMemoryManager::G1CardSetMemoryManager(G1CardSetConfiguration* config,
G1CardSetFreePool* free_list_pool) : _config(config) {
_allocators = NEW_C_HEAP_ARRAY(G1CardSetAllocator<G1CardSetContainer>,
_config->num_mem_object_types(),
mtGC);
G1CardSetAllocOptions* alloc_options = _config->mem_object_alloc_options();
for (uint i = 0; i < num_mem_object_types(); i++) {
new (&_allocators[i]) G1CardSetAllocator<G1CardSetContainer>(_config->mem_object_type_name_str(i),
alloc_options[i],
free_list_pool->free_list(i));
}
FREE_C_HEAP_ARRAY(size_t, alloc_options);
}
uint G1CardSetMemoryManager::num_mem_object_types() const {
return _config->num_mem_object_types();
}
G1CardSetMemoryManager::~G1CardSetMemoryManager() {
for (uint i = 0; i < num_mem_object_types(); i++) {
_allocators[i].~G1CardSetAllocator();
}
FREE_C_HEAP_ARRAY(G1CardSetAllocator<G1CardSetContainer>, _allocators);
}
void G1CardSetMemoryManager::free(uint type, void* value) {
assert(type < num_mem_object_types(), "must be");
_allocators[type].free((G1CardSetContainer*)value);
}
void G1CardSetMemoryManager::flush() {
for (uint i = 0; i < num_mem_object_types(); i++) {
_allocators[i].drop_all();
}
}
void G1CardSetMemoryManager::print(outputStream* os) {
os->print_cr("MM " PTR_FORMAT " size %zu", p2i(this), sizeof(*this));
for (uint i = 0; i < num_mem_object_types(); i++) {
_allocators[i].print(os);
}
}
size_t G1CardSetMemoryManager::mem_size() const {
size_t result = 0;
for (uint i = 0; i < num_mem_object_types(); i++) {
result += _allocators[i].mem_size();
}
return sizeof(*this) -
(sizeof(G1CardSetAllocator<G1CardSetContainer>) * num_mem_object_types()) +
result;
}
size_t G1CardSetMemoryManager::wasted_mem_size() const {
size_t result = 0;
for (uint i = 0; i < num_mem_object_types(); i++) {
result += _allocators[i].wasted_mem_size();
}
return result;
}
G1CardSetMemoryStats G1CardSetMemoryManager::memory_stats() const {
G1CardSetMemoryStats result;
for (uint i = 0; i < num_mem_object_types(); i++) {
result._num_mem_sizes[i] += _allocators[i].mem_size();
result._num_buffers[i] += _allocators[i].num_buffers();
}
return result;
}

View File

@ -0,0 +1,384 @@
/*
* Copyright (c) 2021, 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.
*
*/
#ifndef SHARE_GC_G1_G1CARDSETMEMORY_HPP
#define SHARE_GC_G1_G1CARDSETMEMORY_HPP
#include "gc/g1/g1CardSet.hpp"
#include "gc/g1/g1CardSetContainers.hpp"
#include "memory/allocation.hpp"
#include "utilities/growableArray.hpp"
#include "utilities/lockFreeStack.hpp"
class G1CardSetConfiguration;
class outputStream;
// Collects G1CardSetAllocator options/heuristics. Called by G1CardSetAllocator
// to determine the next size of the allocated G1CardSetBuffer.
class G1CardSetAllocOptions {
uint _elem_size;
uint _initial_num_elems;
// Defines a limit to the number of elements in the buffer
uint _max_num_elems;
uint exponential_expand(uint prev_num_elems) {
return clamp(prev_num_elems * 2, _initial_num_elems, _max_num_elems);
}
public:
static const uint BufferAlignment = 8;
static const uint MinimumBufferSize = 8;
static const uint MaximumBufferSize = UINT_MAX / 2;
G1CardSetAllocOptions(uint elem_size, uint initial_num_elems = MinimumBufferSize, uint max_num_elems = MaximumBufferSize) :
_elem_size(align_up(elem_size, BufferAlignment)),
_initial_num_elems(initial_num_elems),
_max_num_elems(max_num_elems) {
}
uint next_num_elems(uint prev_num_elems) {
return exponential_expand(prev_num_elems);
}
uint elem_size () const {return _elem_size;}
};
// A single buffer/arena containing _num_elems blocks of memory of _elem_size.
// G1CardSetBuffers can be linked together using a singly linked list.
class G1CardSetBuffer : public CHeapObj<mtGCCardSet> {
uint _elem_size;
uint _num_elems;
G1CardSetBuffer* volatile _next;
char* _buffer; // Actual data.
// Index into the next free block to allocate into. Full if equal (or larger)
// to _num_elems (can be larger because we atomically increment this value and
// check only afterwards if the allocation has been successful).
uint volatile _next_allocate;
public:
G1CardSetBuffer(uint elem_size, uint num_elems, G1CardSetBuffer* next);
~G1CardSetBuffer();
G1CardSetBuffer* volatile* next_addr() { return &_next; }
void* get_new_buffer_elem();
uint num_elems() const { return _num_elems; }
G1CardSetBuffer* next() const { return _next; }
void set_next(G1CardSetBuffer* next) {
assert(next != this, " loop condition");
_next = next;
}
void reset(G1CardSetBuffer* next) {
_next_allocate = 0;
assert(next != this, " loop condition");
set_next(next);
memset((void*)_buffer, 0, (size_t)_num_elems * _elem_size);
}
uint elem_size() const { return _elem_size; }
size_t mem_size() const { return sizeof(*this) + (size_t)_num_elems * _elem_size; }
bool is_full() const { return _next_allocate >= _num_elems; }
};
// Set of (free) G1CardSetBuffers. The assumed usage is that allocation
// to it and removal of elements is strictly separate, but every action may be
// performed by multiple threads at the same time.
// Counts and memory usage are current on a best-effort basis if accessed concurrently.
class G1CardSetBufferList {
static G1CardSetBuffer* volatile* next_ptr(G1CardSetBuffer& node) {
return node.next_addr();
}
typedef LockFreeStack<G1CardSetBuffer, &next_ptr> NodeStack;
NodeStack _list;
volatile size_t _num_buffers;
volatile size_t _mem_size;
public:
G1CardSetBufferList() : _list(), _num_buffers(0), _mem_size(0) { }
~G1CardSetBufferList() { free_all(); }
void bulk_add(G1CardSetBuffer& first, G1CardSetBuffer& last, size_t num, size_t mem_size);
void add(G1CardSetBuffer& elem) { _list.prepend(elem); }
G1CardSetBuffer* get();
G1CardSetBuffer* get_all(size_t& num_buffers, size_t& mem_size);
// Give back all memory to the OS.
void free_all();
void print_on(outputStream* out, const char* prefix = "");
size_t num_buffers() const { return Atomic::load(&_num_buffers); }
size_t mem_size() const { return Atomic::load(&_mem_size); }
};
// Arena-like allocator for (card set) heap memory objects (Elem elements).
//
// Actual allocation from the C heap occurs on G1CardSetBuffer basis, i.e. sets
// of elements. The assumed allocation pattern for these G1CardSetBuffer elements
// is assumed to be strictly two-phased:
//
// - in the first phase, G1CardSetBuffers are allocated from the C heap (or a free
// list given at initialization time). This allocation may occur in parallel. This
// typically corresponds to a single mutator phase, but may extend over multiple.
//
// - in the second phase, G1CardSetBuffers are given back in bulk to the free list.
// This is typically done during a GC pause.
//
// Some third party is responsible for giving back memory from the free list to
// the operating system.
//
// Allocation and deallocation in the first phase on G1CardSetContainer basis
// may occur by multiple threads at once.
//
// Allocation occurs from an internal free list of G1CardSetContainers first,
// only then trying to bump-allocate from the current G1CardSetBuffer. If there is
// none, this class allocates a new G1CardSetBuffer (allocated from the C heap,
// asking the G1CardSetAllocOptions instance about sizes etc) and uses that one.
//
// The G1CardSetContainerOnHeaps free list is a linked list of G1CardSetContainers
// within all G1CardSetBuffer instances allocated so far. It uses a separate
// pending list and global synchronization to avoid the ABA problem when the
// user frees a memory object.
//
// The class also manages a few counters for statistics using atomic operations.
// Their values are only consistent within each other with extra global
// synchronization.
//
// Since it is expected that every CardSet (and in extension each region) has its
// own set of allocators, there is intentionally no padding between them to save
// memory.
template <class Elem>
class G1CardSetAllocator {
// G1CardSetBuffer management.
// G1CardSetAllocOptions provides parameters for allocation buffer
// sizing and expansion.
G1CardSetAllocOptions _alloc_options;
G1CardSetBuffer* volatile _first; // The (start of the) list of all buffers.
G1CardSetBuffer* _last; // The last element of the list of all buffers.
volatile uint _num_buffers; // Number of assigned buffers to this allocator.
volatile size_t _mem_size; // Memory used by all buffers.
G1CardSetBufferList* _free_buffer_list; // The global free buffer list to
// preferentially get new buffers from.
// G1CardSetContainer node management within the G1CardSetBuffers allocated
// by this allocator.
static G1CardSetContainer* volatile* next_ptr(G1CardSetContainer& node);
typedef LockFreeStack<G1CardSetContainer, &next_ptr> NodeStack;
volatile bool _transfer_lock;
NodeStack _free_nodes_list;
NodeStack _pending_nodes_list;
volatile uint _num_pending_nodes; // Number of nodes in the pending list.
volatile uint _num_free_nodes; // Number of nodes in the free list.
volatile uint _num_allocated_nodes; // Number of total nodes allocated and in use.
volatile uint _num_available_nodes; // Number of nodes available in all buffers (allocated + free + pending + not yet used).
// Try to transfer nodes from _pending_nodes_list to _free_nodes_list, with a
// synchronization delay for any in-progress pops from the _free_nodes_list
// to solve ABA here.
bool try_transfer_pending();
uint num_free_elems() const;
G1CardSetBuffer* create_new_buffer(G1CardSetBuffer* const prev);
uint elem_size() const { return _alloc_options.elem_size(); }
public:
G1CardSetAllocator(const char* name,
const G1CardSetAllocOptions& buffer_options,
G1CardSetBufferList* free_buffer_list);
~G1CardSetAllocator() {
drop_all();
}
Elem* allocate();
void free(Elem* elem);
// Deallocate all buffers to the free buffer list and reset this allocator. Must
// be called in a globally synchronized area.
void drop_all();
uint num_buffers() const;
size_t mem_size() const {
return sizeof(*this) +
num_buffers() * sizeof(G1CardSetBuffer) + (size_t)_num_available_nodes * elem_size();
}
size_t wasted_mem_size() const {
return ((size_t)_num_available_nodes - (_num_allocated_nodes - _num_pending_nodes)) * elem_size();
}
void print(outputStream* os);
};
// Statistics for a fixed set of buffer lists. Contains the number of buffers and memory
// used for each. Note that statistics are typically not taken atomically so there
// can be inconsistencies. The user must be prepared for them.
class G1CardSetMemoryStats {
public:
size_t _num_mem_sizes[G1CardSetConfiguration::num_mem_object_types()];
size_t _num_buffers[G1CardSetConfiguration::num_mem_object_types()];
// Returns all-zero statistics.
G1CardSetMemoryStats();
// For every element in the set (indicated by i), call fn to provide the
// memory size and number of buffers for that i'th buffer list.
G1CardSetMemoryStats(void (*fn)(const void* context, uint i, size_t& mem_size, size_t& num_buffers), const void* context);
void add(G1CardSetMemoryStats const other) {
STATIC_ASSERT(ARRAY_SIZE(_num_buffers) == ARRAY_SIZE(_num_mem_sizes));
for (uint i = 0; i < ARRAY_SIZE(_num_mem_sizes); i++) {
_num_mem_sizes[i] += other._num_mem_sizes[i];
_num_buffers[i] += other._num_buffers[i];
}
}
void clear();
uint num_pools() const { return G1CardSetConfiguration::num_mem_object_types(); }
};
// A set of free lists holding memory buffers for use by G1CardSetAllocators.
class G1CardSetFreePool {
// The global free pool.
static G1CardSetFreePool _freelist_pool;
uint _num_free_lists;
G1CardSetBufferList* _free_lists;
public:
static G1CardSetFreePool* free_list_pool() { return &_freelist_pool; }
static G1CardSetMemoryStats free_list_sizes() { return _freelist_pool.memory_sizes(); }
class G1ReturnMemoryProcessor;
typedef GrowableArrayCHeap<G1ReturnMemoryProcessor*, mtGC> G1ReturnMemoryProcessorSet;
static void update_unlink_processors(G1ReturnMemoryProcessorSet* unlink_processors);
explicit G1CardSetFreePool(uint num_free_lists);
~G1CardSetFreePool();
G1CardSetBufferList* free_list(uint i) {
assert(i < _num_free_lists, "must be");
return &_free_lists[i];
}
uint num_free_lists() const { return _num_free_lists; }
// Return sizes for free list i in this free list pool.
void get_size(uint i, size_t& mem_size, size_t& num_buffers) const;
G1CardSetMemoryStats memory_sizes() const;
size_t mem_size() const;
void print_on(outputStream* out);
};
// Data structure containing current in-progress state for returning memory to the
// operating system for a single G1CardSetBufferList.
class G1CardSetFreePool::G1ReturnMemoryProcessor : public CHeapObj<mtGC> {
G1CardSetBufferList* _source;
size_t _return_to_vm_size;
G1CardSetBuffer* _first;
size_t _unlinked_bytes;
size_t _num_unlinked;
public:
explicit G1ReturnMemoryProcessor(size_t return_to_vm) :
_source(nullptr), _return_to_vm_size(return_to_vm), _first(nullptr), _unlinked_bytes(0), _num_unlinked(0) {
}
// Updates the instance members about the given card set buffer list for the purpose
// of giving back memory. Only necessary members are updated, e.g. if there is
// nothing to return to the VM, do not set the source list.
void visit_free_list(G1CardSetBufferList* source);
bool finished_return_to_vm() const { return _return_to_vm_size == 0; }
bool finished_return_to_os() const { return _first == nullptr; }
// Returns memory to the VM until the given deadline expires. Returns true if
// there is no more work. Guarantees forward progress, i.e. at least one buffer
// has been processed after returning.
// return_to_vm() re-adds buffers to the respective free list.
bool return_to_vm(jlong deadline);
// Returns memory to the VM until the given deadline expires. Returns true if
// there is no more work. Guarantees forward progress, i.e. at least one buffer
// has been processed after returning.
// return_to_os() gives back buffers to the OS.
bool return_to_os(jlong deadline);
};
class G1CardSetMemoryManager : public CHeapObj<mtGCCardSet> {
G1CardSetConfiguration* _config;
G1CardSetAllocator<G1CardSetContainer>* _allocators;
uint num_mem_object_types() const;
public:
G1CardSetMemoryManager(G1CardSetConfiguration* config,
G1CardSetFreePool* free_list_pool);
virtual ~G1CardSetMemoryManager();
// Allocate and free a memory object of given type.
inline uint8_t* allocate(uint type);
void free(uint type, void* value);
// Allocate and free a hash table node.
inline uint8_t* allocate_node();
inline void free_node(void* value);
void flush();
void print(outputStream* os);
size_t mem_size() const;
size_t wasted_mem_size() const;
G1CardSetMemoryStats memory_stats() const;
};
#endif // SHARE_GC_G1_G1CARDSETMEMORY_HPP

View File

@ -0,0 +1,132 @@
/*
* Copyright (c) 2021, 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.
*
*/
#ifndef SHARE_GC_G1_G1CARDSETMEMORY_INLINE_HPP
#define SHARE_GC_G1_G1CARDSETMEMORY_INLINE_HPP
#include "gc/g1/g1CardSetMemory.hpp"
#include "gc/g1/g1CardSetContainers.hpp"
#include "utilities/ostream.hpp"
#include "gc/g1/g1CardSetContainers.inline.hpp"
#include "utilities/globalCounter.inline.hpp"
template <class Elem>
G1CardSetContainer* volatile* G1CardSetAllocator<Elem>::next_ptr(G1CardSetContainer& node) {
return node.next_addr();
}
template <class Elem>
G1CardSetBuffer* G1CardSetAllocator<Elem>::create_new_buffer(G1CardSetBuffer* const prev) {
// Take an existing buffer if available.
G1CardSetBuffer* next = _free_buffer_list->get();
if (next == nullptr) {
uint prev_num_elems = (prev != nullptr) ? prev->num_elems() : 0;
uint num_elems = _alloc_options.next_num_elems(prev_num_elems);
next = new G1CardSetBuffer(elem_size(), num_elems, prev);
} else {
assert(elem_size() == next->elem_size() , "Mismatch %d != %d Elem %zu", elem_size(), next->elem_size(), sizeof(Elem));
next->reset(prev);
}
// Install it as current allocation buffer.
G1CardSetBuffer* old = Atomic::cmpxchg(&_first, prev, next);
if (old != prev) {
// Somebody else installed the buffer, use that one.
delete next;
return old;
} else {
// Did we install the first element in the list? If so, this is also the last.
if (prev == nullptr) {
_last = next;
}
// Successfully installed the buffer into the list.
Atomic::inc(&_num_buffers, memory_order_relaxed);
Atomic::add(&_mem_size, next->mem_size(), memory_order_relaxed);
Atomic::add(&_num_available_nodes, next->num_elems(), memory_order_relaxed);
return next;
}
}
template <class Elem>
Elem* G1CardSetAllocator<Elem>::allocate() {
assert(elem_size() > 0, "instance size not set.");
if (num_free_elems() > 0) {
// Pop under critical section to deal with ABA problem
// Other solutions to the same problem are more complicated (ref counting, HP)
GlobalCounter::CriticalSection cs(Thread::current());
G1CardSetContainer* node = _free_nodes_list.pop();
if (node != nullptr) {
Elem* elem = reinterpret_cast<Elem*>(reinterpret_cast<char*>(node));
Atomic::sub(&_num_free_nodes, 1u);
guarantee(is_aligned(elem, 8), "result " PTR_FORMAT " not aligned", p2i(elem));
return elem;
}
}
G1CardSetBuffer* cur = Atomic::load_acquire(&_first);
if (cur == nullptr) {
cur = create_new_buffer(cur);
}
while (true) {
Elem* elem = (Elem*)cur->get_new_buffer_elem();
if (elem != nullptr) {
Atomic::inc(&_num_allocated_nodes, memory_order_relaxed);
guarantee(is_aligned(elem, 8), "result " PTR_FORMAT " not aligned", p2i(elem));
return elem;
}
// The buffer is full. Next round.
assert(cur->is_full(), "must be");
cur = create_new_buffer(cur);
}
}
inline uint8_t* G1CardSetMemoryManager::allocate(uint type) {
assert(type < num_mem_object_types(), "must be");
return (uint8_t*)_allocators[type].allocate();
}
inline uint8_t* G1CardSetMemoryManager::allocate_node() {
return allocate(0);
}
inline void G1CardSetMemoryManager::free_node(void* value) {
free(0, value);
}
template <class Elem>
inline uint G1CardSetAllocator<Elem>::num_buffers() const {
return Atomic::load(&_num_buffers);
}
template <class Elem>
inline uint G1CardSetAllocator<Elem>::num_free_elems() const {
return Atomic::load(&_num_free_nodes);
}
#endif // SHARE_GC_G1_G1CARDSETMEMORY_INLINE_HPP

View File

@ -102,7 +102,7 @@ public:
// already scanned cards. Assumes that most cards in that area are Clean.
// Returns the number of dirtied cards that were not yet dirty. This result may
// be inaccurate as it does not perform the dirtying atomically.
inline size_t mark_region_dirty(size_t start_card_index, size_t num_cards);
inline size_t mark_range_dirty(size_t start_card_index, size_t num_cards);
// Change the given range of dirty cards to "which". All of these cards must be Dirty.
inline void change_dirty_cards_to(size_t start_card_index, size_t num_cards, CardValue which);

View File

@ -43,7 +43,7 @@ inline bool G1CardTable::mark_clean_as_dirty(CardValue* card) {
return false;
}
inline size_t G1CardTable::mark_region_dirty(size_t start_card_index, size_t num_cards) {
inline size_t G1CardTable::mark_range_dirty(size_t start_card_index, size_t num_cards) {
assert(is_aligned(start_card_index, sizeof(size_t)), "Start card index must be aligned.");
assert(is_aligned(num_cards, sizeof(size_t)), "Number of cards to change must be evenly divisible.");

View File

@ -28,7 +28,7 @@
#include "gc/g1/g1CollectedHeap.inline.hpp"
#include "gc/g1/g1ConcurrentMark.inline.hpp"
#include "gc/g1/heapRegion.hpp"
#include "gc/g1/heapRegionRemSet.hpp"
#include "gc/g1/heapRegionRemSet.inline.hpp"
#include "oops/access.inline.hpp"
#include "oops/compressedOops.inline.hpp"
#include "oops/oop.inline.hpp"

View File

@ -34,6 +34,7 @@
#include "gc/g1/g1BarrierSet.hpp"
#include "gc/g1/g1CollectedHeap.inline.hpp"
#include "gc/g1/g1CollectionSet.hpp"
#include "gc/g1/g1CollectionSetCandidates.hpp"
#include "gc/g1/g1CollectorState.hpp"
#include "gc/g1/g1ConcurrentRefine.hpp"
#include "gc/g1/g1ConcurrentRefineThread.hpp"
@ -68,7 +69,7 @@
#include "gc/g1/g1VMOperations.hpp"
#include "gc/g1/g1YoungGCPostEvacuateTasks.hpp"
#include "gc/g1/heapRegion.inline.hpp"
#include "gc/g1/heapRegionRemSet.hpp"
#include "gc/g1/heapRegionRemSet.inline.hpp"
#include "gc/g1/heapRegionSet.inline.hpp"
#include "gc/shared/concurrentGCBreakpoints.hpp"
#include "gc/shared/gcBehaviours.hpp"
@ -147,7 +148,7 @@ void G1CollectedHeap::run_batch_task(G1BatchedGangTask* cl) {
HeapRegion* G1CollectedHeap::new_heap_region(uint hrs_index,
MemRegion mr) {
return new HeapRegion(hrs_index, bot(), mr);
return new HeapRegion(hrs_index, bot(), mr, &_card_set_config);
}
// Private methods.
@ -1436,6 +1437,7 @@ G1CollectedHeap::G1CollectedHeap() :
CollectedHeap(),
_service_thread(NULL),
_periodic_gc_task(NULL),
_free_card_set_memory_task(NULL),
_workers(NULL),
_card_table(NULL),
_collection_pause_end(Ticks::now()),
@ -1472,6 +1474,7 @@ G1CollectedHeap::G1CollectedHeap() :
_collection_set(this, _policy),
_hot_card_cache(NULL),
_rem_set(NULL),
_card_set_config(),
_cm(NULL),
_cm_thread(NULL),
_cr(NULL),
@ -1741,6 +1744,9 @@ jint G1CollectedHeap::initialize() {
_periodic_gc_task = new G1PeriodicGCTask("Periodic GC Task");
_service_thread->register_task(_periodic_gc_task);
_free_card_set_memory_task = new G1CardSetFreeMemoryTask("Card Set Free Memory Task");
_service_thread->register_task(_free_card_set_memory_task);
{
G1DirtyCardQueueSet& dcqs = G1BarrierSet::dirty_card_queue_set();
dcqs.set_process_cards_threshold(concurrent_refine()->yellow_zone());
@ -2621,6 +2627,9 @@ void G1CollectedHeap::gc_epilogue(bool full) {
_numa->print_statistics();
_collection_pause_end = Ticks::now();
_free_card_set_memory_task->notify_new_stats(&_young_gen_card_set_stats,
&_collection_set_candidates_card_set_stats);
}
uint G1CollectedHeap::uncommit_regions(uint region_limit) {
@ -2958,7 +2967,6 @@ void G1CollectedHeap::do_collection_pause_at_safepoint_helper(double target_paus
Threads::number_of_non_daemon_threads());
active_workers = workers()->update_active_workers(active_workers);
log_info(gc,task)("Using %u workers of %u for evacuation", active_workers, workers()->total_workers());
G1MonitoringScope ms(g1mm(),
false /* full_gc */,
collector_state()->in_mixed_phase() /* all_memory_pools_affected */);
@ -2967,12 +2975,10 @@ void G1CollectedHeap::do_collection_pause_at_safepoint_helper(double target_paus
{
IsGCActiveMark x;
gc_prologue(false);
G1HeapVerifier::G1VerifyType verify_type = young_collection_verify_type();
verify_before_young_collection(verify_type);
{
// The elapsed time induced by the start time below deliberately elides
// the possible verification above.
@ -3319,6 +3325,15 @@ bool G1CollectedHeap::should_do_eager_reclaim() const {
(has_humongous_reclaim_candidates() || do_humongous_object_logging()));
}
bool G1CollectedHeap::should_sample_collection_set_candidates() const {
G1CollectionSetCandidates* candidates = G1CollectedHeap::heap()->collection_set()->candidates();
return candidates != NULL && candidates->num_remaining() > 0;
}
void G1CollectedHeap::set_collection_set_candidates_stats(G1CardSetMemoryStats& stats) {
_collection_set_candidates_card_set_stats = stats;
}
class G1PrepareEvacuationTask : public AbstractGangTask {
class G1PrepareRegionsClosure : public HeapRegionClosure {
G1CollectedHeap* _g1h;
@ -3326,6 +3341,17 @@ class G1PrepareEvacuationTask : public AbstractGangTask {
uint _worker_humongous_total;
uint _worker_humongous_candidates;
G1CardSetMemoryStats _card_set_stats;
void sample_card_set_size(HeapRegion* hr) {
// Sample card set sizes for young gen and humongous before GC: this makes
// the policy to give back memory to the OS keep the most recent amount of
// memory for these regions.
if (hr->is_young() || hr->is_starts_humongous()) {
_card_set_stats.add(hr->rem_set()->card_set_memory_stats());
}
}
bool humongous_region_is_candidate(HeapRegion* region) const {
assert(region->is_starts_humongous(), "Must start a humongous object");
@ -3398,6 +3424,8 @@ class G1PrepareEvacuationTask : public AbstractGangTask {
// First prepare the region for scanning
_g1h->rem_set()->prepare_region_for_scan(hr);
sample_card_set_size(hr);
// Now check if region is a humongous candidate
if (!hr->is_starts_humongous()) {
_g1h->register_region_with_region_attr(hr);
@ -3428,12 +3456,19 @@ class G1PrepareEvacuationTask : public AbstractGangTask {
return false;
}
G1CardSetMemoryStats card_set_stats() const {
return _card_set_stats;
}
};
G1CollectedHeap* _g1h;
HeapRegionClaimer _claimer;
volatile uint _humongous_total;
volatile uint _humongous_candidates;
G1CardSetMemoryStats _all_card_set_stats;
public:
G1PrepareEvacuationTask(G1CollectedHeap* g1h) :
AbstractGangTask("Prepare Evacuation"),
@ -3445,6 +3480,9 @@ public:
void work(uint worker_id) {
G1PrepareRegionsClosure cl(_g1h, this);
_g1h->heap_region_par_iterate_from_worker_offset(&cl, &_claimer, worker_id);
MutexLocker x(ParGCRareEvent_lock, Mutex::_no_safepoint_check_flag);
_all_card_set_stats.add(cl.card_set_stats());
}
void add_humongous_candidates(uint candidates) {
@ -3462,6 +3500,10 @@ public:
uint humongous_total() {
return _humongous_total;
}
G1CardSetMemoryStats all_card_set_stats() const {
return _all_card_set_stats;
}
};
void G1CollectedHeap::pre_evacuate_collection_set(G1EvacuationInfo& evacuation_info, G1ParScanThreadStateSet* per_thread_states) {
@ -3489,6 +3531,8 @@ void G1CollectedHeap::pre_evacuate_collection_set(G1EvacuationInfo& evacuation_i
G1PrepareEvacuationTask g1_prep_task(this);
Tickspan task_time = run_task_timed(&g1_prep_task);
_young_gen_card_set_stats = g1_prep_task.all_card_set_stats();
phase_times()->record_register_regions(task_time.seconds() * 1000.0);
_num_humongous_objects = g1_prep_task.humongous_total();
_num_humongous_reclaim_candidates = g1_prep_task.humongous_candidates();
@ -3763,6 +3807,7 @@ void G1CollectedHeap::post_evacuate_collection_set(G1EvacuationInfo& evacuation_
evacuation_info.set_bytes_used(_bytes_used_during_gc);
policy()->print_age_table();
rem_set()->print_coarsen_stats();
}
void G1CollectedHeap::record_obj_copy_mem_stats() {

View File

@ -27,6 +27,8 @@
#include "gc/g1/g1BarrierSet.hpp"
#include "gc/g1/g1BiasedArray.hpp"
#include "gc/g1/g1CardSet.hpp"
#include "gc/g1/g1CardSetFreeMemoryTask.hpp"
#include "gc/g1/g1CardTable.hpp"
#include "gc/g1/g1CollectionSet.hpp"
#include "gc/g1/g1CollectorState.hpp"
@ -66,6 +68,7 @@
// Forward declarations
class HeapRegion;
class GenerationSpec;
class G1CardSetFreeMemoryTask;
class G1ParScanThreadState;
class G1ParScanThreadStateSet;
class G1ParScanThreadState;
@ -158,6 +161,7 @@ class G1CollectedHeap : public CollectedHeap {
private:
G1ServiceThread* _service_thread;
G1ServiceTask* _periodic_gc_task;
G1CardSetFreeMemoryTask* _free_card_set_memory_task;
WorkGang* _workers;
G1CardTable* _card_table;
@ -173,6 +177,11 @@ private:
HeapRegionSet _archive_set;
HeapRegionSet _humongous_set;
// Young gen memory statistics before GC.
G1CardSetMemoryStats _young_gen_card_set_stats;
// Collection set candidates memory statistics after GC.
G1CardSetMemoryStats _collection_set_candidates_card_set_stats;
void rebuild_free_region_list();
// Start a new incremental collection set for the next pause.
void start_new_collection_set();
@ -267,6 +276,9 @@ public:
bool should_do_eager_reclaim() const;
bool should_sample_collection_set_candidates() const;
void set_collection_set_candidates_stats(G1CardSetMemoryStats& stats);
private:
G1HRPrinter _hr_printer;
@ -840,6 +852,8 @@ public:
// The g1 remembered set of the heap.
G1RemSet* _rem_set;
// Global card set configuration
G1CardSetConfiguration _card_set_config;
void post_evacuate_cleanup_1(G1ParScanThreadStateSet* per_thread_states,
G1RedirtyCardsQueueSet* rdcqs);

View File

@ -31,7 +31,7 @@
#include "gc/g1/g1ParScanThreadState.hpp"
#include "gc/g1/g1Policy.hpp"
#include "gc/g1/heapRegion.inline.hpp"
#include "gc/g1/heapRegionRemSet.hpp"
#include "gc/g1/heapRegionRemSet.inline.hpp"
#include "gc/g1/heapRegionSet.hpp"
#include "logging/logStream.hpp"
#include "runtime/orderAccess.hpp"

View File

@ -26,7 +26,7 @@
#include "gc/g1/g1CollectedHeap.inline.hpp"
#include "gc/g1/g1CollectionSetCandidates.hpp"
#include "gc/g1/g1CollectionSetChooser.hpp"
#include "gc/g1/heapRegionRemSet.hpp"
#include "gc/g1/heapRegionRemSet.inline.hpp"
#include "gc/shared/space.inline.hpp"
#include "runtime/atomic.hpp"
#include "utilities/quickSort.hpp"

View File

@ -27,6 +27,7 @@
#include "classfile/systemDictionary.hpp"
#include "code/codeCache.hpp"
#include "gc/g1/g1BarrierSet.hpp"
#include "gc/g1/g1CardSetMemory.hpp"
#include "gc/g1/g1CollectedHeap.inline.hpp"
#include "gc/g1/g1CollectorState.hpp"
#include "gc/g1/g1ConcurrentMark.inline.hpp"
@ -39,7 +40,7 @@
#include "gc/g1/g1ThreadLocalData.hpp"
#include "gc/g1/g1Trace.hpp"
#include "gc/g1/heapRegion.inline.hpp"
#include "gc/g1/heapRegionRemSet.hpp"
#include "gc/g1/heapRegionRemSet.inline.hpp"
#include "gc/g1/heapRegionSet.inline.hpp"
#include "gc/shared/gcId.hpp"
#include "gc/shared/gcTimer.hpp"
@ -2947,7 +2948,7 @@ G1PrintRegionLivenessInfoClosure::~G1PrintRegionLivenessInfoClosure() {
}
// add static memory usages to remembered set sizes
_total_remset_bytes += HeapRegionRemSet::fl_mem_size() + HeapRegionRemSet::static_mem_size();
_total_remset_bytes += G1CardSetFreePool::free_list_pool()->mem_size() + HeapRegionRemSet::static_mem_size();
// Print the footer of the output.
log_trace(gc, liveness)(G1PPRL_LINE_PREFIX);
log_trace(gc, liveness)(G1PPRL_LINE_PREFIX

View File

@ -34,7 +34,7 @@
#include "gc/g1/g1Policy.hpp"
#include "gc/g1/g1RegionMarkStatsCache.inline.hpp"
#include "gc/g1/g1RemSetTrackingPolicy.hpp"
#include "gc/g1/heapRegionRemSet.hpp"
#include "gc/g1/heapRegionRemSet.inline.hpp"
#include "gc/g1/heapRegion.hpp"
#include "gc/shared/suspendibleThreadSet.hpp"
#include "gc/shared/taskqueue.inline.hpp"

View File

@ -34,7 +34,7 @@
#include "gc/g1/g1RedirtyCardsQueue.hpp"
#include "gc/g1/g1RemSet.hpp"
#include "gc/g1/g1ThreadLocalData.hpp"
#include "gc/g1/heapRegionRemSet.hpp"
#include "gc/g1/heapRegionRemSet.inline.hpp"
#include "gc/shared/suspendibleThreadSet.hpp"
#include "memory/iterator.hpp"
#include "runtime/atomic.hpp"

View File

@ -31,7 +31,7 @@
#include "gc/g1/g1OopClosures.inline.hpp"
#include "gc/g1/g1RedirtyCardsQueue.hpp"
#include "gc/g1/heapRegion.hpp"
#include "gc/g1/heapRegionRemSet.hpp"
#include "gc/g1/heapRegionRemSet.inline.hpp"
#include "gc/shared/preservedMarks.inline.hpp"
#include "oops/access.inline.hpp"
#include "oops/compressedOops.inline.hpp"

View File

@ -31,7 +31,7 @@
#include "gc/g1/g1FullCollector.inline.hpp"
#include "gc/g1/g1ConcurrentMarkBitMap.inline.hpp"
#include "gc/g1/g1FullGCMarker.inline.hpp"
#include "gc/g1/heapRegionRemSet.hpp"
#include "gc/g1/heapRegionRemSet.inline.hpp"
#include "memory/iterator.inline.hpp"
#include "memory/universe.hpp"
#include "oops/access.inline.hpp"

View File

@ -41,6 +41,8 @@
#include "utilities/enumIterator.hpp"
#include "utilities/macros.hpp"
constexpr const char* G1GCPhaseTimes::GCMergeRSWorkItemsStrings[];
G1GCPhaseTimes::G1GCPhaseTimes(STWGCTimer* gc_timer, uint max_gc_threads) :
_max_gc_threads(max_gc_threads),
_gc_start_counter(0),
@ -71,16 +73,14 @@ G1GCPhaseTimes::G1GCPhaseTimes(STWGCTimer* gc_timer, uint max_gc_threads) :
_gc_par_phases[MergeER] = new WorkerDataArray<double>("MergeER", "Eager Reclaim (ms):", max_gc_threads);
_gc_par_phases[MergeRS] = new WorkerDataArray<double>("MergeRS", "Remembered Sets (ms):", max_gc_threads);
_gc_par_phases[MergeRS]->create_thread_work_items("Merged Sparse:", MergeRSMergedSparse);
_gc_par_phases[MergeRS]->create_thread_work_items("Merged Fine:", MergeRSMergedFine);
_gc_par_phases[MergeRS]->create_thread_work_items("Merged Coarse:", MergeRSMergedCoarse);
_gc_par_phases[MergeRS]->create_thread_work_items("Dirty Cards:", MergeRSDirtyCards);
for (uint i = 0; i < MergeRSContainersSentinel; i++) {
_gc_par_phases[MergeRS]->create_thread_work_items(GCMergeRSWorkItemsStrings[i], i);
}
_gc_par_phases[OptMergeRS] = new WorkerDataArray<double>("OptMergeRS", "Optional Remembered Sets (ms):", max_gc_threads);
_gc_par_phases[OptMergeRS]->create_thread_work_items("Merged Sparse:", MergeRSMergedSparse);
_gc_par_phases[OptMergeRS]->create_thread_work_items("Merged Fine:", MergeRSMergedFine);
_gc_par_phases[OptMergeRS]->create_thread_work_items("Merged Coarse:", MergeRSMergedCoarse);
_gc_par_phases[OptMergeRS]->create_thread_work_items("Dirty Cards:", MergeRSDirtyCards);
for (uint i = 0; i < MergeRSContainersSentinel; i++) {
_gc_par_phases[OptMergeRS]->create_thread_work_items(GCMergeRSWorkItemsStrings[i], i);
}
_gc_par_phases[MergeLB] = new WorkerDataArray<double>("MergeLB", "Log Buffers (ms):", max_gc_threads);
if (G1HotCardCache::default_use_cache()) {
@ -134,6 +134,8 @@ G1GCPhaseTimes::G1GCPhaseTimes(STWGCTimer* gc_timer, uint max_gc_threads) :
_gc_par_phases[EagerlyReclaimHumongousObjects]->create_thread_work_items("Humongous Candidates", EagerlyReclaimNumCandidates);
_gc_par_phases[EagerlyReclaimHumongousObjects]->create_thread_work_items("Humongous Reclaimed", EagerlyReclaimNumReclaimed);
_gc_par_phases[SampleCollectionSetCandidates] = new WorkerDataArray<double>("SampleCandidates", "Sample CSet Candidates (ms):", max_gc_threads);
_gc_par_phases[Termination]->create_thread_work_items("Termination Attempts:");
_gc_par_phases[OptTermination]->create_thread_work_items("Optional Termination Attempts:");
@ -170,6 +172,7 @@ void G1GCPhaseTimes::reset() {
_recorded_clear_claimed_marks_time_ms = 0.0;
_recorded_young_cset_choice_time_ms = 0.0;
_recorded_non_young_cset_choice_time_ms = 0.0;
_recorded_sample_collection_set_candidates_time_ms = 0.0;
_recorded_preserve_cm_referents_time_ms = 0.0;
_recorded_start_new_cset_time_ms = 0.0;
_recorded_serial_free_cset_time_ms = 0.0;
@ -449,6 +452,7 @@ double G1GCPhaseTimes::print_post_evacuate_collection_set() const {
_recorded_preserve_cm_referents_time_ms +
_cur_ref_proc_time_ms +
(_weak_phase_times.total_time_sec() * MILLIUNITS) +
_recorded_sample_collection_set_candidates_time_ms +
_cur_post_evacuate_cleanup_1_time_ms +
_cur_post_evacuate_cleanup_2_time_ms +
_recorded_total_rebuild_freelist_time_ms +
@ -472,6 +476,8 @@ double G1GCPhaseTimes::print_post_evacuate_collection_set() const {
debug_phase(_gc_par_phases[RemoveSelfForwardingPtr], 1);
}
debug_time("Sample Collection Set Candidates", _recorded_sample_collection_set_candidates_time_ms);
trace_phase(_gc_par_phases[RedirtyCards]);
debug_time("Post Evacuate Cleanup 2", _cur_post_evacuate_cleanup_2_time_ms);
if (G1CollectedHeap::heap()->evacuation_failed()) {
debug_phase(_gc_par_phases[RecalculateUsed], 1);
@ -485,6 +491,9 @@ double G1GCPhaseTimes::print_post_evacuate_collection_set() const {
if (G1CollectedHeap::heap()->should_do_eager_reclaim()) {
debug_phase(_gc_par_phases[EagerlyReclaimHumongousObjects], 1);
}
if (G1CollectedHeap::heap()->should_sample_collection_set_candidates()) {
debug_phase(_gc_par_phases[SampleCollectionSetCandidates], 1);
}
debug_phase(_gc_par_phases[RedirtyCards], 1);
debug_phase(_gc_par_phases[FreeCollectionSet], 1);
trace_phase(_gc_par_phases[YoungFreeCSet], true, 1);

View File

@ -75,6 +75,7 @@ class G1GCPhaseTimes : public CHeapObj<mtGC> {
YoungFreeCSet,
NonYoungFreeCSet,
RebuildFreeList,
SampleCollectionSetCandidates,
MergePSS,
RemoveSelfForwardingPtr,
ClearCardTable,
@ -97,13 +98,24 @@ class G1GCPhaseTimes : public CHeapObj<mtGC> {
return GCParPhases(StrongOopStorageSetRoots + index);
}
enum GCMergeRSWorkTimes {
MergeRSMergedSparse,
MergeRSMergedFine,
MergeRSMergedCoarse,
MergeRSDirtyCards
enum GCMergeRSWorkItems : uint {
MergeRSMergedInline = 0,
MergeRSMergedArrayOfCards,
MergeRSMergedHowl,
MergeRSMergedFull,
MergeRSHowlInline,
MergeRSHowlArrayOfCards,
MergeRSHowlBitmap,
MergeRSHowlFull,
MergeRSDirtyCards,
MergeRSContainersSentinel
};
static constexpr const char* GCMergeRSWorkItemsStrings[MergeRSContainersSentinel] =
{ "Merged Inline", "Merged ArrayOfCards", "Merged Howl", "Merged Full",
"Merged Howl Inline", "Merged Howl ArrayOfCards", "Merged Howl BitMap", "Merged Howl Full",
"Dirty Cards" };
enum GCScanHRWorkItems {
ScanHRScannedCards,
ScanHRScannedBlocks,
@ -173,6 +185,8 @@ class G1GCPhaseTimes : public CHeapObj<mtGC> {
double _recorded_young_cset_choice_time_ms;
double _recorded_non_young_cset_choice_time_ms;
double _recorded_sample_collection_set_candidates_time_ms;
double _recorded_preserve_cm_referents_time_ms;
double _recorded_start_new_cset_time_ms;
@ -329,6 +343,10 @@ class G1GCPhaseTimes : public CHeapObj<mtGC> {
_recorded_non_young_cset_choice_time_ms = time_ms;
}
void record_sample_collection_set_candidates_time_ms(double time_ms) {
_recorded_sample_collection_set_candidates_time_ms = time_ms;
}
void record_preserve_cm_referents_time_ms(double time_ms) {
_recorded_preserve_cm_referents_time_ms = time_ms;
}

View File

@ -32,7 +32,7 @@
#include "gc/g1/g1ParScanThreadState.inline.hpp"
#include "gc/g1/g1RemSet.hpp"
#include "gc/g1/heapRegion.inline.hpp"
#include "gc/g1/heapRegionRemSet.hpp"
#include "gc/g1/heapRegionRemSet.inline.hpp"
#include "memory/iterator.inline.hpp"
#include "oops/access.inline.hpp"
#include "oops/compressedOops.inline.hpp"

View File

@ -30,7 +30,7 @@
#include "gc/g1/g1RedirtyCardsQueue.hpp"
#include "gc/g1/g1OopClosures.hpp"
#include "gc/g1/g1RemSet.hpp"
#include "gc/g1/heapRegionRemSet.hpp"
#include "gc/g1/heapRegionRemSet.inline.hpp"
#include "gc/shared/ageTable.hpp"
#include "gc/shared/partialArrayTaskStepper.hpp"
#include "gc/shared/stringdedup/stringDedup.hpp"

View File

@ -40,7 +40,7 @@
#include "gc/g1/g1SurvivorRegions.hpp"
#include "gc/g1/g1YoungGenSizer.hpp"
#include "gc/g1/heapRegion.inline.hpp"
#include "gc/g1/heapRegionRemSet.hpp"
#include "gc/g1/heapRegionRemSet.inline.hpp"
#include "gc/shared/concurrentGCBreakpoints.hpp"
#include "gc/shared/gcPolicyCounters.hpp"
#include "logging/log.hpp"
@ -50,6 +50,8 @@
#include "utilities/growableArray.hpp"
#include "utilities/pair.hpp"
#include "gc/shared/gcTraceTime.inline.hpp"
G1Policy::G1Policy(STWGCTimer* gc_timer) :
_predictor(G1ConfidencePercent / 100.0),
_analytics(new G1Analytics(&_predictor)),

View File

@ -26,6 +26,7 @@
#include "gc/g1/g1BarrierSet.hpp"
#include "gc/g1/g1BatchedGangTask.hpp"
#include "gc/g1/g1BlockOffsetTable.inline.hpp"
#include "gc/g1/g1CardSet.inline.hpp"
#include "gc/g1/g1CardTable.inline.hpp"
#include "gc/g1/g1CardTableEntryClosure.hpp"
#include "gc/g1/g1CollectedHeap.inline.hpp"
@ -45,7 +46,6 @@
#include "gc/g1/heapRegion.inline.hpp"
#include "gc/g1/heapRegionManager.inline.hpp"
#include "gc/g1/heapRegionRemSet.inline.hpp"
#include "gc/g1/sparsePRT.hpp"
#include "gc/shared/gcTraceTime.inline.hpp"
#include "gc/shared/ptrQueue.hpp"
#include "gc/shared/suspendibleThreadSet.hpp"
@ -381,9 +381,7 @@ public:
"Trying to access index " SIZE_FORMAT " out of bounds " SIZE_FORMAT,
card_idx >> _scan_chunks_shift, _num_total_scan_chunks);
size_t const chunk_idx = card_idx >> _scan_chunks_shift;
if (!_region_scan_chunks[chunk_idx]) {
_region_scan_chunks[chunk_idx] = true;
}
_region_scan_chunks[chunk_idx] = true;
}
G1AbstractSubTask* create_cleanup_after_scan_heap_roots_task() {
@ -1061,9 +1059,9 @@ void G1RemSet::prepare_for_scan_heap_roots() {
_scan_state->prepare();
}
// Small ring buffer used for prefetching cards for read/write from the card
// Small ring buffer used for prefetching cards for write from the card
// table during GC.
template <class T, bool for_write>
template <class T>
class G1MergeHeapRootsPrefetchCache {
public:
static const uint CacheSize = G1MergeHeapRootsPrefetchCacheSize;
@ -1096,11 +1094,7 @@ public:
}
T* push(T* elem) {
if (for_write) {
Prefetch::write(elem, 0);
} else {
Prefetch::read(elem, 0);
}
Prefetch::write(elem, 0);
T* result = _cache[_cur_cache_idx];
_cache[_cur_cache_idx++] = elem;
_cur_cache_idx &= (CacheSize - 1);
@ -1111,7 +1105,33 @@ public:
class G1MergeHeapRootsTask : public AbstractGangTask {
// Visitor for remembered sets, dropping entries onto the card table.
class G1MergeCardSetStats {
size_t _merged[G1GCPhaseTimes::MergeRSContainersSentinel];
public:
G1MergeCardSetStats() {
for (uint i = 0; i < ARRAY_SIZE(_merged); i++) {
_merged[i] = 0;
}
}
void inc_card_set_merged(uint tag) {
assert(tag < ARRAY_SIZE(_merged), "tag out of bounds %u", tag);
_merged[tag]++;
}
void inc_cards_dirty(size_t increment = 1) {
_merged[G1GCPhaseTimes::MergeRSDirtyCards] += increment;
}
size_t merged(uint i) const { return _merged[i]; }
};
// Visitor for remembered sets. Several methods of it are called by a region's
// card set iterator to drop card set remembered set entries onto the card.
// table. This is in addition to being the HeapRegionClosure to iterate over
// all region's remembered sets.
//
// We add a small prefetching cache in front of the actual work as dropping
// onto the card table is basically random memory access. This improves
// performance of this operation significantly.
@ -1121,24 +1141,20 @@ class G1MergeHeapRootsTask : public AbstractGangTask {
G1RemSetScanState* _scan_state;
G1CardTable* _ct;
uint _merged_sparse;
uint _merged_fine;
uint _merged_coarse;
size_t _cards_dirty;
G1MergeCardSetStats _stats;
// Cached card table index of the currently processed region to avoid constant
// recalculation as our remembered set containers are per region.
size_t _region_base_idx;
class G1MergeCardSetCache : public G1MergeHeapRootsPrefetchCache<G1CardTable::CardValue, true> {
class G1MergeCardSetCache : public G1MergeHeapRootsPrefetchCache<G1CardTable::CardValue> {
G1MergeCardSetClosure* const _merge_card_cl;
public:
G1MergeCardSetCache(G1MergeCardSetClosure* const merge_card_cl) :
// Initially set dummy card value to Dirty to avoid any actual mark work if we
// try to process it.
G1MergeHeapRootsPrefetchCache<G1CardTable::CardValue, true>(G1CardTable::dirty_card_val()),
G1MergeHeapRootsPrefetchCache<G1CardTable::CardValue>(G1CardTable::dirty_card_val()),
_merge_card_cl(merge_card_cl) { }
~G1MergeCardSetCache() {
@ -1160,26 +1176,19 @@ class G1MergeHeapRootsTask : public AbstractGangTask {
void mark_card(G1CardTable::CardValue* value) {
if (_ct->mark_clean_as_dirty(value)) {
_cards_dirty++;
_stats.inc_cards_dirty();
_scan_state->set_chunk_dirty(_ct->index_for_cardvalue(value));
}
}
void start_iterate(uint const region_idx) {
_region_base_idx = (size_t)region_idx << HeapRegion::LogCardsPerRegion;
}
public:
G1MergeCardSetClosure(G1RemSetScanState* scan_state) :
_scan_state(scan_state),
_ct(G1CollectedHeap::heap()->card_table()),
_merged_sparse(0),
_merged_fine(0),
_merged_coarse(0),
_cards_dirty(0),
_stats(),
_region_base_idx(0),
_merge_card_set_cache(this) {
}
_merge_card_set_cache(this) { }
void do_card(uint const card_idx) {
G1CardTable::CardValue* to_prefetch = _ct->byte_for_index(_region_base_idx + card_idx);
@ -1188,47 +1197,26 @@ class G1MergeHeapRootsTask : public AbstractGangTask {
mark_card(to_process);
}
void next_coarse_prt(uint const region_idx) {
if (!remember_if_interesting(region_idx)) {
return;
// Returns whether the given region actually needs iteration.
bool start_iterate(uint const tag, uint const region_idx) {
assert(tag < G1GCPhaseTimes::MergeRSDirtyCards, "invalid tag %u", tag);
if (remember_if_interesting(region_idx)) {
_region_base_idx = (size_t)region_idx << HeapRegion::LogCardsPerRegion;
_stats.inc_card_set_merged(tag);
return true;
}
return false;
}
_merged_coarse++;
start_iterate(region_idx);
_cards_dirty += _ct->mark_region_dirty(_region_base_idx, HeapRegion::CardsPerRegion);
void do_card_range(uint const start_card_idx, uint const length) {
assert(start_card_idx == 0, "must be");
assert(length == HeapRegion::CardsPerRegion, "must be");
size_t num_dirtied = _ct->mark_range_dirty(_region_base_idx, HeapRegion::CardsPerRegion);
_stats.inc_cards_dirty(num_dirtied);
_scan_state->set_chunk_region_dirty(_region_base_idx);
}
void next_fine_prt(uint const region_idx, BitMap* bm) {
if (!remember_if_interesting(region_idx)) {
return;
}
_merged_fine++;
start_iterate(region_idx);
BitMap::idx_t cur = bm->get_next_one_offset(0);
while (cur != bm->size()) {
do_card((uint)cur);
cur = bm->get_next_one_offset(cur + 1);
}
}
void next_sparse_prt(uint const region_idx, SparsePRTEntry::card_elem_t* cards, uint const num_cards) {
if (!remember_if_interesting(region_idx)) {
return;
}
_merged_sparse++;
start_iterate(region_idx);
for (uint i = 0; i < num_cards; i++) {
do_card(cards[i]);
}
}
// Helper to put the remembered set cards for these regions onto the card
// Helper to merge the cards in the card set for the given region onto the card
// table.
//
// Called directly for humongous starts regions because we should not add
@ -1242,12 +1230,12 @@ class G1MergeHeapRootsTask : public AbstractGangTask {
// pass) or the "dirty" list will be merged with the "all" list later otherwise.
// (And there is no problem either way if the region does not contain dirty
// cards).
void dump_rem_set_for_region(HeapRegion* r) {
void merge_card_set_for_region(HeapRegion* r) {
assert(r->in_collection_set() || r->is_starts_humongous(), "must be");
HeapRegionRemSet* rem_set = r->rem_set();
if (!rem_set->is_empty()) {
rem_set->iterate_prts(*this);
rem_set->iterate_for_merge(*this);
}
}
@ -1255,25 +1243,22 @@ class G1MergeHeapRootsTask : public AbstractGangTask {
assert(r->in_collection_set(), "must be");
_scan_state->add_all_dirty_region(r->hrm_index());
dump_rem_set_for_region(r);
merge_card_set_for_region(r);
return false;
}
size_t merged_sparse() const { return _merged_sparse; }
size_t merged_fine() const { return _merged_fine; }
size_t merged_coarse() const { return _merged_coarse; }
size_t cards_dirty() const { return _cards_dirty; }
G1MergeCardSetStats stats() const { return _stats; }
};
// Visitor for the remembered sets of humongous candidate regions to merge their
// remembered set into the card table.
class G1FlushHumongousCandidateRemSets : public HeapRegionClosure {
G1MergeCardSetClosure _cl;
G1RemSetScanState* _scan_state;
G1MergeCardSetStats _merge_stats;
public:
G1FlushHumongousCandidateRemSets(G1RemSetScanState* scan_state) : _cl(scan_state) { }
G1FlushHumongousCandidateRemSets(G1RemSetScanState* scan_state) : _scan_state(scan_state), _merge_stats() { }
virtual bool do_heap_region(HeapRegion* r) {
G1CollectedHeap* g1h = G1CollectedHeap::heap();
@ -1287,7 +1272,12 @@ class G1MergeHeapRootsTask : public AbstractGangTask {
guarantee(r->rem_set()->occupancy_less_or_equal_than(G1EagerReclaimRemSetThreshold),
"Found a not-small remembered set here. This is inconsistent with previous assumptions.");
_cl.dump_rem_set_for_region(r);
G1MergeCardSetStats stats;
{
G1MergeCardSetClosure cl(_scan_state);
cl.merge_card_set_for_region(r);
stats = cl.stats();
}
// We should only clear the card based remembered set here as we will not
// implicitly rebuild anything else during eager reclaim. Note that at the moment
@ -1307,16 +1297,11 @@ class G1MergeHeapRootsTask : public AbstractGangTask {
return false;
}
size_t merged_sparse() const { return _cl.merged_sparse(); }
size_t merged_fine() const { return _cl.merged_fine(); }
size_t merged_coarse() const { return _cl.merged_coarse(); }
size_t cards_dirty() const { return _cl.cards_dirty(); }
size_t merged(uint i) const { return _merge_stats.merged(i); }
};
// Visitor for the log buffer entries to merge them into the card table.
class G1MergeLogBufferCardsClosure : public G1CardTableEntryClosure {
friend class G1MergeLogBufferCardsCache;
G1RemSetScanState* _scan_state;
G1CardTable* _ct;
@ -1324,23 +1309,6 @@ class G1MergeHeapRootsTask : public AbstractGangTask {
size_t _cards_dirty;
size_t _cards_skipped;
class G1MergeLogBufferCardsCache : public G1MergeHeapRootsPrefetchCache<G1CardTable::CardValue, false> {
G1MergeLogBufferCardsClosure* const _merge_log_buffer_cl;
public:
G1MergeLogBufferCardsCache(G1MergeLogBufferCardsClosure* const merge_log_buffer_cl) :
// Initially set dummy card value to Clean to avoid any actual work if we
// try to process it.
G1MergeHeapRootsPrefetchCache<G1CardTable::CardValue, false>(G1CardTable::clean_card_val()),
_merge_log_buffer_cl(merge_log_buffer_cl) { }
~G1MergeLogBufferCardsCache() {
for (uint i = 0; i < CacheSize; i++) {
_merge_log_buffer_cl->process_card(push(&_dummy_card));
}
}
} _merge_log_buffer_cache;
void process_card(CardValue* card_ptr) {
if (*card_ptr == G1CardTable::dirty_card_val()) {
uint const region_idx = _ct->region_idx_for(card_ptr);
@ -1355,8 +1323,7 @@ class G1MergeHeapRootsTask : public AbstractGangTask {
_scan_state(scan_state),
_ct(g1h->card_table()),
_cards_dirty(0),
_cards_skipped(0),
_merge_log_buffer_cache(this)
_cards_skipped(0)
{}
void do_card_ptr(CardValue* card_ptr, uint worker_id) {
@ -1373,8 +1340,7 @@ class G1MergeHeapRootsTask : public AbstractGangTask {
// This code may count duplicate entries in the log buffers (even if rare) multiple
// times.
if (_scan_state->contains_cards_to_process(region_idx)) {
CardValue* to_process = _merge_log_buffer_cache.push(card_ptr);
process_card(to_process);
process_card(card_ptr);
} else {
// We may have had dirty cards in the (initial) collection set (or the
// young regions which are always in the initial collection set). We do
@ -1442,22 +1408,24 @@ public:
G1FlushHumongousCandidateRemSets cl(_scan_state);
g1h->heap_region_iterate(&cl);
p->record_or_add_thread_work_item(merge_remset_phase, worker_id, cl.merged_sparse(), G1GCPhaseTimes::MergeRSMergedSparse);
p->record_or_add_thread_work_item(merge_remset_phase, worker_id, cl.merged_fine(), G1GCPhaseTimes::MergeRSMergedFine);
p->record_or_add_thread_work_item(merge_remset_phase, worker_id, cl.merged_coarse(), G1GCPhaseTimes::MergeRSMergedCoarse);
p->record_or_add_thread_work_item(merge_remset_phase, worker_id, cl.cards_dirty(), G1GCPhaseTimes::MergeRSDirtyCards);
for (uint i = 0; i < G1GCPhaseTimes::MergeRSContainersSentinel; i++) {
p->record_or_add_thread_work_item(merge_remset_phase, worker_id, cl.merged(i), i);
}
}
// Merge remembered sets of current candidates.
{
G1GCParPhaseTimesTracker x(p, merge_remset_phase, worker_id, _initial_evacuation /* must_record */);
G1MergeCardSetClosure cl(_scan_state);
g1h->collection_set_iterate_increment_from(&cl, &_hr_claimer, worker_id);
G1MergeCardSetStats stats;
{
G1MergeCardSetClosure cl(_scan_state);
g1h->collection_set_iterate_increment_from(&cl, &_hr_claimer, worker_id);
stats = cl.stats();
}
p->record_or_add_thread_work_item(merge_remset_phase, worker_id, cl.merged_sparse(), G1GCPhaseTimes::MergeRSMergedSparse);
p->record_or_add_thread_work_item(merge_remset_phase, worker_id, cl.merged_fine(), G1GCPhaseTimes::MergeRSMergedFine);
p->record_or_add_thread_work_item(merge_remset_phase, worker_id, cl.merged_coarse(), G1GCPhaseTimes::MergeRSMergedCoarse);
p->record_or_add_thread_work_item(merge_remset_phase, worker_id, cl.cards_dirty(), G1GCPhaseTimes::MergeRSDirtyCards);
for (uint i = 0; i < G1GCPhaseTimes::MergeRSContainersSentinel; i++) {
p->record_or_add_thread_work_item(merge_remset_phase, worker_id, stats.merged(i), i);
}
}
// Apply closure to log entries in the HCC.
@ -1486,20 +1454,25 @@ public:
};
void G1RemSet::print_merge_heap_roots_stats() {
size_t num_visited_cards = _scan_state->num_visited_cards();
LogTarget(Debug, gc, remset) lt;
if (lt.is_enabled()) {
LogStream ls(lt);
size_t total_dirty_region_cards = _scan_state->num_cards_in_dirty_regions();
size_t num_visited_cards = _scan_state->num_visited_cards();
G1CollectedHeap* g1h = G1CollectedHeap::heap();
size_t total_old_region_cards =
(g1h->num_regions() - (g1h->num_free_regions() - g1h->collection_set()->cur_length())) * HeapRegion::CardsPerRegion;
size_t total_dirty_region_cards = _scan_state->num_cards_in_dirty_regions();
log_debug(gc,remset)("Visited cards " SIZE_FORMAT " Total dirty " SIZE_FORMAT " (%.2lf%%) Total old " SIZE_FORMAT " (%.2lf%%)",
num_visited_cards,
total_dirty_region_cards,
percent_of(num_visited_cards, total_dirty_region_cards),
total_old_region_cards,
percent_of(num_visited_cards, total_old_region_cards));
G1CollectedHeap* g1h = G1CollectedHeap::heap();
size_t total_old_region_cards =
(g1h->num_regions() - (g1h->num_free_regions() - g1h->collection_set()->cur_length())) * HeapRegion::CardsPerRegion;
ls.print_cr("Visited cards " SIZE_FORMAT " Total dirty " SIZE_FORMAT " (%.2lf%%) Total old " SIZE_FORMAT " (%.2lf%%)",
num_visited_cards,
total_dirty_region_cards,
percent_of(num_visited_cards, total_dirty_region_cards),
total_old_region_cards,
percent_of(num_visited_cards, total_old_region_cards));
}
}
void G1RemSet::merge_heap_roots(bool initial_evacuation) {
@ -1531,9 +1504,7 @@ void G1RemSet::merge_heap_roots(bool initial_evacuation) {
workers->run_task(&cl, num_workers);
}
if (log_is_enabled(Debug, gc, remset)) {
print_merge_heap_roots_stats();
}
print_merge_heap_roots_stats();
}
void G1RemSet::complete_evac_phase(bool has_more_than_one_evacuation_phase) {
@ -1548,6 +1519,15 @@ G1AbstractSubTask* G1RemSet::create_cleanup_after_scan_heap_roots_task() {
return _scan_state->create_cleanup_after_scan_heap_roots_task();
}
void G1RemSet::print_coarsen_stats() {
LogTarget(Debug, gc, remset) lt;
if (lt.is_enabled()) {
LogStream ls(lt);
G1CardSet::print_coarsen_stats(&ls);
}
}
inline void check_card_ptr(CardTable::CardValue* card_ptr, G1CardTable* ct) {
#ifdef ASSERT
G1CollectedHeap* g1h = G1CollectedHeap::heap();

View File

@ -107,6 +107,10 @@ public:
// Prepare for and cleanup after scanning the heap roots. Must be called
// once before and after in sequential code.
void prepare_for_scan_heap_roots();
// Cleans the card table from temporary duplicate detection information.
void cleanup_after_scan_heap_roots();
// Print coarsening stats.
void print_coarsen_stats();
// Creates a gang task for cleaining up temporary data structures and the
// card table, removing temporary duplicate detection information.
G1AbstractSubTask* create_cleanup_after_scan_heap_roots_task();

View File

@ -24,6 +24,7 @@
#include "precompiled.hpp"
#include "gc/g1/g1BarrierSet.hpp"
#include "gc/g1/g1CardSetMemory.hpp"
#include "gc/g1/g1CollectedHeap.inline.hpp"
#include "gc/g1/g1ConcurrentRefine.hpp"
#include "gc/g1/g1ConcurrentRefineThread.hpp"
@ -31,7 +32,7 @@
#include "gc/g1/g1RemSet.hpp"
#include "gc/g1/g1RemSetSummary.hpp"
#include "gc/g1/heapRegion.hpp"
#include "gc/g1/heapRegionRemSet.hpp"
#include "gc/g1/heapRegionRemSet.inline.hpp"
#include "memory/allocation.inline.hpp"
#include "memory/iterator.hpp"
#include "runtime/thread.inline.hpp"
@ -48,9 +49,10 @@ void G1RemSetSummary::update() {
_counter++;
}
} collector(this);
G1CollectedHeap* g1h = G1CollectedHeap::heap();
g1h->concurrent_refine()->threads_do(&collector);
_num_coarsenings = HeapRegionRemSet::n_coarsenings();
_coarsenings = HeapRegionRemSet::coarsen_stats();
set_sampling_task_vtime(g1h->rem_set()->sampling_task_vtime());
}
@ -68,7 +70,7 @@ double G1RemSetSummary::rs_thread_vtime(uint thread) const {
}
G1RemSetSummary::G1RemSetSummary(bool should_update) :
_num_coarsenings(0),
_coarsenings(),
_num_vtimes(G1ConcurrentRefine::max_num_threads()),
_rs_threads_vtimes(NEW_C_HEAP_ARRAY(double, _num_vtimes, mtGC)),
_sampling_task_vtime(0.0f) {
@ -88,7 +90,7 @@ void G1RemSetSummary::set(G1RemSetSummary* other) {
assert(other != NULL, "just checking");
assert(_num_vtimes == other->_num_vtimes, "just checking");
_num_coarsenings = other->num_coarsenings();
_coarsenings = other->_coarsenings;
memcpy(_rs_threads_vtimes, other->_rs_threads_vtimes, sizeof(double) * _num_vtimes);
@ -99,7 +101,7 @@ void G1RemSetSummary::subtract_from(G1RemSetSummary* other) {
assert(other != NULL, "just checking");
assert(_num_vtimes == other->_num_vtimes, "just checking");
_num_coarsenings = other->num_coarsenings() - _num_coarsenings;
_coarsenings.subtract_from(other->_coarsenings);
for (uint i = 0; i < _num_vtimes; i++) {
set_rs_thread_vtime(i, other->rs_thread_vtime(i) - rs_thread_vtime(i));
@ -112,9 +114,11 @@ class RegionTypeCounter {
private:
const char* _name;
size_t _rs_wasted_mem_size;
size_t _rs_mem_size;
size_t _cards_occupied;
size_t _amount;
size_t _amount_tracked;
size_t _code_root_mem_size;
size_t _code_root_elems;
@ -136,21 +140,25 @@ private:
}
size_t amount() const { return _amount; }
size_t amount_tracked() const { return _amount_tracked; }
public:
RegionTypeCounter(const char* name) : _name(name), _rs_mem_size(0), _cards_occupied(0),
_amount(0), _code_root_mem_size(0), _code_root_elems(0) { }
RegionTypeCounter(const char* name) : _name(name), _rs_wasted_mem_size(0), _rs_mem_size(0), _cards_occupied(0),
_amount(0), _amount_tracked(0), _code_root_mem_size(0), _code_root_elems(0) { }
void add(size_t rs_mem_size, size_t cards_occupied, size_t code_root_mem_size,
size_t code_root_elems) {
void add(size_t rs_wasted_mem_size, size_t rs_mem_size, size_t cards_occupied,
size_t code_root_mem_size, size_t code_root_elems, bool tracked) {
_rs_wasted_mem_size += rs_wasted_mem_size;
_rs_mem_size += rs_mem_size;
_cards_occupied += cards_occupied;
_code_root_mem_size += code_root_mem_size;
_code_root_elems += code_root_elems;
_amount++;
_amount_tracked += tracked ? 1 : 0;
}
size_t rs_wasted_mem_size() const { return _rs_wasted_mem_size; }
size_t rs_mem_size() const { return _rs_mem_size; }
size_t cards_occupied() const { return _cards_occupied; }
@ -158,15 +166,18 @@ public:
size_t code_root_elems() const { return _code_root_elems; }
void print_rs_mem_info_on(outputStream * out, size_t total) {
out->print_cr(" " SIZE_FORMAT_W(8) "%s (%5.1f%%) by " SIZE_FORMAT " %s regions",
byte_size_in_proper_unit(rs_mem_size()),
proper_unit_for_byte_size(rs_mem_size()),
rs_mem_size_percent_of(total), amount(), _name);
out->print_cr(" " SIZE_FORMAT_W(8) " (%5.1f%%) by " SIZE_FORMAT " "
"(" SIZE_FORMAT ") %s regions wasted " SIZE_FORMAT,
rs_mem_size(), rs_mem_size_percent_of(total),
amount_tracked(), amount(),
_name, rs_wasted_mem_size());
}
void print_cards_occupied_info_on(outputStream * out, size_t total) {
out->print_cr(" " SIZE_FORMAT_W(8) " (%5.1f%%) entries by " SIZE_FORMAT " %s regions",
cards_occupied(), cards_occupied_percent_of(total), amount(), _name);
out->print_cr(" " SIZE_FORMAT_W(8) " (%5.1f%%) entries by " SIZE_FORMAT " "
"(" SIZE_FORMAT ") %s regions",
cards_occupied(), cards_occupied_percent_of(total),
amount_tracked(), amount(), _name);
}
void print_code_root_mem_info_on(outputStream * out, size_t total) {
@ -195,6 +206,7 @@ private:
size_t _max_rs_mem_sz;
HeapRegion* _max_rs_mem_sz_region;
size_t total_rs_wasted_mem_sz() const { return _all.rs_wasted_mem_size(); }
size_t total_rs_mem_sz() const { return _all.rs_mem_size(); }
size_t total_cards_occupied() const { return _all.cards_occupied(); }
@ -222,6 +234,7 @@ public:
// HeapRegionRemSet::mem_size() includes the
// size of the strong code roots
size_t rs_wasted_mem_sz = hrrs->wasted_mem_size();
size_t rs_mem_sz = hrrs->mem_size();
if (rs_mem_sz > _max_rs_mem_sz) {
_max_rs_mem_sz = rs_mem_sz;
@ -249,8 +262,10 @@ public:
} else {
ShouldNotReachHere();
}
current->add(rs_mem_sz, occupied_cards, code_root_mem_sz, code_root_elems);
_all.add(rs_mem_sz, occupied_cards, code_root_mem_sz, code_root_elems);
current->add(rs_wasted_mem_sz, rs_mem_sz, occupied_cards,
code_root_mem_sz, code_root_elems, r->rem_set()->is_tracked());
_all.add(rs_wasted_mem_sz, rs_mem_sz, occupied_cards,
code_root_mem_sz, code_root_elems, r->rem_set()->is_tracked());
return false;
}
@ -259,23 +274,15 @@ public:
RegionTypeCounter* counters[] = { &_young, &_humongous, &_free, &_old, &_archive, NULL };
out->print_cr(" Current rem set statistics");
out->print_cr(" Total per region rem sets sizes = " SIZE_FORMAT "%s."
" Max = " SIZE_FORMAT "%s.",
byte_size_in_proper_unit(total_rs_mem_sz()),
proper_unit_for_byte_size(total_rs_mem_sz()),
byte_size_in_proper_unit(max_rs_mem_sz()),
proper_unit_for_byte_size(max_rs_mem_sz()));
out->print_cr(" Total per region rem sets sizes = " SIZE_FORMAT
" Max = " SIZE_FORMAT " wasted = " SIZE_FORMAT,
total_rs_mem_sz(),
max_rs_mem_sz(),
total_rs_wasted_mem_sz());
for (RegionTypeCounter** current = &counters[0]; *current != NULL; current++) {
(*current)->print_rs_mem_info_on(out, total_rs_mem_sz());
}
out->print_cr(" Static structures = " SIZE_FORMAT "%s,"
" free_lists = " SIZE_FORMAT "%s.",
byte_size_in_proper_unit(HeapRegionRemSet::static_mem_size()),
proper_unit_for_byte_size(HeapRegionRemSet::static_mem_size()),
byte_size_in_proper_unit(HeapRegionRemSet::fl_mem_size()),
proper_unit_for_byte_size(HeapRegionRemSet::fl_mem_size()));
out->print_cr(" " SIZE_FORMAT " occupied cards represented.",
total_cards_occupied());
for (RegionTypeCounter** current = &counters[0]; *current != NULL; current++) {
@ -285,12 +292,14 @@ public:
// Largest sized rem set region statistics
HeapRegionRemSet* rem_set = max_rs_mem_sz_region()->rem_set();
out->print_cr(" Region with largest rem set = " HR_FORMAT ", "
"size = " SIZE_FORMAT "%s, occupied = " SIZE_FORMAT "%s.",
"size = " SIZE_FORMAT " occupied = " SIZE_FORMAT,
HR_FORMAT_PARAMS(max_rs_mem_sz_region()),
byte_size_in_proper_unit(rem_set->mem_size()),
proper_unit_for_byte_size(rem_set->mem_size()),
byte_size_in_proper_unit(rem_set->occupied()),
proper_unit_for_byte_size(rem_set->occupied()));
rem_set->mem_size(),
rem_set->occupied());
HeapRegionRemSet::print_static_mem_size(out);
G1CardSetFreePool::free_list_pool()->print_on(out);
// Strong code root statistics
HeapRegionRemSet* max_code_root_rem_set = max_code_root_mem_sz_region()->rem_set();
out->print_cr(" Total heap region code root sets sizes = " SIZE_FORMAT "%s."
@ -319,7 +328,8 @@ public:
};
void G1RemSetSummary::print_on(outputStream* out) {
out->print_cr(" Did " SIZE_FORMAT " coarsenings.", num_coarsenings());
out->print("Coarsening: ");
_coarsenings.print_on(out);
out->print_cr(" Concurrent refinement threads times (s)");
out->print(" ");
for (uint i = 0; i < _num_vtimes; i++) {

View File

@ -25,6 +25,7 @@
#ifndef SHARE_GC_G1_G1REMSETSUMMARY_HPP
#define SHARE_GC_G1_G1REMSETSUMMARY_HPP
#include "gc/g1/g1CardSet.hpp"
#include "utilities/globalDefinitions.hpp"
#include "utilities/ostream.hpp"
@ -33,7 +34,7 @@ class G1RemSet;
// A G1RemSetSummary manages statistical information about the G1RemSet
class G1RemSetSummary {
size_t _num_coarsenings;
G1CardSetCoarsenStats _coarsenings;
size_t _num_vtimes;
double* _rs_threads_vtimes;
@ -65,10 +66,6 @@ public:
double sampling_task_vtime() const {
return _sampling_task_vtime;
}
size_t num_coarsenings() const {
return _num_coarsenings;
}
};
#endif // SHARE_GC_G1_G1REMSETSUMMARY_HPP

View File

@ -26,7 +26,7 @@
#include "gc/g1/g1CollectionSetChooser.hpp"
#include "gc/g1/g1RemSetTrackingPolicy.hpp"
#include "gc/g1/heapRegion.inline.hpp"
#include "gc/g1/heapRegionRemSet.hpp"
#include "gc/g1/heapRegionRemSet.inline.hpp"
#include "runtime/safepoint.hpp"
bool G1RemSetTrackingPolicy::needs_scan_for_rebuild(HeapRegion* r) const {

View File

@ -25,8 +25,10 @@
#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.hpp"
#include "gc/g1/g1ConcurrentMark.inline.hpp"
#include "gc/g1/g1EvacStats.inline.hpp"
#include "gc/g1/g1ParScanThreadState.hpp"
@ -41,6 +43,9 @@ G1PostEvacuateCollectionSetCleanupTask1::G1PostEvacuateCollectionSetCleanupTask1
{
add_serial_task(new MergePssTask(per_thread_states));
add_serial_task(new RecalculateUsedTask());
if (SampleCollectionSetCandidatesTask::should_execute()) {
add_serial_task(new SampleCollectionSetCandidatesTask());
}
if (RemoveSelfForwardPtrsTask::should_execute()) {
add_parallel_task(new RemoveSelfForwardPtrsTask(rdcqs));
}
@ -64,6 +69,32 @@ void G1PostEvacuateCollectionSetCleanupTask1::RecalculateUsedTask::do_work(uint
G1CollectedHeap::heap()->update_used_after_gc();
}
bool G1PostEvacuateCollectionSetCleanupTask1::SampleCollectionSetCandidatesTask::should_execute() {
return G1CollectedHeap::heap()->should_sample_collection_set_candidates();
}
double G1PostEvacuateCollectionSetCleanupTask1::SampleCollectionSetCandidatesTask::worker_cost() const {
return should_execute() ? 1.0 : AlmostNoWork;
}
class G1SampleCollectionSetCandidatesClosure : public HeapRegionClosure {
public:
G1CardSetMemoryStats _total;
bool do_heap_region(HeapRegion* r) override {
_total.add(r->rem_set()->card_set_memory_stats());
return false;
}
};
void G1PostEvacuateCollectionSetCleanupTask1::SampleCollectionSetCandidatesTask::do_work(uint worker_id) {
G1CollectedHeap* g1h = G1CollectedHeap::heap();
G1SampleCollectionSetCandidatesClosure cl;
g1h->collection_set()->candidates()->iterate(&cl);
g1h->set_collection_set_candidates_stats(cl._total);
}
bool G1PostEvacuateCollectionSetCleanupTask1::RemoveSelfForwardPtrsTask::should_execute() {
return G1CollectedHeap::heap()->evacuation_failed();
}

View File

@ -38,11 +38,13 @@ class G1RedirtyCardsQueueSet;
// First set of post evacuate collection set tasks containing ("s" means serial):
// - Merge PSS (s)
// - Recalculate Used (s)
// - Sample Collection Set Candidates (s)
// - Remove Self Forwards (on evacuation failure)
// - Clear Card Table
class G1PostEvacuateCollectionSetCleanupTask1 : public G1BatchedGangTask {
class MergePssTask;
class RecalculateUsedTask;
class SampleCollectionSetCandidatesTask;
class RemoveSelfForwardPtrsTask;
public:
@ -68,6 +70,16 @@ public:
void do_work(uint worker_id) override;
};
class G1PostEvacuateCollectionSetCleanupTask1::SampleCollectionSetCandidatesTask : public G1AbstractSubTask {
public:
SampleCollectionSetCandidatesTask() : G1AbstractSubTask(G1GCPhaseTimes::SampleCollectionSetCandidates) { }
static bool should_execute();
double worker_cost() const override;
void do_work(uint worker_id) override;
};
class G1PostEvacuateCollectionSetCleanupTask1::RemoveSelfForwardPtrsTask : public G1AbstractSubTask {
G1ParRemoveSelfForwardPtrsTask _task;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2001, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2001, 2021, 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
@ -130,15 +130,16 @@
range(0, max_jint) \
\
product(size_t, G1ConcRefinementThresholdStep, 2, \
"Each time the rset update queue increases by this amount " \
"activate the next refinement thread if available. " \
"Each time the remembered set update queue increases by this " \
"amount activate the next refinement thread if available. " \
"The actual step size will be selected ergonomically by " \
"default, with this value used to determine a lower bound.") \
range(1, SIZE_MAX) \
\
product(intx, G1RSetUpdatingPauseTimePercent, 10, \
"A target percentage of time that is allowed to be spend on " \
"process RS update buffers during the collection pause.") \
"processing remembered set update buffers during the collection " \
"pause.") \
range(0, 100) \
\
product(bool, G1UseAdaptiveConcRefinement, true, \
@ -153,26 +154,40 @@
"The threshold that defines (>=) a hot card.") \
range(0, max_jubyte) \
\
develop(intx, G1RSetRegionEntriesBase, 256, \
"Max number of regions in a fine-grain table per MB.") \
range(1, max_jint/wordSize) \
develop(uint, G1RemSetArrayOfCardsEntriesBase, 4, \
"Maximum number of entries per region in the Array of Cards " \
"card set container per MB of a heap region.") \
range(1, 65536) \
\
product(intx, G1RSetRegionEntries, 0, \
"Max number of regions for which we keep bitmaps." \
"Will be set ergonomically by default") \
range(0, max_jint/wordSize) \
constraint(G1RSetRegionEntriesConstraintFunc,AfterErgo) \
product(uint, G1RemSetArrayOfCardsEntries, 0, EXPERIMENTAL, \
"Maximum number of entries per Array of Cards card set " \
"container. Will be set ergonomically by default.") \
range(0, 65536) \
constraint(G1RemSetArrayOfCardsEntriesConstraintFunc,AfterErgo) \
\
develop(intx, G1RSetSparseRegionEntriesBase, 4, \
"Max number of entries per region in a sparse table " \
"per MB.") \
range(1, max_jint/wordSize) \
product(uint, G1RemSetHowlMaxNumBuckets, 8, EXPERIMENTAL, \
"Maximum number of buckets per Howl card set container. The " \
"default gives at worst bitmaps of size 8k. This showed to be a " \
"good tradeoff between bitmap size (waste) and cacheability of " \
"the bucket array. Must be a power of two.") \
range(1, 1024) \
constraint(G1RemSetHowlMaxNumBucketsConstraintFunc,AfterErgo) \
\
product(intx, G1RSetSparseRegionEntries, 0, \
"Max number of entries per region in a sparse table." \
"Will be set ergonomically by default.") \
range(0, max_jint/wordSize) \
constraint(G1RSetSparseRegionEntriesConstraintFunc,AfterErgo) \
product(uint, G1RemSetHowlNumBuckets, 0, EXPERIMENTAL, \
"Number of buckets per Howl card set container. Must be a power " \
"of two. Will be set ergonomically by default.") \
range(0, 1024) \
constraint(G1RemSetHowlNumBucketsConstraintFunc,AfterErgo) \
\
product(uint, G1RemSetCoarsenHowlBitmapToHowlFullPercent, 90, EXPERIMENTAL, \
"Percentage at which to coarsen a Howl bitmap to Howl full card " \
"set container.") \
range(1, 100) \
\
product(uint, G1RemSetCoarsenHowlToFullPercent, 90, EXPERIMENTAL, \
"Percentage at which to coarsen a Howl card set to Full card " \
"set container.") \
range(1, 100) \
\
develop(intx, G1MaxVerifyFailures, -1, \
"The maximum number of verification failures to print. " \
@ -190,7 +205,7 @@
constraint(G1HeapRegionSizeConstraintFunc,AfterMemoryInit) \
\
product(uint, G1ConcRefinementThreads, 0, \
"The number of parallel rem set update threads. " \
"The number of parallel remembered set update threads. " \
"Will be set ergonomically by default.") \
range(0, (max_jint-1)/wordSize) \
\
@ -309,7 +324,23 @@
"disables this check.") \
range(0.0, (double)max_uintx) \
\
product(bool, G1AllowPreventiveGC, true, DIAGNOSTIC, \
product(uint, G1RemSetFreeMemoryRescheduleDelayMillis, 10, EXPERIMENTAL, \
"Time after which the card set free memory task reschedules " \
"itself if there is work remaining.") \
range(1, UINT_MAX) \
\
product(double, G1RemSetFreeMemoryStepDurationMillis, 1, EXPERIMENTAL, \
"The amount of time that the free memory task should spend " \
"before a pause of G1RemSetFreeMemoryRescheduleDelayMillis " \
"length.") \
range(1e-3, 1e+6) \
\
product(double, G1RemSetFreeMemoryKeepExcessRatio, 0.1, EXPERIMENTAL, \
"The percentage of free card set memory that G1 should keep as " \
"percentage of the currently used memory.") \
range(0.0, 1.0) \
\
product(bool, G1AllowPreventiveGC, true, DIAGNOSTIC, \
"Allows collections to be triggered proactively based on the \
number of free regions and the expected survival rates in each \
section of the heap.")

View File

@ -34,7 +34,7 @@
#include "gc/g1/heapRegion.inline.hpp"
#include "gc/g1/heapRegionBounds.inline.hpp"
#include "gc/g1/heapRegionManager.inline.hpp"
#include "gc/g1/heapRegionRemSet.hpp"
#include "gc/g1/heapRegionRemSet.inline.hpp"
#include "gc/g1/heapRegionTracer.hpp"
#include "gc/shared/genOopClosures.inline.hpp"
#include "logging/log.hpp"
@ -226,7 +226,8 @@ void HeapRegion::clear_humongous() {
HeapRegion::HeapRegion(uint hrm_index,
G1BlockOffsetTable* bot,
MemRegion mr) :
MemRegion mr,
G1CardSetConfiguration* config) :
_bottom(mr.start()),
_end(mr.end()),
_top(NULL),
@ -252,7 +253,7 @@ HeapRegion::HeapRegion(uint hrm_index,
assert(Universe::on_page_boundary(mr.start()) && Universe::on_page_boundary(mr.end()),
"invalid space boundaries");
_rem_set = new HeapRegionRemSet(bot, this);
_rem_set = new HeapRegionRemSet(this, config);
initialize();
}
@ -610,11 +611,12 @@ public:
if (!_failures) {
log.error("----------");
}
LogStream ls(log.error());
to->rem_set()->print_info(&ls, p);
log.error("Missing rem set entry:");
log.error("Field " PTR_FORMAT " of obj " PTR_FORMAT " in region " HR_FORMAT,
p2i(p), p2i(_containing_obj), HR_FORMAT_PARAMS(from));
ResourceMark rm;
LogStream ls(log.error());
_containing_obj->print_on(&ls);
log.error("points to obj " PTR_FORMAT " in region " HR_FORMAT " remset %s",
p2i(obj), HR_FORMAT_PARAMS(to), to->rem_set()->get_state_str());

View File

@ -36,6 +36,8 @@
#include "runtime/mutex.hpp"
#include "utilities/macros.hpp"
class G1CardSetConfiguration;
class G1CardSetMemoryManager;
class G1CollectedHeap;
class G1CMBitMap;
class G1Predictions;
@ -281,7 +283,10 @@ private:
// starting at p extending to at most the prev TAMS using the given mark bitmap.
inline size_t block_size_using_bitmap(const HeapWord* p, const G1CMBitMap* const prev_bitmap) const;
public:
HeapRegion(uint hrm_index, G1BlockOffsetTable* bot, MemRegion mr);
HeapRegion(uint hrm_index,
G1BlockOffsetTable* bot,
MemRegion mr,
G1CardSetConfiguration* config);
// If this region is a member of a HeapRegionManager, the index in that
// sequence, otherwise -1.
@ -445,6 +450,7 @@ public:
// Unsets the humongous-related fields on the region.
void clear_humongous();
void set_rem_set(HeapRegionRemSet* rem_set) { _rem_set = rem_set; }
// If the region has a remembered set, return a pointer to it.
HeapRegionRemSet* rem_set() const {
return _rem_set;

View File

@ -22,13 +22,14 @@
*
*/
#include <cstdio>
#include "precompiled.hpp"
#include "gc/g1/g1BlockOffsetTable.inline.hpp"
#include "gc/g1/g1CollectedHeap.inline.hpp"
#include "gc/g1/g1ConcurrentRefine.hpp"
#include "gc/g1/heapRegionManager.inline.hpp"
#include "gc/g1/heapRegionRemSet.inline.hpp"
#include "gc/g1/sparsePRT.inline.hpp"
#include "memory/allocation.hpp"
#include "memory/padded.inline.hpp"
#include "oops/oop.inline.hpp"
@ -44,371 +45,19 @@
const char* HeapRegionRemSet::_state_strings[] = {"Untracked", "Updating", "Complete"};
const char* HeapRegionRemSet::_short_state_strings[] = {"UNTRA", "UPDAT", "CMPLT"};
PerRegionTable* PerRegionTable::alloc(HeapRegion* hr) {
PerRegionTable* fl = _free_list;
while (fl != NULL) {
PerRegionTable* nxt = fl->next();
PerRegionTable* res = Atomic::cmpxchg(&_free_list, fl, nxt);
if (res == fl) {
fl->init(hr, true);
return fl;
} else {
fl = _free_list;
}
}
assert(fl == NULL, "Loop condition.");
return new PerRegionTable(hr);
}
PerRegionTable* volatile PerRegionTable::_free_list = NULL;
size_t OtherRegionsTable::_max_fine_entries = 0;
size_t OtherRegionsTable::_mod_max_fine_entries_mask = 0;
size_t OtherRegionsTable::_fine_eviction_stride = 0;
size_t OtherRegionsTable::_fine_eviction_sample_size = 0;
OtherRegionsTable::OtherRegionsTable(Mutex* m) :
_g1h(G1CollectedHeap::heap()),
_m(m),
_num_occupied(0),
_coarse_map(mtGC),
_has_coarse_entries(false),
_fine_grain_regions(NULL),
_n_fine_entries(0),
_first_all_fine_prts(NULL),
_last_all_fine_prts(NULL),
_fine_eviction_start(0),
_sparse_table()
{
typedef PerRegionTable* PerRegionTablePtr;
if (_max_fine_entries == 0) {
assert(_mod_max_fine_entries_mask == 0, "Both or none.");
size_t max_entries_log = (size_t)log2i(G1RSetRegionEntries);
_max_fine_entries = (size_t)1 << max_entries_log;
_mod_max_fine_entries_mask = _max_fine_entries - 1;
assert(_fine_eviction_sample_size == 0
&& _fine_eviction_stride == 0, "All init at same time.");
_fine_eviction_sample_size = MAX2((size_t)4, max_entries_log);
_fine_eviction_stride = _max_fine_entries / _fine_eviction_sample_size;
}
_fine_grain_regions = NEW_C_HEAP_ARRAY(PerRegionTablePtr, _max_fine_entries, mtGC);
for (size_t i = 0; i < _max_fine_entries; i++) {
_fine_grain_regions[i] = NULL;
}
}
void OtherRegionsTable::link_to_all(PerRegionTable* prt) {
// We always append to the beginning of the list for convenience;
// the order of entries in this list does not matter.
if (_first_all_fine_prts != NULL) {
prt->set_next(_first_all_fine_prts);
} else {
// this is the first element we insert. Adjust the "last" pointer
_last_all_fine_prts = prt;
assert(prt->next() == NULL, "just checking");
}
_first_all_fine_prts = prt;
assert(_first_all_fine_prts == prt, "just checking");
assert((_first_all_fine_prts == NULL && _last_all_fine_prts == NULL) ||
(_first_all_fine_prts != NULL && _last_all_fine_prts != NULL),
"just checking");
assert(_last_all_fine_prts == NULL || _last_all_fine_prts->next() == NULL,
"just checking");
}
CardIdx_t OtherRegionsTable::card_within_region(OopOrNarrowOopStar within_region, HeapRegion* hr) {
assert(hr->is_in_reserved(within_region),
"HeapWord " PTR_FORMAT " is outside of region %u [" PTR_FORMAT ", " PTR_FORMAT ")",
p2i(within_region), hr->hrm_index(), p2i(hr->bottom()), p2i(hr->end()));
CardIdx_t result = (CardIdx_t)(pointer_delta((HeapWord*)within_region, hr->bottom()) >> (CardTable::card_shift - LogHeapWordSize));
return result;
}
void OtherRegionsTable::add_reference(OopOrNarrowOopStar from, uint tid) {
// Note that this may be a continued H region.
HeapRegion* from_hr = _g1h->heap_region_containing(from);
RegionIdx_t from_hrm_ind = (RegionIdx_t) from_hr->hrm_index();
// If the region is already coarsened, return.
if (is_region_coarsened(from_hrm_ind)) {
assert(contains_reference(from), "We just found " PTR_FORMAT " in the Coarse table", p2i(from));
return;
}
size_t num_added_by_coarsening = 0;
// Otherwise find a per-region table to add it to.
size_t ind = from_hrm_ind & _mod_max_fine_entries_mask;
PerRegionTable* prt = find_region_table(ind, from_hr);
if (prt == NULL) {
MutexLocker x(_m, Mutex::_no_safepoint_check_flag);
// Rechecking if the region is coarsened, while holding the lock.
if (is_region_coarsened(from_hrm_ind)) {
assert(contains_reference_locked(from), "We just found " PTR_FORMAT " in the Coarse table", p2i(from));
return;
}
// Confirm that it's really not there...
prt = find_region_table(ind, from_hr);
if (prt == NULL) {
CardIdx_t card_index = card_within_region(from, from_hr);
SparsePRT::AddCardResult result = _sparse_table.add_card(from_hrm_ind, card_index);
if (result != SparsePRT::overflow) {
if (result == SparsePRT::added) {
Atomic::inc(&_num_occupied, memory_order_relaxed);
}
assert(contains_reference_locked(from), "We just added " PTR_FORMAT " to the Sparse table", p2i(from));
return;
}
// Sparse PRT returned overflow (sparse table is full)
if (_n_fine_entries == _max_fine_entries) {
prt = delete_region_table(num_added_by_coarsening);
// There is no need to clear the links to the 'all' list here:
// prt will be reused immediately, i.e. remain in the 'all' list.
prt->init(from_hr, false /* clear_links_to_all_list */);
} else {
prt = PerRegionTable::alloc(from_hr);
link_to_all(prt);
}
PerRegionTable* first_prt = _fine_grain_regions[ind];
prt->set_collision_list_next(first_prt);
// The assignment into _fine_grain_regions allows the prt to
// start being used concurrently. In addition to
// collision_list_next which must be visible (else concurrent
// parsing of the list, if any, may fail to see other entries),
// the content of the prt must be visible (else for instance
// some mark bits may not yet seem cleared or a 'later' update
// performed by a concurrent thread could be undone when the
// zeroing becomes visible). This requires store ordering.
Atomic::release_store(&_fine_grain_regions[ind], prt);
_n_fine_entries++;
// Transfer from sparse to fine-grain. The cards from the sparse table
// were already added to the total in _num_occupied.
SparsePRTEntry *sprt_entry = _sparse_table.get_entry(from_hrm_ind);
assert(sprt_entry != NULL, "There should have been an entry");
for (int i = 0; i < sprt_entry->num_valid_cards(); i++) {
CardIdx_t c = sprt_entry->card(i);
prt->add_card(c);
}
// Now we can delete the sparse entry.
bool res = _sparse_table.delete_entry(from_hrm_ind);
assert(res, "It should have been there.");
}
assert(prt != NULL && prt->hr() == from_hr, "consequence");
}
// Note that we can't assert "prt->hr() == from_hr", because of the
// possibility of concurrent reuse. But see head comment of
// OtherRegionsTable for why this is OK.
assert(prt != NULL, "Inv");
if (prt->add_reference(from)) {
num_added_by_coarsening++;
}
Atomic::add(&_num_occupied, num_added_by_coarsening, memory_order_relaxed);
assert(contains_reference(from), "We just added " PTR_FORMAT " to the PRT (%d)", p2i(from), prt->contains_reference(from));
}
PerRegionTable*
OtherRegionsTable::find_region_table(size_t ind, HeapRegion* hr) const {
assert(ind < _max_fine_entries, "Preconditions.");
PerRegionTable* prt = _fine_grain_regions[ind];
while (prt != NULL && prt->hr() != hr) {
prt = prt->collision_list_next();
}
// Loop postcondition is the method postcondition.
return prt;
}
jint OtherRegionsTable::_n_coarsenings = 0;
PerRegionTable* OtherRegionsTable::delete_region_table(size_t& added_by_deleted) {
assert(_m->owned_by_self(), "Precondition");
assert(_n_fine_entries == _max_fine_entries, "Precondition");
PerRegionTable* max = NULL;
jint max_occ = 0;
PerRegionTable** max_prev = NULL;
size_t i = _fine_eviction_start;
for (size_t k = 0; k < _fine_eviction_sample_size; k++) {
size_t ii = i;
// Make sure we get a non-NULL sample.
while (_fine_grain_regions[ii] == NULL) {
ii++;
if (ii == _max_fine_entries) ii = 0;
guarantee(ii != i, "We must find one.");
}
PerRegionTable** prev = &_fine_grain_regions[ii];
PerRegionTable* cur = *prev;
while (cur != NULL) {
jint cur_occ = cur->occupied();
if (max == NULL || cur_occ > max_occ) {
max = cur;
max_prev = prev;
max_occ = cur_occ;
}
prev = cur->collision_list_next_addr();
cur = cur->collision_list_next();
}
i = i + _fine_eviction_stride;
if (i >= _n_fine_entries) i = i - _n_fine_entries;
}
_fine_eviction_start++;
if (_fine_eviction_start >= _n_fine_entries) {
_fine_eviction_start -= _n_fine_entries;
}
guarantee(max != NULL, "Since _n_fine_entries > 0");
guarantee(max_prev != NULL, "Since max != NULL.");
// Ensure the corresponding coarse bit is set.
size_t max_hrm_index = (size_t) max->hr()->hrm_index();
if (Atomic::load(&_has_coarse_entries)) {
_coarse_map.at_put(max_hrm_index, true);
} else {
// This will lazily initialize an uninitialized bitmap
_coarse_map.reinitialize(G1CollectedHeap::heap()->max_reserved_regions());
assert(!_coarse_map.at(max_hrm_index), "No coarse entries");
_coarse_map.at_put(max_hrm_index, true);
// Release store guarantees that the bitmap has initialized before any
// concurrent reader will ever see _has_coarse_entries is true
// (when read with load_acquire)
Atomic::release_store(&_has_coarse_entries, true);
}
added_by_deleted = HeapRegion::CardsPerRegion - max_occ;
// Unsplice.
*max_prev = max->collision_list_next();
Atomic::inc(&_n_coarsenings);
_n_fine_entries--;
return max;
}
bool OtherRegionsTable::occupancy_less_or_equal_than(size_t limit) const {
return occupied() <= limit;
}
bool OtherRegionsTable::is_empty() const {
return occupied() == 0;
}
size_t OtherRegionsTable::occupied() const {
return _num_occupied;
}
size_t OtherRegionsTable::mem_size() const {
size_t sum = 0;
// all PRTs are of the same size so it is sufficient to query only one of them.
if (_first_all_fine_prts != NULL) {
assert(_last_all_fine_prts != NULL &&
_first_all_fine_prts->mem_size() == _last_all_fine_prts->mem_size(), "check that mem_size() is constant");
sum += _first_all_fine_prts->mem_size() * _n_fine_entries;
}
sum += (sizeof(PerRegionTable*) * _max_fine_entries);
sum += (_coarse_map.size_in_words() * HeapWordSize);
sum += (_sparse_table.mem_size());
sum += sizeof(OtherRegionsTable) - sizeof(_sparse_table); // Avoid double counting above.
return sum;
}
size_t OtherRegionsTable::static_mem_size() {
return G1FromCardCache::static_mem_size();
}
size_t OtherRegionsTable::fl_mem_size() {
return PerRegionTable::fl_mem_size();
}
void OtherRegionsTable::clear() {
// if there are no entries, skip this step
if (_first_all_fine_prts != NULL) {
guarantee(_first_all_fine_prts != NULL && _last_all_fine_prts != NULL, "just checking");
PerRegionTable::bulk_free(_first_all_fine_prts, _last_all_fine_prts);
memset(_fine_grain_regions, 0, _max_fine_entries * sizeof(_fine_grain_regions[0]));
} else {
guarantee(_first_all_fine_prts == NULL && _last_all_fine_prts == NULL, "just checking");
}
_first_all_fine_prts = _last_all_fine_prts = NULL;
_sparse_table.clear();
if (Atomic::load(&_has_coarse_entries)) {
_coarse_map.clear();
}
_n_fine_entries = 0;
Atomic::store(&_has_coarse_entries, false);
_num_occupied = 0;
}
bool OtherRegionsTable::contains_reference(OopOrNarrowOopStar from) const {
// Cast away const in this case.
MutexLocker x((Mutex*)_m, Mutex::_no_safepoint_check_flag);
return contains_reference_locked(from);
}
bool OtherRegionsTable::contains_reference_locked(OopOrNarrowOopStar from) const {
HeapRegion* hr = _g1h->heap_region_containing(from);
RegionIdx_t hr_ind = (RegionIdx_t) hr->hrm_index();
// Is this region in the coarse map?
if (is_region_coarsened(hr_ind)) return true;
PerRegionTable* prt = find_region_table(hr_ind & _mod_max_fine_entries_mask,
hr);
if (prt != NULL) {
return prt->contains_reference(from);
} else {
CardIdx_t card_index = card_within_region(from, hr);
return _sparse_table.contains_card(hr_ind, card_index);
}
}
// A load_acquire on _has_coarse_entries - coupled with the release_store in
// delete_region_table - guarantees we don't access _coarse_map before
// it's been properly initialized.
bool OtherRegionsTable::is_region_coarsened(RegionIdx_t from_hrm_ind) const {
return Atomic::load_acquire(&_has_coarse_entries) && _coarse_map.at(from_hrm_ind);
}
HeapRegionRemSet::HeapRegionRemSet(G1BlockOffsetTable* bot,
HeapRegion* hr)
: _bot(bot),
_code_roots(),
_m(Mutex::leaf, FormatBuffer<128>("HeapRegionRemSet lock #%u", hr->hrm_index()), true, Mutex::_safepoint_check_never),
_other_regions(&_m),
_hr(hr),
_state(Untracked)
{
}
HeapRegionRemSet::HeapRegionRemSet(HeapRegion* hr,
G1CardSetConfiguration* config) :
_m(Mutex::leaf + 1, FormatBuffer<128>("HeapRegionRemSet lock #%u", hr->hrm_index()), true, Monitor::_safepoint_check_never),
_code_roots(),
_card_set_mm(config, G1CardSetFreePool::free_list_pool()),
_card_set(config, &_card_set_mm),
_hr(hr),
_state(Untracked) { }
void HeapRegionRemSet::clear_fcc() {
G1FromCardCache::clear(_hr->hrm_index());
}
void HeapRegionRemSet::setup_remset_size() {
const int LOG_M = 20;
guarantee(HeapRegion::LogOfHRGrainBytes >= LOG_M, "Code assumes the region size >= 1M, but is " SIZE_FORMAT "B", HeapRegion::GrainBytes);
int region_size_log_mb = HeapRegion::LogOfHRGrainBytes - LOG_M;
if (FLAG_IS_DEFAULT(G1RSetSparseRegionEntries)) {
G1RSetSparseRegionEntries = G1RSetSparseRegionEntriesBase * ((size_t)1 << (region_size_log_mb + 1));
}
if (FLAG_IS_DEFAULT(G1RSetRegionEntries)) {
G1RSetRegionEntries = G1RSetRegionEntriesBase * (region_size_log_mb + 1);
}
guarantee(G1RSetSparseRegionEntries > 0 && G1RSetRegionEntries > 0 , "Sanity");
}
void HeapRegionRemSet::clear(bool only_cardset) {
MutexLocker x(&_m, Mutex::_no_safepoint_check_flag);
clear_locked(only_cardset);
@ -419,11 +68,15 @@ void HeapRegionRemSet::clear_locked(bool only_cardset) {
_code_roots.clear();
}
clear_fcc();
_other_regions.clear();
_card_set.clear();
set_state_empty();
assert(occupied() == 0, "Should be clear.");
}
void HeapRegionRemSet::print_static_mem_size(outputStream* out) {
out->print_cr(" Static structures = " SIZE_FORMAT, HeapRegionRemSet::static_mem_size());
}
// Code roots support
//
// The code root set is protected by two separate locking schemes

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2001, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2001, 2021, 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
@ -25,299 +25,62 @@
#ifndef SHARE_GC_G1_HEAPREGIONREMSET_HPP
#define SHARE_GC_G1_HEAPREGIONREMSET_HPP
#include "gc/g1/g1CardSet.hpp"
#include "gc/g1/g1CardSetMemory.hpp"
#include "gc/g1/g1CodeCacheRemSet.hpp"
#include "gc/g1/g1FromCardCache.hpp"
#include "gc/g1/sparsePRT.hpp"
#include "runtime/atomic.hpp"
#include "runtime/safepoint.hpp"
#include "utilities/bitMap.hpp"
// Remembered set for a heap region. Represent a set of "cards" that
// contain pointers into the owner heap region. Cards are defined somewhat
// abstractly, in terms of what the "BlockOffsetTable" in use can parse.
class G1CollectedHeap;
class G1BlockOffsetTable;
class G1CardLiveData;
class HeapRegion;
class PerRegionTable;
class SparsePRT;
class nmethod;
// The "_coarse_map" is a bitmap with one bit for each region, where set
// bits indicate that the corresponding region may contain some pointer
// into the owning region.
// The "_fine_grain_entries" array is an open hash table of PerRegionTables
// (PRTs), indicating regions for which we're keeping the RS as a set of
// cards. The strategy is to cap the size of the fine-grain table,
// deleting an entry and setting the corresponding coarse-grained bit when
// we would overflow this cap.
// We use a mixture of locking and lock-free techniques here. We allow
// threads to locate PRTs without locking, but threads attempting to alter
// a bucket list obtain a lock. This means that any failing attempt to
// find a PRT must be retried with the lock. It might seem dangerous that
// a read can find a PRT that is concurrently deleted. This is all right,
// because:
//
// 1) We only actually free PRT's at safe points (though we reuse them at
// other times).
// 2) We find PRT's in an attempt to add entries. If a PRT is deleted,
// it's _coarse_map bit is set, so the that we were attempting to add
// is represented. If a deleted PRT is re-used, a thread adding a bit,
// thinking the PRT is for a different region, does no harm.
class OtherRegionsTable {
G1CollectedHeap* _g1h;
Mutex* _m;
size_t volatile _num_occupied;
// These are protected by "_m".
CHeapBitMap _coarse_map;
bool volatile _has_coarse_entries;
static jint _n_coarsenings;
PerRegionTable** _fine_grain_regions;
size_t _n_fine_entries;
// The fine grain remembered sets are linked together using
// their 'next' fields.
// This allows fast bulk freeing of all the fine grain remembered
// set entries, and fast finding of all of them without iterating
// over the _fine_grain_regions table.
PerRegionTable * _first_all_fine_prts;
PerRegionTable * _last_all_fine_prts;
// Used to sample a subset of the fine grain PRTs to determine which
// PRT to evict and coarsen.
size_t _fine_eviction_start;
static size_t _fine_eviction_stride;
static size_t _fine_eviction_sample_size;
SparsePRT _sparse_table;
// These are static after init.
static size_t _max_fine_entries;
static size_t _mod_max_fine_entries_mask;
// Requires "prt" to be the first element of the bucket list appropriate
// for "hr". If this list contains an entry for "hr", return it,
// otherwise return "NULL".
PerRegionTable* find_region_table(size_t ind, HeapRegion* hr) const;
// Find, delete, and return a candidate PerRegionTable, if any exists,
// adding the deleted region to the coarse bitmap. Requires the caller
// to hold _m, and the fine-grain table to be full.
PerRegionTable* delete_region_table(size_t& added_by_deleted);
// link/add the given fine grain remembered set into the "all" list
void link_to_all(PerRegionTable * prt);
bool contains_reference_locked(OopOrNarrowOopStar from) const;
public:
// Create a new remembered set. The given mutex is used to ensure consistency.
OtherRegionsTable(Mutex* m);
template <class Closure>
void iterate(Closure& v);
// Returns the card index of the given within_region pointer relative to the bottom
// of the given heap region.
static CardIdx_t card_within_region(OopOrNarrowOopStar within_region, HeapRegion* hr);
// Adds the reference from "from to this remembered set.
void add_reference(OopOrNarrowOopStar from, uint tid);
// Returns whether the remembered set contains the given reference.
bool contains_reference(OopOrNarrowOopStar from) const;
// Returns whether this remembered set (and all sub-sets) have an occupancy
// that is less or equal than the given occupancy.
bool occupancy_less_or_equal_than(size_t limit) const;
// Returns whether this remembered set (and all sub-sets) does not contain any entry.
bool is_empty() const;
// Returns the number of cards contained in this remembered set.
size_t occupied() const;
static jint n_coarsenings() { return _n_coarsenings; }
// Returns size of the actual remembered set containers in bytes.
size_t mem_size() const;
// Returns the size of static data in bytes.
static size_t static_mem_size();
// Returns the size of the free list content in bytes.
static size_t fl_mem_size();
// Clear the entire contents of this remembered set.
void clear();
// Safe for use by concurrent readers outside _m
bool is_region_coarsened(RegionIdx_t from_hrm_ind) const;
};
class PerRegionTable: public CHeapObj<mtGC> {
friend class OtherRegionsTable;
HeapRegion* _hr;
CHeapBitMap _bm;
jint _occupied;
// next pointer for free/allocated 'all' list
PerRegionTable* _next;
// next pointer in collision list
PerRegionTable * _collision_list_next;
// Global free list of PRTs
static PerRegionTable* volatile _free_list;
protected:
PerRegionTable(HeapRegion* hr) :
_hr(hr),
_bm(HeapRegion::CardsPerRegion, mtGC),
_occupied(0),
_next(NULL),
_collision_list_next(NULL)
{}
public:
// We need access in order to union things into the base table.
BitMap* bm() { return &_bm; }
HeapRegion* hr() const { return Atomic::load_acquire(&_hr); }
jint occupied() const {
return _occupied;
}
void init(HeapRegion* hr, bool clear_links_to_all_list);
inline bool add_reference(OopOrNarrowOopStar from);
inline bool add_card(CardIdx_t from_card_index);
// (Destructively) union the bitmap of the current table into the given
// bitmap (which is assumed to be of the same size.)
void union_bitmap_into(BitMap* bm) {
bm->set_union(_bm);
}
// Mem size in bytes.
size_t mem_size() const {
return sizeof(PerRegionTable) + _bm.size_in_words() * HeapWordSize;
}
// Requires "from" to be in "hr()".
bool contains_reference(OopOrNarrowOopStar from) const {
assert(hr()->is_in_reserved(from), "Precondition.");
size_t card_ind = pointer_delta(from, hr()->bottom(),
G1CardTable::card_size);
return _bm.at(card_ind);
}
// Bulk-free the PRTs from prt to last, assumes that they are
// linked together using their _next field.
static void bulk_free(PerRegionTable* prt, PerRegionTable* last) {
while (true) {
PerRegionTable* fl = _free_list;
last->set_next(fl);
PerRegionTable* res = Atomic::cmpxchg(&_free_list, fl, prt);
if (res == fl) {
return;
}
}
ShouldNotReachHere();
}
static void free(PerRegionTable* prt) {
bulk_free(prt, prt);
}
// Returns an initialized PerRegionTable instance.
static PerRegionTable* alloc(HeapRegion* hr);
PerRegionTable* next() const { return _next; }
void set_next(PerRegionTable* next) { _next = next; }
// Accessor and Modification routines for the pointer for the
// singly linked collision list that links the PRTs within the
// OtherRegionsTable::_fine_grain_regions hash table.
//
PerRegionTable* collision_list_next() const {
return _collision_list_next;
}
void set_collision_list_next(PerRegionTable* next) {
_collision_list_next = next;
}
PerRegionTable** collision_list_next_addr() {
return &_collision_list_next;
}
static size_t fl_mem_size() {
PerRegionTable* cur = _free_list;
size_t res = 0;
while (cur != NULL) {
res += cur->mem_size();
cur = cur->next();
}
return res;
}
static void test_fl_mem_size();
};
class outputStream;
class HeapRegionRemSet : public CHeapObj<mtGC> {
friend class VMStructs;
private:
G1BlockOffsetTable* _bot;
Mutex _m;
// A set of code blobs (nmethods) whose code contains pointers into
// the region that owns this RSet.
G1CodeRootSet _code_roots;
Mutex _m;
G1CardSetMemoryManager _card_set_mm;
OtherRegionsTable _other_regions;
// The set of cards in the Java heap
G1CardSet _card_set;
HeapRegion* _hr;
inline void split_card(OopOrNarrowOopStar from, uint& card_region, uint& card_within_region) const;
void clear_fcc();
public:
HeapRegionRemSet(G1BlockOffsetTable* bot, HeapRegion* hr);
HeapRegionRemSet(HeapRegion* hr, G1CardSetConfiguration* config);
// Setup sparse and fine-grain tables sizes.
static void setup_remset_size();
bool cardset_is_empty() const {
return _card_set.is_empty();
}
bool is_empty() const {
return (strong_code_roots_list_length() == 0) && _other_regions.is_empty();
return (strong_code_roots_list_length() == 0) && cardset_is_empty();
}
bool occupancy_less_or_equal_than(size_t occ) const {
return (strong_code_roots_list_length() == 0) && _other_regions.occupancy_less_or_equal_than(occ);
return (strong_code_roots_list_length() == 0) && _card_set.occupancy_less_or_equal_to(occ);
}
// For each PRT in the card (remembered) set call one of the following methods
// of the given closure:
//
// set_full_region_dirty(uint region_idx) - pass the region index for coarse PRTs
// set_bitmap_dirty(uint region_idx, BitMap* bitmap) - pass the region index and bitmap for fine PRTs
// set_cards_dirty(uint region_idx, elem_t* cards, uint num_cards) - pass region index and cards for sparse PRTs
template <class Closure>
inline void iterate_prts(Closure& cl);
// Iterate the card based remembered set for merging them into the card table.
// The passed closure must be a CardOrRangeVisitor; we use a template parameter
// to pass it in to facilitate inlining as much as possible.
template <class CardOrRangeVisitor>
inline void iterate_for_merge(CardOrRangeVisitor& cl);
size_t occupied() {
return _other_regions.occupied();
return _card_set.occupied();
}
static jint n_coarsenings() { return OtherRegionsTable::n_coarsenings(); }
// Coarsening statistics since VM start.
static G1CardSetCoarsenStats coarsen_stats() { return G1CardSet::coarsen_stats(); }
private:
enum RemSetState {
@ -339,74 +102,42 @@ public:
bool is_updating() { return _state == Updating; }
bool is_complete() { return _state == Complete; }
void set_state_empty() {
guarantee(SafepointSynchronize::is_at_safepoint() || !is_tracked(), "Should only set to Untracked during safepoint but is %s.", get_state_str());
if (_state == Untracked) {
return;
}
clear_fcc();
_state = Untracked;
}
inline void set_state_empty();
inline void set_state_updating();
inline void set_state_complete();
void set_state_updating() {
guarantee(SafepointSynchronize::is_at_safepoint() && !is_tracked(), "Should only set to Updating from Untracked during safepoint but is %s", get_state_str());
clear_fcc();
_state = Updating;
}
void set_state_complete() {
clear_fcc();
_state = Complete;
}
void add_reference(OopOrNarrowOopStar from, uint tid) {
RemSetState state = _state;
if (state == Untracked) {
return;
}
uint cur_idx = _hr->hrm_index();
uintptr_t from_card = uintptr_t(from) >> CardTable::card_shift;
if (G1FromCardCache::contains_or_replace(tid, cur_idx, from_card)) {
assert(contains_reference(from), "We just found " PTR_FORMAT " in the FromCardCache", p2i(from));
return;
}
_other_regions.add_reference(from, tid);
}
inline void add_reference(OopOrNarrowOopStar from, uint tid);
// The region is being reclaimed; clear its remset, and any mention of
// entries for this region in other remsets.
void clear(bool only_cardset = false);
void clear_locked(bool only_cardset = false);
// The actual # of bytes this hr_remset takes up.
// Note also includes the strong code root set.
G1CardSetMemoryStats card_set_memory_stats() const { return _card_set_mm.memory_stats(); }
// The actual # of bytes this hr_remset takes up. Also includes the strong code
// root set.
size_t mem_size() {
MutexLocker x(&_m, Mutex::_no_safepoint_check_flag);
return _other_regions.mem_size()
// This correction is necessary because the above includes the second
// part.
+ (sizeof(HeapRegionRemSet) - sizeof(OtherRegionsTable))
return _card_set.mem_size()
+ (sizeof(HeapRegionRemSet) - sizeof(G1CardSet)) // Avoid double-counting G1CardSet.
+ strong_code_roots_mem_size();
}
size_t wasted_mem_size() {
return _card_set.wasted_mem_size();
}
// Returns the memory occupancy of all static data structures associated
// with remembered sets.
static size_t static_mem_size() {
return OtherRegionsTable::static_mem_size() + G1CodeRootSet::static_mem_size();
return G1CardSet::static_mem_size() + G1CodeRootSet::static_mem_size() + sizeof(G1CardSetFreePool);
}
// Returns the memory occupancy of all free_list data structures associated
// with remembered sets.
static size_t fl_mem_size() {
return OtherRegionsTable::fl_mem_size();
}
static void print_static_mem_size(outputStream* out);
bool contains_reference(OopOrNarrowOopStar from) const {
return _other_regions.contains_reference(from);
}
inline bool contains_reference(OopOrNarrowOopStar from);
inline void print_info(outputStream* st, OopOrNarrowOopStar from);
// Routines for managing the list of code roots that point into
// the heap region that owns this RSet.

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2020, 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
@ -27,74 +27,86 @@
#include "gc/g1/heapRegionRemSet.hpp"
#include "gc/g1/g1CardSet.inline.hpp"
#include "gc/g1/g1CollectedHeap.inline.hpp"
#include "gc/g1/heapRegion.inline.hpp"
#include "gc/g1/sparsePRT.hpp"
#include "runtime/atomic.hpp"
#include "utilities/bitMap.inline.hpp"
template <class Closure>
inline void HeapRegionRemSet::iterate_prts(Closure& cl) {
_other_regions.iterate(cl);
void HeapRegionRemSet::set_state_empty() {
guarantee(SafepointSynchronize::is_at_safepoint() || !is_tracked(),
"Should only set to Untracked during safepoint but is %s.", get_state_str());
if (_state == Untracked) {
return;
}
clear_fcc();
_state = Untracked;
}
inline bool PerRegionTable::add_card(CardIdx_t from_card_index) {
if (_bm.par_set_bit(from_card_index)) {
Atomic::inc(&_occupied, memory_order_relaxed);
return true;
}
return false;
void HeapRegionRemSet::set_state_updating() {
guarantee(SafepointSynchronize::is_at_safepoint() && !is_tracked(),
"Should only set to Updating from Untracked during safepoint but is %s", get_state_str());
clear_fcc();
_state = Updating;
}
inline bool PerRegionTable::add_reference(OopOrNarrowOopStar from) {
// Must make this robust in case "from" is not in "_hr", because of
// concurrency.
HeapRegion* loc_hr = hr();
// If the test below fails, then this table was reused concurrently
// with this operation. This is OK, since the old table was coarsened,
// and adding a bit to the new table is never incorrect.
if (loc_hr->is_in_reserved(from)) {
CardIdx_t from_card = OtherRegionsTable::card_within_region(from, loc_hr);
return add_card(from_card);
}
return false;
void HeapRegionRemSet::set_state_complete() {
clear_fcc();
_state = Complete;
}
inline void PerRegionTable::init(HeapRegion* hr, bool clear_links_to_all_list) {
if (clear_links_to_all_list) {
set_next(NULL);
}
_collision_list_next = NULL;
_occupied = 0;
_bm.clear();
// Make sure that the bitmap clearing above has been finished before publishing
// this PRT to concurrent threads.
Atomic::release_store(&_hr, hr);
template <class CardOrRangeVisitor>
inline void HeapRegionRemSet::iterate_for_merge(CardOrRangeVisitor& cl) {
_card_set.iterate_for_merge(cl);
}
template <class Closure>
void OtherRegionsTable::iterate(Closure& cl) {
if (Atomic::load(&_has_coarse_entries)) {
BitMap::idx_t cur = _coarse_map.get_next_one_offset(0);
while (cur != _coarse_map.size()) {
cl.next_coarse_prt((uint)cur);
cur = _coarse_map.get_next_one_offset(cur + 1);
}
void HeapRegionRemSet::split_card(OopOrNarrowOopStar from, uint& card_region, uint& card_within_region) const {
HeapRegion* hr = G1CollectedHeap::heap()->heap_region_containing(from);
card_region = hr->hrm_index();
card_within_region = (uint)(pointer_delta((HeapWord*)from, hr->bottom()) >> (CardTable::card_shift - LogHeapWordSize));
}
void HeapRegionRemSet::add_reference(OopOrNarrowOopStar from, uint tid) {
RemSetState state = _state;
if (state == Untracked) {
return;
}
{
PerRegionTable* cur = _first_all_fine_prts;
while (cur != NULL) {
cl.next_fine_prt(cur->hr()->hrm_index(), cur->bm());
cur = cur->next();
}
}
{
SparsePRTBucketIter iter(&_sparse_table);
SparsePRTEntry* cur;
while (iter.has_next(cur)) {
cl.next_sparse_prt(cur->r_ind(), cur->cards(), cur->num_valid_cards());
}
uint cur_idx = _hr->hrm_index();
uintptr_t from_card = uintptr_t(from) >> CardTable::card_shift;
if (G1FromCardCache::contains_or_replace(tid, cur_idx, from_card)) {
// We can't check whether the card is in the remembered set - the card container
// may be coarsened just now.
//assert(contains_reference(from), "We just found " PTR_FORMAT " in the FromCardCache", p2i(from));
return;
}
uint card_region;
uint card_within_region;
split_card(from, card_region, card_within_region);
_card_set.add_card(card_region, card_within_region);
}
bool HeapRegionRemSet::contains_reference(OopOrNarrowOopStar from) {
uint card_region;
uint card_within_region;
split_card(from, card_region, card_within_region);
return _card_set.contains_card(card_region, card_within_region);
}
void HeapRegionRemSet::print_info(outputStream* st, OopOrNarrowOopStar from) {
uint card_region;
uint card_within_region;
split_card(from, card_region, card_within_region);
_card_set.print_info(st, card_region, card_within_region);
}
#endif // SHARE_VM_GC_G1_HEAPREGIONREMSET_INLINE_HPP

View File

@ -25,7 +25,7 @@
#include "precompiled.hpp"
#include "gc/g1/g1CollectedHeap.inline.hpp"
#include "gc/g1/g1NUMA.hpp"
#include "gc/g1/heapRegionRemSet.hpp"
#include "gc/g1/heapRegionRemSet.inline.hpp"
#include "gc/g1/heapRegionSet.inline.hpp"
uint FreeRegionList::_unrealistically_long_length = 0;

View File

@ -28,15 +28,15 @@
#include "runtime/globals_extension.hpp"
#include "utilities/globalDefinitions.hpp"
JVMFlag::Error G1RSetRegionEntriesConstraintFunc(intx value, bool verbose) {
JVMFlag::Error G1RemSetArrayOfCardsEntriesConstraintFunc(uint value, bool verbose) {
if (!UseG1GC) return JVMFlag::SUCCESS;
// Default value of G1RSetRegionEntries=0 means will be set ergonomically.
// Default value of G1RemSetArrayOfCardsEntries=0 means will be set ergonomically.
// Minimum value is 1.
if (FLAG_IS_CMDLINE(G1RSetRegionEntries) && (value < 1)) {
if (FLAG_IS_CMDLINE(G1RemSetArrayOfCardsEntries) && (value < 1)) {
JVMFlag::printError(verbose,
"G1RSetRegionEntries (" INTX_FORMAT ") must be "
"greater than or equal to 1\n",
"G1RemSetArrayOfCardsEntries (%u) must be "
"greater than or equal to 1.\n",
value);
return JVMFlag::VIOLATES_CONSTRAINT;
} else {
@ -44,20 +44,35 @@ JVMFlag::Error G1RSetRegionEntriesConstraintFunc(intx value, bool verbose) {
}
}
JVMFlag::Error G1RSetSparseRegionEntriesConstraintFunc(intx value, bool verbose) {
JVMFlag::Error G1RemSetHowlNumBucketsConstraintFunc(uint value, bool verbose) {
if (!UseG1GC) return JVMFlag::SUCCESS;
// Default value of G1RSetSparseRegionEntries=0 means will be set ergonomically.
// Minimum value is 1.
if (FLAG_IS_CMDLINE(G1RSetSparseRegionEntries) && (value < 1)) {
JVMFlag::printError(verbose,
"G1RSetSparseRegionEntries (" INTX_FORMAT ") must be "
"greater than or equal to 1\n",
value);
return JVMFlag::VIOLATES_CONSTRAINT;
} else {
if (!FLAG_IS_CMDLINE(G1RemSetHowlNumBuckets)) {
return JVMFlag::SUCCESS;
}
if (value == 0 || !is_power_of_2(G1RemSetHowlNumBuckets)) {
JVMFlag::printError(verbose,
"G1RemSetHowlNumBuckets (%u) must be a power of two "
"and greater than or equal to 1.\n",
value);
return JVMFlag::VIOLATES_CONSTRAINT;
}
return JVMFlag::SUCCESS;
}
JVMFlag::Error G1RemSetHowlMaxNumBucketsConstraintFunc(uint value, bool verbose) {
if (!UseG1GC) return JVMFlag::SUCCESS;
if (!FLAG_IS_CMDLINE(G1RemSetHowlMaxNumBuckets)) {
return JVMFlag::SUCCESS;
}
if (!is_power_of_2(G1RemSetHowlMaxNumBuckets)) {
JVMFlag::printError(verbose,
"G1RemSetMaxHowlNumBuckets (%u) must be a power of two.\n",
value);
return JVMFlag::VIOLATES_CONSTRAINT;
}
return JVMFlag::SUCCESS;
}
JVMFlag::Error G1HeapRegionSizeConstraintFunc(size_t value, bool verbose) {

View File

@ -30,9 +30,12 @@
#define G1_GC_CONSTRAINTS(f) \
\
/* G1 Flag Constraints */ \
f(intx, G1RSetRegionEntriesConstraintFunc) \
f(intx, G1RSetSparseRegionEntriesConstraintFunc) \
/* G1 Remembered Sets Constraints */ \
f(uint, G1RemSetArrayOfCardsEntriesConstraintFunc)\
f(uint, G1RemSetHowlMaxNumBucketsConstraintFunc) \
f(uint, G1RemSetHowlNumBucketsConstraintFunc) \
\
/* G1 Heap Size Constraints */ \
f(size_t, G1HeapRegionSizeConstraintFunc) \
f(uintx, G1NewSizePercentConstraintFunc) \
f(uintx, G1MaxNewSizePercentConstraintFunc) \

View File

@ -1,311 +0,0 @@
/*
* Copyright (c) 2001, 2019, 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 "gc/g1/heapRegion.hpp"
#include "gc/g1/heapRegionBounds.inline.hpp"
#include "gc/g1/heapRegionRemSet.hpp"
#include "gc/g1/sparsePRT.hpp"
#include "gc/shared/cardTableBarrierSet.hpp"
#include "gc/shared/space.inline.hpp"
#include "memory/allocation.inline.hpp"
// Check that the size of the SparsePRTEntry is evenly divisible by the maximum
// member type to avoid SIGBUS when accessing them.
STATIC_ASSERT(sizeof(SparsePRTEntry) % sizeof(int) == 0);
void SparsePRTEntry::init(RegionIdx_t region_ind) {
// Check that the card array element type can represent all cards in the region.
// Choose a large SparsePRTEntry::card_elem_t (e.g. CardIdx_t) if required.
assert(((size_t)1 << (sizeof(SparsePRTEntry::card_elem_t) * BitsPerByte)) *
G1CardTable::card_size >= HeapRegionBounds::max_size(), "precondition");
assert(G1RSetSparseRegionEntries > 0, "precondition");
_region_ind = region_ind;
_next_index = RSHashTable::NullEntry;
_next_null = 0;
}
bool SparsePRTEntry::contains_card(CardIdx_t card_index) const {
for (int i = 0; i < num_valid_cards(); i++) {
if (card(i) == card_index) {
return true;
}
}
return false;
}
SparsePRT::AddCardResult SparsePRTEntry::add_card(CardIdx_t card_index) {
for (int i = 0; i < num_valid_cards(); i++) {
if (card(i) == card_index) {
return SparsePRT::found;
}
}
if (num_valid_cards() < cards_num() - 1) {
_cards[_next_null] = (card_elem_t)card_index;
_next_null++;
return SparsePRT::added;
}
// Otherwise, we're full.
return SparsePRT::overflow;
}
void SparsePRTEntry::copy_cards(card_elem_t* cards) const {
memcpy(cards, _cards, cards_num() * sizeof(card_elem_t));
}
void SparsePRTEntry::copy_cards(SparsePRTEntry* e) const {
copy_cards(e->_cards);
assert(_next_null >= 0, "invariant");
assert(_next_null <= cards_num(), "invariant");
e->_next_null = _next_null;
}
// ----------------------------------------------------------------------
float RSHashTable::TableOccupancyFactor = 0.5f;
// The empty table can't hold any entries and is effectively immutable
// This means it can be used as an initial sentinel value
static int empty_buckets[] = { RSHashTable::NullEntry };
RSHashTable RSHashTable::empty_table;
RSHashTable::RSHashTable() :
_num_entries(0),
_capacity(0),
_capacity_mask(0),
_occupied_entries(0),
_entries(NULL),
_buckets(empty_buckets),
_free_region(0),
_free_list(NullEntry) { }
RSHashTable::RSHashTable(size_t capacity) :
_num_entries((capacity * TableOccupancyFactor) + 1),
_capacity(capacity),
_capacity_mask(capacity - 1),
_occupied_entries(0),
_entries((SparsePRTEntry*)NEW_C_HEAP_ARRAY(char, _num_entries * SparsePRTEntry::size(), mtGC)),
_buckets(NEW_C_HEAP_ARRAY(int, capacity, mtGC)),
_free_region(0),
_free_list(NullEntry)
{
clear();
}
RSHashTable::~RSHashTable() {
// Nothing to free for empty RSHashTable
if (_buckets != empty_buckets) {
assert(_entries != NULL, "invariant");
FREE_C_HEAP_ARRAY(SparsePRTEntry, _entries);
FREE_C_HEAP_ARRAY(int, _buckets);
}
}
void RSHashTable::clear() {
assert(_buckets != empty_buckets, "Shouldn't call this for the empty_table");
_occupied_entries = 0;
guarantee(_entries != NULL, "invariant");
guarantee(_buckets != NULL, "invariant");
guarantee(_capacity <= ((size_t)1 << (sizeof(int)*BitsPerByte-1)) - 1,
"_capacity too large");
// This will put -1 == NullEntry in the key field of all entries.
memset((void*)_entries, NullEntry, _num_entries * SparsePRTEntry::size());
memset((void*)_buckets, NullEntry, _capacity * sizeof(int));
_free_list = NullEntry;
_free_region = 0;
}
SparsePRT::AddCardResult RSHashTable::add_card(RegionIdx_t region_ind, CardIdx_t card_index) {
assert(this != &empty_table, "can't add a card to the empty table");
SparsePRTEntry* e = entry_for_region_ind_create(region_ind);
assert(e != NULL && e->r_ind() == region_ind,
"Postcondition of call above.");
SparsePRT::AddCardResult res = e->add_card(card_index);
assert(e->num_valid_cards() > 0, "Postcondition");
return res;
}
SparsePRTEntry* RSHashTable::get_entry(RegionIdx_t region_ind) const {
int ind = (int) (region_ind & capacity_mask());
int cur_ind = _buckets[ind];
SparsePRTEntry* cur;
while (cur_ind != NullEntry &&
(cur = entry(cur_ind))->r_ind() != region_ind) {
cur_ind = cur->next_index();
}
if (cur_ind == NullEntry) return NULL;
// Otherwise...
assert(cur->r_ind() == region_ind, "Postcondition of loop + test above.");
assert(cur->num_valid_cards() > 0, "Inv");
return cur;
}
bool RSHashTable::delete_entry(RegionIdx_t region_ind) {
int ind = (int) (region_ind & capacity_mask());
int* prev_loc = &_buckets[ind];
int cur_ind = *prev_loc;
SparsePRTEntry* cur;
while (cur_ind != NullEntry &&
(cur = entry(cur_ind))->r_ind() != region_ind) {
prev_loc = cur->next_index_addr();
cur_ind = *prev_loc;
}
if (cur_ind == NullEntry) return false;
// Otherwise, splice out "cur".
*prev_loc = cur->next_index();
free_entry(cur_ind);
_occupied_entries--;
return true;
}
SparsePRTEntry*
RSHashTable::entry_for_region_ind_create(RegionIdx_t region_ind) {
SparsePRTEntry* res = get_entry(region_ind);
if (res == NULL) {
int new_ind = alloc_entry();
res = entry(new_ind);
res->init(region_ind);
// Insert at front.
int ind = (int) (region_ind & capacity_mask());
res->set_next_index(_buckets[ind]);
_buckets[ind] = new_ind;
_occupied_entries++;
}
return res;
}
int RSHashTable::alloc_entry() {
int res;
if (_free_list != NullEntry) {
res = _free_list;
_free_list = entry(res)->next_index();
return res;
} else if ((size_t)_free_region < _num_entries) {
res = _free_region;
_free_region++;
return res;
} else {
return NullEntry;
}
}
void RSHashTable::free_entry(int fi) {
entry(fi)->set_next_index(_free_list);
_free_list = fi;
}
void RSHashTable::add_entry(SparsePRTEntry* e) {
assert(e->num_valid_cards() > 0, "Precondition.");
SparsePRTEntry* e2 = entry_for_region_ind_create(e->r_ind());
e->copy_cards(e2);
assert(e2->num_valid_cards() > 0, "Postcondition.");
}
bool RSHashTableBucketIter::has_next(SparsePRTEntry*& entry) {
while (_bl_ind == RSHashTable::NullEntry) {
if (_tbl_ind + 1 >= _rsht->capacity()) {
return false;
}
_tbl_ind++;
_bl_ind = _rsht->_buckets[_tbl_ind];
}
entry = _rsht->entry(_bl_ind);
_bl_ind = entry->next_index();
return true;
}
bool RSHashTable::contains_card(RegionIdx_t region_index, CardIdx_t card_index) const {
SparsePRTEntry* e = get_entry(region_index);
return (e != NULL && e->contains_card(card_index));
}
size_t RSHashTable::mem_size() const {
return sizeof(RSHashTable) +
_num_entries * (SparsePRTEntry::size() + sizeof(int));
}
// ----------------------------------------------------------------------
SparsePRT::SparsePRT() :
_table(&RSHashTable::empty_table) {
}
SparsePRT::~SparsePRT() {
if (_table != &RSHashTable::empty_table) {
delete _table;
}
}
size_t SparsePRT::mem_size() const {
// We ignore "_cur" here, because it either = _next, or else it is
// on the deleted list.
return sizeof(SparsePRT) + _table->mem_size();
}
SparsePRT::AddCardResult SparsePRT::add_card(RegionIdx_t region_id, CardIdx_t card_index) {
if (_table->should_expand()) {
expand();
}
return _table->add_card(region_id, card_index);
}
SparsePRTEntry* SparsePRT::get_entry(RegionIdx_t region_id) {
return _table->get_entry(region_id);
}
bool SparsePRT::delete_entry(RegionIdx_t region_id) {
return _table->delete_entry(region_id);
}
void SparsePRT::clear() {
// If the entry table not at initial capacity, just reset to the empty table.
if (_table->capacity() == InitialCapacity) {
_table->clear();
} else if (_table != &RSHashTable::empty_table) {
delete _table;
_table = &RSHashTable::empty_table;
}
}
void SparsePRT::expand() {
RSHashTable* last = _table;
if (last != &RSHashTable::empty_table) {
_table = new RSHashTable(last->capacity() * 2);
for (size_t i = 0; i < last->num_entries(); i++) {
SparsePRTEntry* e = last->entry((int)i);
if (e->valid_entry()) {
_table->add_entry(e);
}
}
delete last;
} else {
_table = new RSHashTable(InitialCapacity);
}
}

View File

@ -1,246 +0,0 @@
/*
* Copyright (c) 2001, 2019, 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.
*
*/
#ifndef SHARE_GC_G1_SPARSEPRT_HPP
#define SHARE_GC_G1_SPARSEPRT_HPP
#include "gc/g1/g1CollectedHeap.hpp"
#include "gc/g1/heapRegion.hpp"
#include "gc/shared/cardTableBarrierSet.hpp"
#include "memory/allocation.hpp"
#include "runtime/mutex.hpp"
#include "utilities/align.hpp"
#include "utilities/globalDefinitions.hpp"
class RSHashTable;
class SparsePRTEntry;
// Sparse remembered set for a heap region (the "owning" region). Maps
// indices of other regions to short sequences of cards in the other region
// that might contain pointers into the owner region.
// Concurrent access to a SparsePRT must be serialized by some external mutex.
class SparsePRT {
friend class SparsePRTBucketIter;
RSHashTable* _table;
static const size_t InitialCapacity = 8;
void expand();
public:
SparsePRT();
~SparsePRT();
size_t mem_size() const;
enum AddCardResult {
overflow, // The table is full, could not add the card to the table.
found, // The card is already in the PRT.
added // The card has been added.
};
// Attempts to ensure that the given card_index in the given region is in
// the sparse table. If successful (because the card was already
// present, or because it was successfully added) returns "true".
// Otherwise, returns "false" to indicate that the addition would
// overflow the entry for the region. The caller must transfer these
// entries to a larger-capacity representation.
AddCardResult add_card(RegionIdx_t region_id, CardIdx_t card_index);
// Return the pointer to the entry associated with the given region.
SparsePRTEntry* get_entry(RegionIdx_t region_ind);
// If there is an entry for "region_ind", removes it and return "true";
// otherwise returns "false."
bool delete_entry(RegionIdx_t region_ind);
// Clear the table, and reinitialize to initial capacity.
void clear();
bool contains_card(RegionIdx_t region_id, CardIdx_t card_index) const;
};
class SparsePRTEntry: public CHeapObj<mtGC> {
public:
// The type of a card entry.
typedef uint16_t card_elem_t;
private:
// We need to make sizeof(SparsePRTEntry) an even multiple of maximum member size,
// in order to force correct alignment that could otherwise cause SIGBUS errors
// when reading the member variables. This calculates the minimum number of card
// array elements required to get that alignment.
static const size_t card_array_alignment = sizeof(int) / sizeof(card_elem_t);
RegionIdx_t _region_ind;
int _next_index;
int _next_null;
// The actual cards stored in this array.
// WARNING: Don't put any data members beyond this line. Card array has, in fact, variable length.
// It should always be the last data member.
card_elem_t _cards[card_array_alignment];
// Copy the current entry's cards into "cards".
inline void copy_cards(card_elem_t* cards) const;
public:
// Returns the size of the entry, used for entry allocation.
static size_t size() { return sizeof(SparsePRTEntry) + sizeof(card_elem_t) * (cards_num() - card_array_alignment); }
// Returns the size of the card array.
static int cards_num() {
return align_up((int)G1RSetSparseRegionEntries, (int)card_array_alignment);
}
// Set the region_ind to the given value, and delete all cards.
inline void init(RegionIdx_t region_ind);
RegionIdx_t r_ind() const { return _region_ind; }
bool valid_entry() const { return r_ind() >= 0; }
int next_index() const { return _next_index; }
int* next_index_addr() { return &_next_index; }
void set_next_index(int ni) { _next_index = ni; }
// Returns "true" iff the entry contains the given card index.
inline bool contains_card(CardIdx_t card_index) const;
// Returns the number of non-NULL card entries.
inline int num_valid_cards() const { return _next_null; }
inline SparsePRT::AddCardResult add_card(CardIdx_t card_index);
// Copy the current entry's cards into the "_card" array of "e."
inline void copy_cards(SparsePRTEntry* e) const;
card_elem_t* cards() { return _cards; }
inline CardIdx_t card(int i) const {
assert(i >= 0, "must be nonnegative");
assert(i < cards_num(), "range checking");
return (CardIdx_t)_cards[i];
}
};
class RSHashTable : public CHeapObj<mtGC> {
friend class RSHashTableBucketIter;
// Inverse maximum hash table occupancy used.
static float TableOccupancyFactor;
size_t _num_entries;
size_t _capacity;
size_t _capacity_mask;
size_t _occupied_entries;
SparsePRTEntry* _entries;
int* _buckets;
int _free_region;
int _free_list;
// Requires that the caller hold a lock preventing parallel modifying
// operations, and that the the table be less than completely full. If
// an entry for "region_ind" is already in the table, finds it and
// returns its address; otherwise allocates, initializes, inserts and
// returns a new entry for "region_ind".
SparsePRTEntry* entry_for_region_ind_create(RegionIdx_t region_ind);
// Returns the index of the next free entry in "_entries".
int alloc_entry();
// Declares the entry "fi" to be free. (It must have already been
// deleted from any bucket lists.
void free_entry(int fi);
// For the empty sentinel created at static initialization time
RSHashTable();
public:
RSHashTable(size_t capacity);
~RSHashTable();
static const int NullEntry = -1;
static RSHashTable empty_table;
bool should_expand() const { return _occupied_entries == _num_entries; }
// Attempts to ensure that the given card_index in the given region is in
// the sparse table. If successful (because the card was already
// present, or because it was successfully added) returns "true".
// Otherwise, returns "false" to indicate that the addition would
// overflow the entry for the region. The caller must transfer these
// entries to a larger-capacity representation.
SparsePRT::AddCardResult add_card(RegionIdx_t region_id, CardIdx_t card_index);
bool delete_entry(RegionIdx_t region_id);
bool contains_card(RegionIdx_t region_id, CardIdx_t card_index) const;
void add_entry(SparsePRTEntry* e);
SparsePRTEntry* get_entry(RegionIdx_t region_id) const;
void clear();
size_t capacity() const { return _capacity; }
size_t capacity_mask() const { return _capacity_mask; }
size_t mem_size() const;
// The number of SparsePRTEntry instances available.
size_t num_entries() const { return _num_entries; }
SparsePRTEntry* entry(int i) const {
assert(i >= 0 && (size_t)i < _num_entries, "precondition");
return (SparsePRTEntry*)((char*)_entries + SparsePRTEntry::size() * i);
}
void print();
};
// This is embedded in HRRS iterator.
class RSHashTableBucketIter {
uint _tbl_ind; // [0.._rsht->_capacity)
int _bl_ind; // [-1, 0.._rsht->_capacity)
RSHashTable* _rsht;
public:
RSHashTableBucketIter(RSHashTable* rsht) :
_tbl_ind(0),
_bl_ind(rsht->_buckets[_tbl_ind]),
_rsht(rsht) { }
bool has_next(SparsePRTEntry*& entry);
};
class SparsePRTBucketIter: public RSHashTableBucketIter {
public:
SparsePRTBucketIter(const SparsePRT* sprt) :
RSHashTableBucketIter(sprt->_table) {}
bool has_next(SparsePRTEntry*& entry) {
return RSHashTableBucketIter::has_next(entry);
}
};
#endif // SHARE_GC_G1_SPARSEPRT_HPP

View File

@ -235,8 +235,8 @@ public:
card_size_in_words = card_size / sizeof(HeapWord)
};
static CardValue clean_card_val() { return clean_card; }
static CardValue dirty_card_val() { return dirty_card; }
static constexpr CardValue clean_card_val() { return clean_card; }
static constexpr CardValue dirty_card_val() { return dirty_card; }
static intptr_t clean_card_row_val() { return clean_card_row; }
// Card marking array base (adjusted for heap low boundary)

View File

@ -34,7 +34,7 @@ template <class T>
class WorkerDataArray : public CHeapObj<mtGC> {
friend class WDAPrinter;
public:
static const uint MaxThreadWorkItems = 6;
static const uint MaxThreadWorkItems = 9;
private:
T* _data;
uint _length;

View File

@ -124,6 +124,7 @@ class AllocatedObj {
f(mtThreadStack, "Thread Stack") \
f(mtCode, "Code") /* generated code */ \
f(mtGC, "GC") \
f(mtGCCardSet, "GCCardSet") /* G1 card set remembered set */ \
f(mtCompiler, "Compiler") \
f(mtJVMCI, "JVMCI") \
f(mtInternal, "Internal") /* memory used by VM, but does not belong to */ \

View File

@ -99,7 +99,7 @@
#include "gc/g1/g1ConcurrentMark.hpp"
#include "gc/g1/g1ConcurrentMarkThread.inline.hpp"
#include "gc/g1/heapRegionManager.hpp"
#include "gc/g1/heapRegionRemSet.hpp"
#include "gc/g1/heapRegionRemSet.inline.hpp"
#endif // INCLUDE_G1GC
#if INCLUDE_PARALLELGC
#include "gc/parallel/parallelScavengeHeap.inline.hpp"

View File

@ -533,6 +533,8 @@ static SpecialFlag const special_jvm_flags[] = {
// -------------- Obsolete Flags - sorted by expired_in --------------
{ "CriticalJNINatives", JDK_Version::jdk(16), JDK_Version::jdk(18), JDK_Version::jdk(19) },
{ "G1RSetRegionEntries", JDK_Version::undefined(), JDK_Version::jdk(18), JDK_Version::jdk(19) },
{ "G1RSetSparseRegionEntries", JDK_Version::undefined(), JDK_Version::jdk(18), JDK_Version::jdk(19) },
{ "AlwaysLockClassLoader", JDK_Version::jdk(17), JDK_Version::jdk(18), JDK_Version::jdk(19) },
{ "UseBiasedLocking", JDK_Version::jdk(15), JDK_Version::jdk(18), JDK_Version::jdk(19) },
{ "BiasedLockingStartupDelay", JDK_Version::jdk(15), JDK_Version::jdk(18), JDK_Version::jdk(19) },

View File

@ -189,6 +189,10 @@ class ConcurrentHashTable : public CHeapObj<F> {
Bucket* get_buckets() { return _buckets; }
Bucket* get_bucket(size_t idx) { return &_buckets[idx]; }
size_t get_mem_size() {
return sizeof(*this) + _size * sizeof(Bucket);
}
};
// For materializing a supplied value.
@ -384,8 +388,10 @@ class ConcurrentHashTable : public CHeapObj<F> {
TableRateStatistics _stats_rate;
size_t get_mem_size(Thread* thread);
size_t get_size_log2(Thread* thread);
size_t get_node_size() const { return sizeof(Node); }
static size_t get_node_size() { return sizeof(Node); }
bool is_max_size_reached() { return _size_limit_reached; }
// This means no paused bucket resize operation is going to resume

View File

@ -37,8 +37,8 @@
// 2^30 = 1G buckets
#define SIZE_BIG_LOG2 30
// 2^5 = 32 buckets
#define SIZE_SMALL_LOG2 5
// 2^2 = 4 buckets
#define SIZE_SMALL_LOG2 2
// Number from spinYield.hpp. In some loops SpinYield would be unfair.
#define SPINPAUSES_PER_YIELD 8192
@ -817,10 +817,7 @@ inline bool ConcurrentHashTable<CONFIG, F>::
}
_new_table = new InternalTable(_table->_log2_size + 1);
if (_new_table->_log2_size == _log2_size_limit) {
_size_limit_reached = true;
}
_size_limit_reached = _new_table->_log2_size == _log2_size_limit;
return true;
}
@ -954,6 +951,7 @@ inline bool ConcurrentHashTable<CONFIG, F>::
{
Node* current_node = bucket->first();
while (current_node != NULL) {
Prefetch::read(current_node->next(), 0);
if (!visitor_f(current_node->value())) {
return false;
}
@ -1032,6 +1030,14 @@ inline ConcurrentHashTable<CONFIG, F>::
delete _table;
}
template <typename CONFIG, MEMFLAGS F>
inline size_t ConcurrentHashTable<CONFIG, F>::
get_mem_size(Thread* thread)
{
ScopedCS cs(thread, this);
return sizeof(*this) + _table->get_mem_size();
}
template <typename CONFIG, MEMFLAGS F>
inline size_t ConcurrentHashTable<CONFIG, F>::
get_size_log2(Thread* thread)
@ -1135,8 +1141,6 @@ inline void ConcurrentHashTable<CONFIG, F>::
// We only allow this method to be used during a safepoint.
assert(SafepointSynchronize::is_at_safepoint(),
"must only be called in a safepoint");
assert(Thread::current()->is_VM_thread(),
"should be in vm thread");
// Here we skip protection,
// thus no other thread may use this table at the same time.

View File

@ -60,11 +60,14 @@ TEST_VM(FreeRegionList, length) {
MemRegion mr3(mr2.end(), HeapRegion::GrainWords);
MemRegion mr4(mr3.end(), HeapRegion::GrainWords);
HeapRegion hr0(0, &bot, mr0);
HeapRegion hr1(1, &bot, mr1);
HeapRegion hr2(2, &bot, mr2);
HeapRegion hr3(3, &bot, mr3);
HeapRegion hr4(4, &bot, mr4);
G1CardSetConfiguration config;
HeapRegion hr0(0, &bot, mr0, &config);
HeapRegion hr1(1, &bot, mr1, &config);
HeapRegion hr2(2, &bot, mr2, &config);
HeapRegion hr3(3, &bot, mr3, &config);
HeapRegion hr4(4, &bot, mr4, &config);
l.add_ordered(&hr1);
l.add_ordered(&hr0);
l.add_ordered(&hr3);

View File

@ -0,0 +1,476 @@
/*
* Copyright (c) 2021, 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 "gc/g1/g1CardSet.inline.hpp"
#include "gc/g1/g1CardSetContainers.hpp"
#include "gc/g1/g1CardSetMemory.hpp"
#include "gc/g1/heapRegionRemSet.hpp"
#include "gc/shared/gcTraceTime.inline.hpp"
#include "gc/shared/workgroup.hpp"
#include "logging/log.hpp"
#include "unittest.hpp"
#include "utilities/powerOfTwo.hpp"
class G1CardSetTest : public ::testing::Test {
class G1CountCardsClosure : public G1CardSet::G1CardSetCardIterator {
public:
size_t _num_cards;
G1CountCardsClosure() : _num_cards(0) { }
void do_card(uint region_idx, uint card_idx) override {
_num_cards++;
}
};
static WorkGang* _workers;
static uint _max_workers;
static WorkGang* workers() {
if (_workers == NULL) {
_max_workers = os::processor_count();
_workers = new WorkGang("G1CardSetTest Work Gang", _max_workers, false, false);
_workers->initialize_workers();
_workers->update_active_workers(_max_workers);
}
return _workers;
}
// Check whether iteration agrees with the expected number of entries. If the
// add has been single-threaded, we can also check whether the occupied()
// (which is an estimate in that case) agrees.
static void check_iteration(G1CardSet* card_set,
const size_t expected,
const bool add_was_single_threaded = true);
public:
G1CardSetTest() { }
~G1CardSetTest() { }
static uint next_random(uint& seed, uint i) {
// ParkMiller random number generator
seed = (seed * 279470273u) % 0xfffffffb;
return (seed % i);
}
static void cardset_basic_test();
static void cardset_mt_test();
static void add_cards(G1CardSet* card_set, uint cards_per_region, uint* cards, uint num_cards, G1AddCardResult* results);
static void contains_cards(G1CardSet* card_set, uint cards_per_region, uint* cards, uint num_cards);
static void translate_cards(uint cards_per_region, uint region_idx, uint* cards, uint num_cards);
static void iterate_cards(G1CardSet* card_set, G1CardSet::G1CardSetCardIterator* cl);
};
WorkGang* G1CardSetTest::_workers = NULL;
uint G1CardSetTest::_max_workers = 0;
void G1CardSetTest::add_cards(G1CardSet* card_set, uint cards_per_region, uint* cards, uint num_cards, G1AddCardResult* results) {
for (uint i = 0; i < num_cards; i++) {
uint region_idx = cards[i] / cards_per_region;
uint card_idx = cards[i] % cards_per_region;
G1AddCardResult res = card_set->add_card(region_idx, card_idx);
if (results != NULL) {
ASSERT_TRUE(res == results[i]);
}
}
}
class G1CheckCardClosure : public G1CardSet::G1CardSetCardIterator {
G1CardSet* _card_set;
uint _cards_per_region;
uint* _cards_to_expect;
uint _num_cards;
bool _wrong_region_idx;
public:
G1CheckCardClosure(G1CardSet* card_set, uint cards_per_region, uint* cards_to_expect, uint num_cards) :
_card_set(card_set),
_cards_per_region(cards_per_region),
_cards_to_expect(cards_to_expect),
_num_cards(num_cards),
_wrong_region_idx(false) {
}
void do_card(uint region_idx, uint card_idx) override {
uint card = _cards_per_region * region_idx + card_idx;
for (uint i = 0; i < _num_cards; i++) {
if (_cards_to_expect[i] == card) {
_cards_to_expect[i] = (uint)-1;
}
}
}
bool all_found() const {
bool all_good = true;
for (uint i = 0; i < _num_cards; i++) {
if (_cards_to_expect[i] != (uint)-1) {
log_error(gc)("Could not find card %u in region %u",
_cards_to_expect[i] % _cards_per_region,
_cards_to_expect[i] / _cards_per_region);
all_good = false;
}
}
return all_good;
}
};
void G1CardSetTest::contains_cards(G1CardSet* card_set, uint cards_per_region, uint* cards, uint num_cards) {
for (uint i = 0; i < num_cards; i++) {
uint region_idx = cards[i] / cards_per_region;
uint card_idx = cards[i] % cards_per_region;
ASSERT_TRUE(card_set->contains_card(region_idx, card_idx));
}
G1CheckCardClosure cl(card_set, cards_per_region, cards, num_cards);
card_set->iterate_cards(cl);
ASSERT_TRUE(cl.all_found());
}
// Offsets the card indexes in the cards array by the region_idx.
void G1CardSetTest::translate_cards(uint cards_per_region, uint region_idx, uint* cards, uint num_cards) {
for (uint i = 0; i < num_cards; i++) {
cards[i] = cards_per_region * region_idx + cards[i];
}
}
class G1CountCardsOccupied : public G1CardSet::G1CardSetPtrIterator {
size_t _num_occupied;
public:
G1CountCardsOccupied() : _num_occupied(0) { }
void do_cardsetptr(uint region_idx, size_t num_occupied, G1CardSet::CardSetPtr card_set) override {
_num_occupied += num_occupied;
}
size_t num_occupied() const { return _num_occupied; }
};
void G1CardSetTest::check_iteration(G1CardSet* card_set, const size_t expected, const bool single_threaded) {
class CheckIterator : public G1CardSet::G1CardSetCardIterator {
public:
G1CardSet* _card_set;
size_t _num_found;
CheckIterator(G1CardSet* card_set) : _card_set(card_set), _num_found(0) { }
void do_card(uint region_idx, uint card_idx) override {
ASSERT_TRUE(_card_set->contains_card(region_idx, card_idx));
_num_found++;
}
} cl(card_set);
card_set->iterate_cards(cl);
ASSERT_TRUE(expected == cl._num_found);
// We can assert this only if we are single-threaded.
if (single_threaded) {
ASSERT_EQ(card_set->occupied(), cl._num_found);
}
}
void G1CardSetTest::cardset_basic_test() {
const uint CardsPerRegion = 2048;
const double FullCardSetThreshold = 0.8;
const double BitmapCoarsenThreshold = 0.9;
G1CardSetConfiguration config(log2i_exact(CardsPerRegion), 28, BitmapCoarsenThreshold, 8, FullCardSetThreshold, CardsPerRegion);
G1CardSetFreePool free_pool(config.num_mem_object_types());
G1CardSetMemoryManager mm(&config, &free_pool);
{
G1CardSet card_set(&config, &mm);
uint cards1[] = { 1, 2, 3 };
G1AddCardResult results1[] = { Added, Added, Added };
translate_cards(CardsPerRegion, 99, cards1, ARRAY_SIZE(cards1));
add_cards(&card_set, CardsPerRegion, cards1, ARRAY_SIZE(cards1), results1);
contains_cards(&card_set, CardsPerRegion, cards1, ARRAY_SIZE(cards1));
ASSERT_TRUE(card_set.occupied() == ARRAY_SIZE(cards1));
G1CountCardsClosure count_cards;
card_set.iterate_cards(count_cards);
ASSERT_TRUE(count_cards._num_cards == ARRAY_SIZE(cards1));
check_iteration(&card_set, card_set.occupied());
card_set.clear();
ASSERT_TRUE(card_set.occupied() == 0);
check_iteration(&card_set, 0);
}
{
G1CardSet card_set(&config, &mm);
uint cards1[] = { 0, 2047, 17, 17 };
G1AddCardResult results1[] = { Added, Added, Added, Found };
translate_cards(CardsPerRegion, 100, cards1, ARRAY_SIZE(cards1));
add_cards(&card_set, CardsPerRegion, cards1, ARRAY_SIZE(cards1), results1);
// -1 because of the duplicate at the end.
contains_cards(&card_set, CardsPerRegion, cards1, ARRAY_SIZE(cards1) - 1);
ASSERT_TRUE(card_set.occupied() == ARRAY_SIZE(cards1) - 1);
G1CountCardsClosure count_cards;
card_set.iterate_cards(count_cards);
ASSERT_TRUE(count_cards._num_cards == ARRAY_SIZE(cards1) - 1);
check_iteration(&card_set, card_set.occupied());
card_set.clear();
ASSERT_TRUE(card_set.occupied() == 0);
}
{
G1CardSet card_set(&config, &mm);
uint cards1[] = { 0, 2047, 17, 18 /* for region 100 */,
1, 128, 35, 17 /* for region 990 */
};
translate_cards(CardsPerRegion, 100, &cards1[0], 4);
translate_cards(CardsPerRegion, 990, &cards1[4], 4);
add_cards(&card_set, CardsPerRegion, cards1, ARRAY_SIZE(cards1), NULL);
contains_cards(&card_set, CardsPerRegion, cards1, ARRAY_SIZE(cards1));
ASSERT_TRUE(card_set.occupied() == ARRAY_SIZE(cards1));
G1CountCardsClosure count_cards;
card_set.iterate_cards(count_cards);
ASSERT_TRUE(count_cards._num_cards == ARRAY_SIZE(cards1));
check_iteration(&card_set, card_set.occupied());
card_set.clear();
ASSERT_TRUE(card_set.occupied() == 0);
}
{
G1CardSet card_set(&config, &mm);
uint cards1[100];
for (uint i = 0; i < ARRAY_SIZE(cards1); i++) {
cards1[i] = i + 3;
translate_cards(CardsPerRegion, i, &cards1[i], 1);
}
add_cards(&card_set, CardsPerRegion, cards1, ARRAY_SIZE(cards1), NULL);
contains_cards(&card_set, CardsPerRegion, cards1, ARRAY_SIZE(cards1));
ASSERT_TRUE(card_set.num_containers() == ARRAY_SIZE(cards1));
ASSERT_TRUE(card_set.occupied() == ARRAY_SIZE(cards1));
G1CountCardsClosure count_cards;
card_set.iterate_cards(count_cards);
ASSERT_TRUE(count_cards._num_cards == ARRAY_SIZE(cards1));
check_iteration(&card_set, card_set.occupied());
card_set.clear();
ASSERT_TRUE(card_set.occupied() == 0);
}
{
G1CardSet card_set(&config, &mm);
// Generate non-prime numbers from 1 to 1000
uint count = 0;
for (uint i = 2; i < 33; i++) {
if (!card_set.contains_card(100, i)) {
for (uint j = i * i; j < 1000; j += i) {
G1AddCardResult res = card_set.add_card(100, j);
count += (res == Added);
}
}
}
G1CountCardsOccupied cl;
card_set.iterate_containers(&cl);
ASSERT_TRUE(count == card_set.occupied());
ASSERT_TRUE(card_set.occupied() == cl.num_occupied());
check_iteration(&card_set, card_set.occupied());
card_set.clear();
ASSERT_TRUE(card_set.occupied() == 0);
}
{ // Test coarsening to full
G1CardSet card_set(&config, &mm);
uint count = 0;
uint i = 10;
uint bitmap_threshold = config.cards_in_howl_bitmap_threshold();
for (; i < bitmap_threshold + 10; i++) {
G1AddCardResult res = card_set.add_card(99, i);
ASSERT_TRUE(res == Added);
count++;
ASSERT_TRUE(count == card_set.occupied());
}
G1AddCardResult res = card_set.add_card(99, config.num_cards_in_howl_bitmap() - 1);
// Adding above card should have coarsened Bitmap -> Full.
ASSERT_TRUE(res == Added);
ASSERT_TRUE(config.num_cards_in_howl_bitmap() == card_set.occupied());
res = card_set.add_card(99, config.num_cards_in_howl_bitmap() - 2);
ASSERT_TRUE(res == Found);
uint threshold = config.cards_in_howl_threshold();
uint adjusted_threshold = config.cards_in_howl_bitmap_threshold() * config.num_buckets_in_howl();
i = config.num_cards_in_howl_bitmap();
count = i;
for (; i < threshold; i++) {
G1AddCardResult res = card_set.add_card(99, i);
ASSERT_TRUE(res == Added);
count++;
ASSERT_TRUE(count == card_set.occupied());
}
res = card_set.add_card(99, CardsPerRegion - 1);
// Adding above card should have coarsened Howl -> Full.
ASSERT_TRUE(res == Added);
ASSERT_TRUE(CardsPerRegion == card_set.occupied());
check_iteration(&card_set, card_set.occupied());
res = card_set.add_card(99, CardsPerRegion - 2);
ASSERT_TRUE(res == Found);
G1CountCardsClosure count_cards;
card_set.iterate_cards(count_cards);
ASSERT_TRUE(count_cards._num_cards == config.max_cards_in_region());
card_set.clear();
ASSERT_TRUE(card_set.occupied() == 0);
}
}
class G1CardSetMtTestTask : public AbstractGangTask {
G1CardSet* _card_set;
size_t _added;
size_t _found;
public:
G1CardSetMtTestTask(G1CardSet* card_set) :
AbstractGangTask(""),
_card_set(card_set),
_added(0),
_found(0) { }
void work(uint worker_id) {
uint seed = worker_id;
size_t added = 0;
size_t found = 0;
for (uint i = 0; i < 100000; i++) {
uint region = G1CardSetTest::next_random(seed, 1000);
uint card = G1CardSetTest::next_random(seed, 10000);
G1AddCardResult res = _card_set->add_card(region, card);
ASSERT_TRUE(res == Added || res == Found);
if (res == Added) {
added++;
} else if (res == Found) {
found++;
}
}
Atomic::add(&_added, added);
Atomic::add(&_found, found);
}
size_t added() const { return _added; }
size_t found() const { return _found; }
};
void G1CardSetTest::cardset_mt_test() {
const uint CardsPerRegion = 16384;
const double FullCardSetThreshold = 1.0;
const uint BitmapCoarsenThreshold = 1.0;
G1CardSetConfiguration config(log2i_exact(CardsPerRegion), 120, BitmapCoarsenThreshold, 8, FullCardSetThreshold, CardsPerRegion);
G1CardSetFreePool free_pool(config.num_mem_object_types());
G1CardSetMemoryManager mm(&config, &free_pool);
G1CardSet card_set(&config, &mm);
const uint num_workers = workers()->active_workers();
G1CardSetMtTestTask cl(&card_set);
{
GCTraceTime(Error, gc) x("Cardset test");
_workers->run_task(&cl, num_workers);
}
size_t num_found = 0;
// Now check the contents of the card set.
for (uint i = 0; i < num_workers; i++) {
uint seed = i;
for (uint j = 0; j < 100000; j++) {
uint region = G1CardSetTest::next_random(seed, 1000);
uint card = G1CardSetTest::next_random(seed, 10000);
bool contains = card_set.contains_card(region, card);
ASSERT_TRUE(contains);
num_found += contains;
}
}
ASSERT_TRUE(num_found == cl.added() + cl.found());
G1CountCardsClosure count_cards;
card_set.iterate_cards(count_cards);
check_iteration(&card_set, count_cards._num_cards, false /* add_was_single_threaded */);
// During coarsening we try to unblock concurrent threads as soon as possible,
// so we do not add the cards from the smaller CardSetContainer to the larger
// one immediately, allowing addition by concurrent threads after allocating
// the space immediately. So the amount of "successfully added" results may be
// (and in case of many threads typically is) higher than the number of unique
// cards.
ASSERT_TRUE(count_cards._num_cards <= cl.added());
}
TEST_VM(G1CardSetTest, basic_cardset_test) {
G1CardSetTest::cardset_basic_test();
}
TEST_VM(G1CardSetTest, mt_cardset_test) {
G1CardSetTest::cardset_mt_test();
}

View File

@ -0,0 +1,263 @@
/*
* Copyright (c) 2021, 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 "gc/g1/g1CardSetContainers.inline.hpp"
#include "gc/g1/heapRegionBounds.inline.hpp"
#include "gc/shared/cardTable.hpp"
#include "memory/allocation.inline.hpp"
#include "utilities/globalDefinitions.hpp"
#include "utilities/powerOfTwo.hpp"
#include "unittest.hpp"
class G1CardSetContainersTest : public ::testing::Test {
public:
G1CardSetContainersTest() { }
~G1CardSetContainersTest() { }
static uint cards_per_inlineptr_set(uint bits_per_card) {
return G1CardSetInlinePtr::max_cards_in_inline_ptr(bits_per_card);
}
static void cardset_inlineptr_test(uint bits_per_card);
static void cardset_array_test(uint cards_per_array);
static void cardset_bitmap_test(uint threshold, uint size_in_bits);
};
class G1FindCardsInRange : public StackObj {
uint _num_cards;
uint _range_min;
bool* _cards_found;
public:
G1FindCardsInRange(uint range_min, uint range_max) :
_num_cards(range_max - range_min + 1),
_range_min(range_min),
_cards_found(NEW_C_HEAP_ARRAY(bool, _num_cards, mtGC)) {
for (uint i = 0; i < _num_cards; i++) {
_cards_found[i] = false;
}
}
void verify_all_found() {
verify_part_found(_num_cards);
}
void verify_part_found(uint num) {
for (uint i = 0; i < num; i++) {
ASSERT_TRUE(_cards_found[i]);
}
}
~G1FindCardsInRange() {
FREE_C_HEAP_ARRAY(mtGC, _cards_found);
}
void operator()(uint card) {
ASSERT_TRUE((card - _range_min) < _num_cards);
ASSERT_FALSE(_cards_found[card - _range_min]); // Must not have been found yet.
_cards_found[card - _range_min] = true;
}
};
void G1CardSetContainersTest::cardset_inlineptr_test(uint bits_per_card) {
const uint CardsPerSet = cards_per_inlineptr_set(bits_per_card);
G1AddCardResult res;
G1CardSet::CardSetPtr value = G1CardSetInlinePtr();
for (uint i = 0; i < CardsPerSet; i++) {
{
G1CardSetInlinePtr cards(&value, value);
res = cards.add(i + 1, bits_per_card, CardsPerSet);
ASSERT_TRUE(res == Added);
}
{
G1CardSetInlinePtr cards(&value, value);
ASSERT_TRUE(cards.contains(i + 1, bits_per_card));
}
}
for (uint i = 0; i < CardsPerSet; i++) {
G1CardSetInlinePtr cards(value);
ASSERT_TRUE(cards.contains(i + 1, bits_per_card));
}
// Try to add again, should all return that the card had been added.
for (uint i = 0; i < CardsPerSet; i++) {
G1CardSetInlinePtr cards(&value, value);
res = cards.add(i + 1, bits_per_card, CardsPerSet);
ASSERT_TRUE(res == Found);
}
// Should be no more space in set.
{
G1CardSetInlinePtr cards(&value, value);
res = cards.add(CardsPerSet + 1, bits_per_card, CardsPerSet);
ASSERT_TRUE(res == Overflow);
}
// Cards should still be in the set.
for (uint i = 0; i < CardsPerSet; i++) {
G1CardSetInlinePtr cards(value);
ASSERT_TRUE(cards.contains(i + 1, bits_per_card));
}
// Boundary cards should not be in the set.
{
G1CardSetInlinePtr cards(value);
ASSERT_TRUE(!cards.contains(0, bits_per_card));
ASSERT_TRUE(!cards.contains(CardsPerSet + 1, bits_per_card));
}
// Verify iteration finds all cards too and only those.
{
G1FindCardsInRange found(1, CardsPerSet);
G1CardSetInlinePtr cards(value);
cards.iterate(found, bits_per_card);
found.verify_all_found();
}
}
void G1CardSetContainersTest::cardset_array_test(uint cards_per_array) {
uint8_t* cardset_data = NEW_C_HEAP_ARRAY(uint8_t, G1CardSetArray::size_in_bytes(cards_per_array), mtGC);
G1CardSetArray* cards = new (cardset_data) G1CardSetArray(1, cards_per_array);
ASSERT_TRUE(cards->contains(1)); // Added during initialization
ASSERT_TRUE(cards->num_entries() == 1); // Check it's the only one.
G1AddCardResult res;
// Add some elements
for (uint i = 1; i < cards_per_array; i++) {
res = cards->add(i + 1);
ASSERT_TRUE(res == Added);
}
// Check they are in the container.
for (uint i = 0; i < cards_per_array; i++) {
ASSERT_TRUE(cards->contains(i + 1));
}
// Try to add again, should all return that the card had been added.
for (uint i = 0; i < cards_per_array; i++) {
res = cards->add(i + 1);
ASSERT_TRUE(res == Found);
}
// Should be no more space in set.
{
res = cards->add(cards_per_array + 1);
ASSERT_TRUE(res == Overflow);
}
// Cards should still be in the set.
for (uint i = 0; i < cards_per_array; i++) {
ASSERT_TRUE(cards->contains(i + 1));
}
ASSERT_TRUE(!cards->contains(0));
ASSERT_TRUE(!cards->contains(cards_per_array + 1));
// Verify iteration finds all cards too.
{
G1FindCardsInRange found(1, cards_per_array);
cards->iterate(found);
found.verify_all_found();
}
FREE_C_HEAP_ARRAY(mtGC, cardset_data);
}
void G1CardSetContainersTest::cardset_bitmap_test(uint threshold, uint size_in_bits) {
uint8_t* cardset_data = NEW_C_HEAP_ARRAY(uint8_t, G1CardSetBitMap::size_in_bytes(size_in_bits), mtGC);
G1CardSetBitMap* cards = new (cardset_data) G1CardSetBitMap(1, size_in_bits);
ASSERT_TRUE(cards->contains(1, size_in_bits)); // Added during initialization
ASSERT_TRUE(cards->num_bits_set() == 1); // Should be the only one.
G1AddCardResult res;
for (uint i = 1; i < threshold; i++) {
res = cards->add(i + 1, threshold, size_in_bits);
ASSERT_TRUE(res == Added);
}
for (uint i = 0; i < threshold; i++) {
ASSERT_TRUE(cards->contains(i + 1, size_in_bits));
}
// Try to add again, should all return that the card had been added.
for (uint i = 0; i < threshold; i++) {
res = cards->add(i + 1, threshold, size_in_bits);
ASSERT_TRUE(res == Found);
}
// Should be no more space in set.
{
res = cards->add(threshold + 1, threshold, size_in_bits);
ASSERT_TRUE(res == Overflow);
}
// Cards should still be in the set.
for (uint i = 0; i < threshold; i++) {
ASSERT_TRUE(cards->contains(i + 1, size_in_bits));
}
ASSERT_TRUE(!cards->contains(0, size_in_bits));
// Verify iteration finds all cards too.
{
G1FindCardsInRange found(1, threshold + 1);
cards->iterate(found, size_in_bits, 0);
found.verify_part_found(threshold);
}
FREE_C_HEAP_ARRAY(mtGC, cardset_data);
}
TEST_VM_F(G1CardSetContainersTest, basic_cardset_inptr_test) {
uint const min = (uint)log2i(HeapRegionBounds::min_size());
uint const max = (uint)log2i(HeapRegionBounds::max_size());
for (uint i = min; i <= max; i++) {
G1CardSetContainersTest::cardset_inlineptr_test(i - CardTable::card_shift);
}
}
TEST_VM_F(G1CardSetContainersTest, basic_cardset_array_test) {
uint array_sizes[] = { 5, 9, 63, 77, 127 };
for (uint i = 0; i < ARRAY_SIZE(array_sizes); i++) {
size_t const max_cards_in_set = ARRAY_SIZE(array_sizes);
G1CardSetContainersTest::cardset_array_test(max_cards_in_set);
}
}
TEST_VM_F(G1CardSetContainersTest, basic_cardset_bitmap_test) {
uint bit_sizes[] = { 64, 2048 };
uint threshold_sizes[] = { 17, 330 };
for (uint i = 0; i < ARRAY_SIZE(bit_sizes); i++) {
G1CardSetContainersTest::cardset_bitmap_test(threshold_sizes[i], bit_sizes[i]);
}
}

View File

@ -0,0 +1,67 @@
/*
* Copyright (c) 2021, 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.
*/
package gc.arguments;
/*
* @test TestG1RemSetFlags
* @requires vm.gc.G1
* @summary Verify that the remembered set flags are updated as expected
* @modules java.base/jdk.internal.misc
* @modules java.management/sun.management
* @library /test/lib
* @library /
* @run driver gc.arguments.TestG1RemSetFlags
*/
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.ArrayList;
import java.util.Arrays;
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.process.ProcessTools;
public class TestG1RemSetFlags {
private static void checkG1RemSetFlags(String[] flags, int exitValue) throws Exception {
ArrayList<String> flagList = new ArrayList<String>();
flagList.addAll(Arrays.asList(flags));
flagList.add("-XX:+UseG1GC");
flagList.add("-XX:+PrintFlagsFinal");
flagList.add("-version");
ProcessBuilder pb = GCArguments.createJavaProcessBuilder(flagList);
OutputAnalyzer output = new OutputAnalyzer(pb.start());
output.shouldHaveExitValue(exitValue);
}
public static void main(String args[]) throws Exception {
checkG1RemSetFlags(new String[] { "-XX:+UnlockExperimentalVMOptions", "-XX:G1RemSetHowlNumBuckets=8", "-XX:G1RemSetHowlMaxNumBuckets=8" }, 0);
checkG1RemSetFlags(new String[] { "-XX:+UnlockExperimentalVMOptions", "-XX:G1RemSetHowlNumBuckets=8", "-XX:G1RemSetHowlMaxNumBuckets=16" }, 0);
checkG1RemSetFlags(new String[] { "-XX:+UnlockExperimentalVMOptions", "-XX:G1RemSetHowlNumBuckets=16", "-XX:G1RemSetHowlMaxNumBuckets=8" }, 1);
checkG1RemSetFlags(new String[] { "-XX:+UnlockExperimentalVMOptions", "-XX:G1RemSetHowlNumBuckets=7" }, 1);
checkG1RemSetFlags(new String[] { "-XX:+UnlockExperimentalVMOptions", "-XX:G1RemSetHowlMaxNumBuckets=7" }, 1);
}
}

View File

@ -101,9 +101,14 @@ public class TestGCLogMessages {
new LogMessageWithLevel("Prepare Merge Heap Roots", Level.DEBUG),
new LogMessageWithLevel("Eager Reclaim", Level.DEBUG),
new LogMessageWithLevel("Remembered Sets", Level.DEBUG),
new LogMessageWithLevel("Merged Sparse", Level.DEBUG),
new LogMessageWithLevel("Merged Fine", Level.DEBUG),
new LogMessageWithLevel("Merged Coarse", Level.DEBUG),
new LogMessageWithLevel("Merged Inline", Level.DEBUG),
new LogMessageWithLevel("Merged ArrayOfCards", Level.DEBUG),
new LogMessageWithLevel("Merged Howl", Level.DEBUG),
new LogMessageWithLevel("Merged Full", Level.DEBUG),
new LogMessageWithLevel("Merged Howl Inline", Level.DEBUG),
new LogMessageWithLevel("Merged Howl ArrayOfCards", Level.DEBUG),
new LogMessageWithLevel("Merged Howl BitMap", Level.DEBUG),
new LogMessageWithLevel("Merged Howl Full", Level.DEBUG),
new LogMessageWithLevel("Hot Card Cache", Level.DEBUG),
new LogMessageWithLevel("Log Buffers", Level.DEBUG),
new LogMessageWithLevel("Dirty Cards", Level.DEBUG),
@ -135,6 +140,7 @@ public class TestGCLogMessages {
new LogMessageWithLevel("Region Register", Level.DEBUG),
new LogMessageWithLevel("Prepare Heap Roots", Level.DEBUG),
new LogMessageWithLevel("Concatenate Dirty Card Logs", Level.DEBUG),
new LogMessageWithLevel("Sample Collection Set Candidates", Level.DEBUG),
// Free CSet
new LogMessageWithLevel("Free Collection Set", Level.DEBUG),
new LogMessageWithLevel("Serial Free Collection Set", Level.TRACE),

View File

@ -34,7 +34,7 @@ package gc.g1;
* @modules java.base/jdk.internal.misc
* @build sun.hotspot.WhiteBox
* @run driver jdk.test.lib.helpers.ClassFileInstaller sun.hotspot.WhiteBox
* @run main/othervm -Xbootclasspath/a:. -Xlog:gc,gc+humongous=debug -XX:+UseG1GC -XX:MaxTenuringThreshold=0 -XX:G1RSetSparseRegionEntries=32 -XX:G1HeapRegionSize=1m -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI gc.g1.TestNoEagerReclaimOfHumongousRegions
* @run main/othervm -Xbootclasspath/a:. -Xlog:gc,gc+humongous=debug -XX:+UseG1GC -XX:MaxTenuringThreshold=0 -XX:+UnlockExperimentalVMOptions -XX:G1RemSetArrayOfCardsEntries=32 -XX:G1HeapRegionSize=1m -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI gc.g1.TestNoEagerReclaimOfHumongousRegions
*/
import java.util.LinkedList;

View File

@ -102,6 +102,8 @@ public class TestStressRSetCoarsening {
private static final long KB = 1024;
private static final long MB = 1024 * KB;
private static final int CARDSIZE = 512; // Card size in bytes.
private static final WhiteBox WB = WhiteBox.getWhiteBox();
public final ObjStorage storage;
@ -229,11 +231,13 @@ public class TestStressRSetCoarsening {
}
public void go() throws InterruptedException {
// threshold for sparce -> fine
final int FINE = WB.getIntxVMFlag("G1RSetSparseRegionEntries").intValue();
// Threshold for Array of Cards -> Howl
final int ARRAY_TO_HOWL_THRESHOLD = WB.getUintVMFlag("G1RemSetArrayOfCardsEntries").intValue();
// threshold for fine -> coarse
final int COARSE = WB.getIntxVMFlag("G1RSetRegionEntries").intValue();
// Threshold for Howl -> Full
int coarsenHowlToFullPercent = WB.getUintVMFlag("G1RemSetCoarsenHowlToFullPercent").intValue();
int cardsPerRegion = WB.getSizeTVMFlag("G1HeapRegionSize").intValue() / CARDSIZE;
final int HOWL_TO_FULL_THRESHOLD = (cardsPerRegion * coarsenHowlToFullPercent) / 100;
// regToRegRefCounts - array of reference counts from region to region
// at the the end of iteration.
@ -241,8 +245,13 @@ public class TestStressRSetCoarsening {
// If c[i] > c[i-1] then during the iteration i more references will
// be created.
// If c[i] < c[i-1] then some referenes will be cleaned.
int[] regToRegRefCounts = {0, FINE / 2, 0, FINE, (FINE + COARSE) / 2, 0,
COARSE, COARSE + 10, FINE + 1, FINE / 2, 0};
int[] regToRegRefCounts = {
0, ARRAY_TO_HOWL_THRESHOLD / 2,
0, ARRAY_TO_HOWL_THRESHOLD,
(ARRAY_TO_HOWL_THRESHOLD + HOWL_TO_FULL_THRESHOLD) / 2, 0,
HOWL_TO_FULL_THRESHOLD, HOWL_TO_FULL_THRESHOLD + 10,
ARRAY_TO_HOWL_THRESHOLD + 1, ARRAY_TO_HOWL_THRESHOLD / 2,
0};
// For progress tracking
int[] progress = new int[regToRegRefCounts.length];

View File

@ -230,8 +230,6 @@ public class TestOptionsWithRanges {
*/
excludeTestMaxRange("ConcGCThreads");
excludeTestMaxRange("G1ConcRefinementThreads");
excludeTestMaxRange("G1RSetRegionEntries");
excludeTestMaxRange("G1RSetSparseRegionEntries");
excludeTestMaxRange("G1UpdateBufferSize");
excludeTestMaxRange("InitialHeapSize");
excludeTestMaxRange("MaxHeapSize");

View File

@ -50,7 +50,7 @@ public class MallocSiteTypeChange {
long addr = wb.NMTMallocWithPseudoStack(4 * 1024, pc);
// Verify that current tracking level is "detail"
pb.command(new String[] { JDKToolFinder.getJDKTool("jcmd"), pid, "VM.native_memory", "summary"});
pb.command(new String[] { JDKToolFinder.getJDKTool("jcmd"), pid, "VM.native_memory", "detail"});
output = new OutputAnalyzer(pb.start());
output.shouldContain("Test (reserved=4KB, committed=4KB)");
@ -59,7 +59,7 @@ public class MallocSiteTypeChange {
output.shouldContain("Baseline succeeded");
wb.NMTFree(addr);
addr = wb.NMTMallocWithPseudoStackAndType(2 * 1024, pc, 8 /* mtInternal */ );
addr = wb.NMTMallocWithPseudoStackAndType(2 * 1024, pc, 9 /* mtInternal */ );
pb.command(new String[] { JDKToolFinder.getJDKTool("jcmd"), pid, "VM.native_memory", "detail.diff"});
output = new OutputAnalyzer(pb.start());
output.shouldContain("(malloc=0KB type=Test -4KB)");

View File

@ -115,7 +115,8 @@ public class TestG1ParallelPhases {
"MergePSS",
"NonYoungFreeCSet",
"YoungFreeCSet",
"RebuildFreeList"
"RebuildFreeList",
"SampleCandidates"
);
// Some GC phases may or may not occur depending on environment. Filter them out