jdk/src/hotspot/share/gc/serial/cardTableRS.cpp
Albert Mingkun Yang 0fcf41112c 8385728: Serial: Check empty MemRegion in maintain_old_to_young_invariant
Reviewed-by: tschatzl, jsikstro, gli
2026-06-05 14:59:20 +00:00

314 lines
10 KiB
C++

/*
* Copyright (c) 2001, 2026, 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 "classfile/classLoaderDataGraph.hpp"
#include "gc/serial/cardTableRS.hpp"
#include "gc/serial/generation.hpp"
#include "gc/serial/serialHeap.inline.hpp"
#include "gc/shared/space.hpp"
#include "memory/iterator.inline.hpp"
#include "runtime/prefetch.inline.hpp"
#include "utilities/align.hpp"
void CardTableRS::scan_old_to_young_refs(TenuredGeneration* tg, HeapWord* saved_top) {
const MemRegion ur = tg->used_region();
const MemRegion urasm = MemRegion(tg->space()->bottom(), saved_top);
assert(ur.contains(urasm),
"[" PTR_FORMAT ", " PTR_FORMAT ") is not contained in "
"[" PTR_FORMAT ", " PTR_FORMAT ")",
p2i(urasm.start()), p2i(urasm.end()), p2i(ur.start()), p2i(ur.end()));
if (!urasm.is_empty()) {
OldGenScanClosure cl(SerialHeap::heap()->young_gen());
non_clean_card_iterate(tg, urasm, &cl);
}
}
void CardTableRS::maintain_old_to_young_invariant(TenuredGeneration* old_gen,
bool is_young_gen_empty) {
if (is_young_gen_empty) {
MemRegion prev_used_mr = old_gen->prev_used_region();
if (!prev_used_mr.is_empty()) {
clear_MemRegion(prev_used_mr);
}
{
MemRegion old_gen_committed{old_gen->space()->bottom(), old_gen->space()->end()};
verify_region(old_gen_committed, clean_card_val(), true);
}
} else {
MemRegion used_mr = old_gen->used_region();
MemRegion prev_used_mr = old_gen->prev_used_region();
if (used_mr.end() < prev_used_mr.end()) {
// Shrunk; need to clear the previously-used but now-unused parts.
clear_MemRegion(MemRegion(used_mr.end(), prev_used_mr.end()));
}
// No idea which card contains old-to-young pointer, so dirtying cards for
// the entire used part of old-gen conservatively.
if (!used_mr.is_empty()) {
dirty_MemRegion(used_mr);
}
}
}
class SerialCheckForUnmarkedOops : public BasicOopIterateClosure {
DefNewGeneration* _young_gen;
CardTableRS* _card_table;
HeapWord* _unmarked_addr;
template <class T> void do_oop_work(T* p) {
oop obj = RawAccess<>::oop_load(p);
if (_young_gen->is_in_reserved(obj) &&
!_card_table->is_dirty_for_addr(p)) {
// Don't overwrite the first missing card mark
if (_unmarked_addr == nullptr) {
_unmarked_addr = (HeapWord*)p;
}
}
}
public:
SerialCheckForUnmarkedOops(DefNewGeneration* young_gen, CardTableRS* card_table) :
_young_gen(young_gen),
_card_table(card_table),
_unmarked_addr(nullptr) {}
void do_oop(oop* p) override { do_oop_work(p); }
void do_oop(narrowOop* p) override { do_oop_work(p); }
bool has_unmarked_oop() {
return _unmarked_addr != nullptr;
}
};
void CardTableRS::verify() {
class CheckForUnmarkedObjects : public ObjectClosure {
DefNewGeneration* _young_gen;
CardTableRS* _card_table;
public:
CheckForUnmarkedObjects() {
SerialHeap* heap = SerialHeap::heap();
_young_gen = heap->young_gen();
_card_table = heap->rem_set();
}
void do_object(oop obj) override {
SerialCheckForUnmarkedOops object_check(_young_gen, _card_table);
obj->oop_iterate(&object_check);
// If this obj is imprecisely-marked, the card for obj-start must be dirty.
if (object_check.has_unmarked_oop()) {
guarantee(_card_table->is_dirty_for_addr(obj), "Found unmarked old-to-young pointer");
}
}
} check;
SerialHeap::heap()->old_gen()->object_iterate(&check);
}
CardTableRS::CardTableRS(MemRegion whole_heap) :
CardTable(whole_heap) { }
// Implemented word-iteration to skip long consecutive clean cards.
CardTable::CardValue* CardTableRS::find_first_dirty_card(CardValue* const start_card,
CardValue* const end_card) {
using Word = uintptr_t;
CardValue* current_card = start_card;
while (!is_aligned(current_card, sizeof(Word))) {
if (current_card >= end_card) {
return end_card;
}
if (is_dirty(current_card)) {
return current_card;
}
++current_card;
}
// Word comparison
while (current_card + sizeof(Word) <= end_card) {
Word* current_word = reinterpret_cast<Word*>(current_card);
if (*current_word != (Word)clean_card_row_val()) {
// Found a dirty card in this word; fall back to per-CardValue comparison.
break;
}
current_card += sizeof(Word);
}
// Per-CardValue comparison.
for (/* empty */; current_card < end_card; ++current_card) {
if (is_dirty(current_card)) {
return current_card;
}
}
return end_card;
}
// Because non-objArray objs can be imprecisely marked (only the obj-start card
// is dirty instead of the part containing old-to-young pointers), if the
// obj-start of a non-objArray is dirty, all cards that the obj resides on,
// except the final one, are unconditionally considered as dirty. This is
// because that obj will be iterated (scanned for old-to-young pointers) as a
// whole.
template<typename Func>
CardTable::CardValue* CardTableRS::find_first_clean_card(CardValue* const start_card,
CardValue* const end_card,
Func& object_start) {
for (CardValue* current_card = start_card; current_card < end_card; /* empty */) {
if (is_dirty(current_card)) {
current_card++;
continue;
}
// A potential candidate.
HeapWord* addr = addr_for(current_card);
HeapWord* obj_start_addr = object_start(addr);
if (obj_start_addr == addr) {
return current_card;
}
// Final obj in dirty-chunk crosses card-boundary.
oop obj = cast_to_oop(obj_start_addr);
if (obj->is_objArray()) {
// ObjArrays are always precisely-marked so we are not allowed to jump to
// the end of the current object.
return current_card;
}
// Final card occupied by obj.
CardValue* obj_final_card = byte_for(obj_start_addr + obj->size() - 1);
if (is_clean(obj_final_card)) {
return obj_final_card;
}
// Continue the search after this known-dirty card...
current_card = obj_final_card + 1;
}
return end_card;
}
void CardTableRS::clear_cards(CardValue* start, CardValue* end) {
size_t num_cards = pointer_delta(end, start, sizeof(CardValue));
memset(start, clean_card_val(), num_cards);
}
static void prefetch_write(void *p) {
if (PrefetchScanIntervalInBytes >= 0) {
Prefetch::write(p, PrefetchScanIntervalInBytes);
}
}
static void scan_obj_with_limit(oop obj,
OldGenScanClosure* cl,
HeapWord* start,
HeapWord* end) {
if (!obj->is_typeArray()) {
prefetch_write(start);
obj->oop_iterate(cl, MemRegion(start, end));
}
}
void CardTableRS::non_clean_card_iterate(TenuredGeneration* tg,
MemRegion mr,
OldGenScanClosure* cl) {
struct {
HeapWord* start_addr;
HeapWord* end_addr;
} cached_obj { nullptr, mr.start() };
auto object_start = [&] (const HeapWord* const addr) {
if (addr < cached_obj.end_addr) {
assert(cached_obj.start_addr != nullptr, "inv");
return cached_obj.start_addr;
}
HeapWord* result = tg->block_start(addr);
cached_obj.start_addr = result;
cached_obj.end_addr = result + cast_to_oop(result)->size();
return result;
};
CardValue* const start_card = byte_for(mr.start());
CardValue* const end_card = byte_for(mr.last()) + 1;
// if mr.end() is not card-aligned, that final card should not be cleared
// because it can be annotated dirty due to old-to-young pointers in
// newly-promoted objs on that card.
CardValue* const clear_limit_card = is_card_aligned(mr.end()) ? end_card - 1
: end_card - 2;
for (CardValue* current_card = start_card; current_card < end_card; /* empty */) {
CardValue* const dirty_l = find_first_dirty_card(current_card, end_card);
if (dirty_l == end_card) {
// No dirty cards to iterate.
return;
}
HeapWord* const addr_l = addr_for(dirty_l);
HeapWord* obj_addr = object_start(addr_l);
CardValue* const dirty_r = find_first_clean_card(dirty_l + 1,
end_card,
object_start);
assert(dirty_l < dirty_r, "inv");
HeapWord* const addr_r = dirty_r == end_card ? mr.end()
: addr_for(dirty_r);
clear_cards(MIN2(dirty_l, clear_limit_card),
MIN2(dirty_r, clear_limit_card));
while (true) {
assert(obj_addr < addr_r, "inv");
oop obj = cast_to_oop(obj_addr);
const bool is_obj_array = obj->is_objArray();
HeapWord* const obj_end_addr = obj_addr + obj->size();
if (is_obj_array) {
// ObjArrays are always precise-marked.
scan_obj_with_limit(obj, cl, addr_l, addr_r);
} else {
scan_obj_with_limit(obj, cl, addr_l, obj_end_addr);
}
if (obj_end_addr >= addr_r) {
current_card = dirty_r + 1;
break;
}
// Move to next obj inside this dirty chunk.
obj_addr = obj_end_addr;
}
}
}
bool CardTableRS::is_in_young(const void* p) const {
return SerialHeap::heap()->is_in_young(p);
}