| /* | |
| * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| * | |
| */ | |
| #include "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 "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) { | |
| clear_MemRegion(old_gen->prev_used_region()); | |
| } 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. | |
| 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); | |
| } |