| /* | |
| * Copyright (c) 2015, 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/shared/gc_globals.hpp" | |
| #include "gc/shared/gcLogPrecious.hpp" | |
| #include "gc/shared/locationPrinter.hpp" | |
| #include "gc/shared/tlab_globals.hpp" | |
| #include "gc/z/zAddress.inline.hpp" | |
| #include "gc/z/zArray.inline.hpp" | |
| #include "gc/z/zGeneration.inline.hpp" | |
| #include "gc/z/zGlobals.hpp" | |
| #include "gc/z/zHeap.inline.hpp" | |
| #include "gc/z/zHeapIterator.hpp" | |
| #include "gc/z/zHeuristics.hpp" | |
| #include "gc/z/zInitialize.hpp" | |
| #include "gc/z/zPage.inline.hpp" | |
| #include "gc/z/zPageTable.inline.hpp" | |
| #include "gc/z/zResurrection.hpp" | |
| #include "gc/z/zStat.hpp" | |
| #include "gc/z/zUncoloredRoot.inline.hpp" | |
| #include "gc/z/zUtils.hpp" | |
| #include "gc/z/zVerify.hpp" | |
| #include "gc/z/zWorkers.hpp" | |
| #include "logging/log.hpp" | |
| #include "memory/iterator.hpp" | |
| #include "memory/metaspaceUtils.hpp" | |
| #include "memory/resourceArea.hpp" | |
| #include "runtime/javaThread.hpp" | |
| #include "utilities/debug.hpp" | |
| static const ZStatCounter ZCounterUndoPageAllocation("Memory", "Undo Page Allocation", ZStatUnitOpsPerSecond); | |
| static const ZStatCounter ZCounterOutOfMemory("Memory", "Out Of Memory", ZStatUnitOpsPerSecond); | |
| ZHeap* ZHeap::_heap = nullptr; | |
| ZHeap::ZHeap() | |
| : _page_allocator(MinHeapSize, InitialHeapSize, SoftMaxHeapSize, MaxHeapSize), | |
| _page_table(), | |
| _allocator_eden(), | |
| _allocator_relocation(), | |
| _serviceability(InitialHeapSize, min_capacity(), max_capacity()), | |
| _old(&_page_table, &_page_allocator), | |
| _young(&_page_table, _old.forwarding_table(), &_page_allocator), | |
| _tlab_usage(), | |
| _initialized(false) { | |
| // Install global heap instance | |
| assert(_heap == nullptr, "Already initialized"); | |
| _heap = this; | |
| if (!_page_allocator.is_initialized()) { | |
| return; | |
| } | |
| // Prime cache | |
| if (!_page_allocator.prime_cache(_old.workers(), InitialHeapSize)) { | |
| ZInitialize::error("Failed to allocate initial Java heap (%zuM)", InitialHeapSize / M); | |
| return; | |
| } | |
| if (UseDynamicNumberOfGCThreads) { | |
| log_info_p(gc, init)("GC Workers Max: %u (dynamic)", ConcGCThreads); | |
| } | |
| // Update statistics | |
| _young.stat_heap()->at_initialize(_page_allocator.min_capacity(), _page_allocator.max_capacity()); | |
| _old.stat_heap()->at_initialize(_page_allocator.min_capacity(), _page_allocator.max_capacity()); | |
| // Successfully initialized | |
| _initialized = true; | |
| } | |
| bool ZHeap::is_initialized() const { | |
| return _initialized; | |
| } | |
| size_t ZHeap::min_capacity() const { | |
| return _page_allocator.min_capacity(); | |
| } | |
| size_t ZHeap::max_capacity() const { | |
| return _page_allocator.max_capacity(); | |
| } | |
| size_t ZHeap::soft_max_capacity() const { | |
| return _page_allocator.soft_max_capacity(); | |
| } | |
| size_t ZHeap::capacity() const { | |
| return _page_allocator.capacity(); | |
| } | |
| size_t ZHeap::used() const { | |
| return _page_allocator.used(); | |
| } | |
| size_t ZHeap::used_generation(ZGenerationId id) const { | |
| return _page_allocator.used_generation(id); | |
| } | |
| size_t ZHeap::used_young() const { | |
| return _page_allocator.used_generation(ZGenerationId::young); | |
| } | |
| size_t ZHeap::used_old() const { | |
| return _page_allocator.used_generation(ZGenerationId::old); | |
| } | |
| size_t ZHeap::unused() const { | |
| return _page_allocator.unused(); | |
| } | |
| size_t ZHeap::tlab_capacity() const { | |
| return _tlab_usage.tlab_capacity(); | |
| } | |
| size_t ZHeap::tlab_used() const { | |
| return _tlab_usage.tlab_used(); | |
| } | |
| size_t ZHeap::max_tlab_size() const { | |
| return ZObjectSizeLimitSmall; | |
| } | |
| size_t ZHeap::unsafe_max_tlab_alloc() const { | |
| size_t size = _allocator_eden.remaining(); | |
| if (size < MinTLABSize) { | |
| // The remaining space in the allocator is not enough to | |
| // fit the smallest possible TLAB. This means that the next | |
| // TLAB allocation will force the allocator to get a new | |
| // backing page anyway, which in turn means that we can then | |
| // fit the largest possible TLAB. | |
| size = max_tlab_size(); | |
| } | |
| return MIN2(size, max_tlab_size()); | |
| } | |
| void ZHeap::reset_tlab_used() { | |
| _tlab_usage.reset(); | |
| } | |
| bool ZHeap::is_in(uintptr_t addr) const { | |
| if (addr == 0) { | |
| // Null isn't in the heap. | |
| return false; | |
| } | |
| // An address is considered to be "in the heap" if it points into | |
| // the allocated part of a page, regardless of which heap view is | |
| // used. Note that an address with the finalizable metadata bit set | |
| // is not pointing into a heap view, and therefore not considered | |
| // to be "in the heap". | |
| assert(!is_valid(zpointer(addr)), "Don't pass in colored oops"); | |
| if (!is_valid(zaddress(addr))) { | |
| return false; | |
| } | |
| const zaddress o = to_zaddress(addr); | |
| const ZPage* const page = _page_table.get(o); | |
| if (page == nullptr) { | |
| return false; | |
| } | |
| return is_in_page_relaxed(page, o); | |
| } | |
| bool ZHeap::is_in_page_relaxed(const ZPage* page, zaddress addr) const { | |
| if (page->is_in(addr)) { | |
| return true; | |
| } | |
| // Could still be a from-object during an in-place relocation | |
| if (_old.is_phase_relocate()) { | |
| const ZForwarding* const forwarding = _old.forwarding(unsafe(addr)); | |
| if (forwarding != nullptr && forwarding->in_place_relocation_is_below_top_at_start(ZAddress::offset(addr))) { | |
| return true; | |
| } | |
| } | |
| if (_young.is_phase_relocate()) { | |
| const ZForwarding* const forwarding = _young.forwarding(unsafe(addr)); | |
| if (forwarding != nullptr && forwarding->in_place_relocation_is_below_top_at_start(ZAddress::offset(addr))) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| void ZHeap::threads_do(ThreadClosure* tc) const { | |
| _page_allocator.threads_do(tc); | |
| _young.threads_do(tc); | |
| _old.threads_do(tc); | |
| } | |
| void ZHeap::out_of_memory() { | |
| ResourceMark rm; | |
| ZStatInc(ZCounterOutOfMemory); | |
| log_info(gc)("Out Of Memory (%s)", Thread::current()->name()); | |
| } | |
| static bool is_small_eden_page(ZPage* page) { | |
| return page->type() == ZPageType::small && page->age() == ZPageAge::eden; | |
| } | |
| void ZHeap::account_alloc_page(ZPage* page) { | |
| // Do TLAB accounting for small eden pages | |
| if (is_small_eden_page(page)) { | |
| _tlab_usage.increase_used(page->size()); | |
| } | |
| } | |
| void ZHeap::account_undo_alloc_page(ZPage* page) { | |
| // Increase the undo counter | |
| ZStatInc(ZCounterUndoPageAllocation); | |
| // Undo TLAB accounting for small eden pages | |
| if (is_small_eden_page(page)) { | |
| _tlab_usage.decrease_used(page->size()); | |
| } | |
| log_trace(gc)("Undo page allocation, thread: " PTR_FORMAT " (%s), page: " PTR_FORMAT ", size: %zu", | |
| p2i(Thread::current()), ZUtils::thread_name(), p2i(page), page->size()); | |
| } | |
| ZPage* ZHeap::alloc_page(ZPageType type, size_t size, ZAllocationFlags flags, ZPageAge age) { | |
| ZPage* const page = _page_allocator.alloc_page(type, size, flags, age); | |
| if (page != nullptr) { | |
| // Insert page table entry | |
| _page_table.insert(page); | |
| // Do accounting for the allocated page | |
| account_alloc_page(page); | |
| } | |
| return page; | |
| } | |
| void ZHeap::undo_alloc_page(ZPage* page) { | |
| assert(page->is_allocating(), "Invalid page state"); | |
| // Undo accounting for the page being freed | |
| account_undo_alloc_page(page); | |
| free_page(page); | |
| } | |
| void ZHeap::free_page(ZPage* page) { | |
| // Remove page table entry | |
| _page_table.remove(page); | |
| // Free page | |
| _page_allocator.free_page(page); | |
| } | |
| size_t ZHeap::free_empty_pages(ZGenerationId id, const ZArray<ZPage*>* pages) { | |
| size_t freed = 0; | |
| // Remove page table entries | |
| ZArrayIterator<ZPage*> iter(pages); | |
| for (ZPage* page; iter.next(&page);) { | |
| _page_table.remove(page); | |
| freed += page->size(); | |
| } | |
| // Free pages | |
| _page_allocator.free_pages(id, pages); | |
| return freed; | |
| } | |
| void ZHeap::keep_alive(oop obj) { | |
| const zaddress addr = to_zaddress(obj); | |
| ZBarrier::mark<ZMark::Resurrect, ZMark::AnyThread, ZMark::Follow, ZMark::Strong>(addr); | |
| } | |
| void ZHeap::mark_flush(Thread* thread) { | |
| _young.mark_flush(thread); | |
| _old.mark_flush(thread); | |
| } | |
| bool ZHeap::is_allocating(zaddress addr) const { | |
| const ZPage* const page = _page_table.get(addr); | |
| return page->is_allocating(); | |
| } | |
| void ZHeap::object_iterate(ObjectClosure* object_cl, bool visit_weaks) { | |
| assert(SafepointSynchronize::is_at_safepoint(), "Should be at safepoint"); | |
| ZHeapIterator iter(1 /* nworkers */, visit_weaks, false /* for_verify */); | |
| iter.object_iterate(object_cl, 0 /* worker_id */); | |
| } | |
| void ZHeap::object_and_field_iterate_for_verify(ObjectClosure* object_cl, OopFieldClosure* field_cl, bool visit_weaks) { | |
| assert(SafepointSynchronize::is_at_safepoint(), "Should be at safepoint"); | |
| ZHeapIterator iter(1 /* nworkers */, visit_weaks, true /* for_verify */); | |
| iter.object_and_field_iterate(object_cl, field_cl, 0 /* worker_id */); | |
| } | |
| ParallelObjectIteratorImpl* ZHeap::parallel_object_iterator(uint nworkers, bool visit_weaks) { | |
| assert(SafepointSynchronize::is_at_safepoint(), "Should be at safepoint"); | |
| return new ZHeapIterator(nworkers, visit_weaks, false /* for_verify */); | |
| } | |
| void ZHeap::serviceability_initialize() { | |
| _serviceability.initialize(); | |
| } | |
| GCMemoryManager* ZHeap::serviceability_cycle_memory_manager(bool minor) { | |
| return _serviceability.cycle_memory_manager(minor); | |
| } | |
| GCMemoryManager* ZHeap::serviceability_pause_memory_manager(bool minor) { | |
| return _serviceability.pause_memory_manager(minor); | |
| } | |
| MemoryPool* ZHeap::serviceability_memory_pool(ZGenerationId id) { | |
| return _serviceability.memory_pool(id); | |
| } | |
| ZServiceabilityCounters* ZHeap::serviceability_counters() { | |
| return _serviceability.counters(); | |
| } | |
| void ZHeap::print_usage_on(outputStream* st) const { | |
| _page_allocator.print_usage_on(st); | |
| } | |
| void ZHeap::print_gc_on(outputStream* st) const { | |
| print_globals_on(st); | |
| st->cr(); | |
| print_page_table_on(st); | |
| st->cr(); | |
| _page_allocator.print_cache_extended_on(st); | |
| } | |
| void ZHeap::print_globals_on(outputStream* st) const { | |
| st->print_cr("ZGC Globals:"); | |
| st->print_cr(" Young Collection: %s/%u", ZGeneration::young()->phase_to_string(), ZGeneration::young()->seqnum()); | |
| st->print_cr(" Old Collection: %s/%u", ZGeneration::old()->phase_to_string(), ZGeneration::old()->seqnum()); | |
| st->print_cr(" Offset Max: " EXACTFMT " (" PTR_FORMAT ")", EXACTFMTARGS(ZAddressOffsetMax), ZAddressOffsetMax); | |
| st->print_cr(" Page Size Small: %zuM", ZPageSizeSmall / M); | |
| if (ZPageSizeMediumEnabled) { | |
| if (ZPageSizeMediumMin == ZPageSizeMediumMax) { | |
| st->print_cr(" Page Size Medium: %zuM", ZPageSizeMediumMax / M); | |
| } else { | |
| st->print_cr(" Page Size Medium: Range [%zuM, %zuM]", ZPageSizeMediumMin / M, ZPageSizeMediumMax / M); | |
| } | |
| } else { | |
| st->print_cr(" Page Size Medium: N/A"); | |
| } | |
| st->cr(); | |
| st->print_cr("ZGC Metadata Bits:"); | |
| st->print_cr(" LoadGood: " PTR_FORMAT, ZPointerLoadGoodMask); | |
| st->print_cr(" LoadBad: " PTR_FORMAT, ZPointerLoadBadMask); | |
| st->print_cr(" MarkGood: " PTR_FORMAT, ZPointerMarkGoodMask); | |
| st->print_cr(" MarkBad: " PTR_FORMAT, ZPointerMarkBadMask); | |
| st->print_cr(" StoreGood: " PTR_FORMAT, ZPointerStoreGoodMask); | |
| st->print_cr(" StoreBad: " PTR_FORMAT, ZPointerStoreBadMask); | |
| st->print_cr(" ------------------- "); | |
| st->print_cr(" Remapped: " PTR_FORMAT, ZPointerRemapped); | |
| st->print_cr(" RemappedYoung: " PTR_FORMAT, ZPointerRemappedYoungMask); | |
| st->print_cr(" RemappedOld: " PTR_FORMAT, ZPointerRemappedOldMask); | |
| st->print_cr(" MarkedYoung: " PTR_FORMAT, ZPointerMarkedYoung); | |
| st->print_cr(" MarkedOld: " PTR_FORMAT, ZPointerMarkedOld); | |
| st->print_cr(" Remembered: " PTR_FORMAT, ZPointerRemembered); | |
| } | |
| void ZHeap::print_page_table_on(outputStream* st) const { | |
| // Do not allow pages to be deleted | |
| _page_allocator.enable_safe_destroy(); | |
| // Print all pages | |
| st->print_cr("ZGC Page Table:"); | |
| StreamIndentor si(st, 1); | |
| ZPageTableIterator iter(&_page_table); | |
| for (ZPage* page; iter.next(&page);) { | |
| page->print_on(st); | |
| } | |
| // Allow pages to be deleted | |
| _page_allocator.disable_safe_destroy(); | |
| } | |
| bool ZHeap::print_location(outputStream* st, uintptr_t addr) const { | |
| // Intentionally unchecked cast | |
| const bool uncolored = is_valid(zaddress(addr)); | |
| const bool colored = is_valid(zpointer(addr)); | |
| if (colored && uncolored) { | |
| // Should not reach here | |
| return false; | |
| } | |
| if (colored) { | |
| return print_location(st, zpointer(addr)); | |
| } | |
| if (uncolored) { | |
| return print_location(st, zaddress(addr)); | |
| } | |
| return false; | |
| } | |
| bool ZHeap::print_location(outputStream* st, zaddress addr) const { | |
| assert(is_valid(addr), "must be"); | |
| st->print(PTR_FORMAT " is a zaddress: ", untype(addr)); | |
| if (addr == zaddress::null) { | |
| st->print_raw_cr("null"); | |
| return true; | |
| } | |
| if (!ZHeap::is_in(untype(addr))) { | |
| st->print_raw_cr("not in heap"); | |
| return false; | |
| } | |
| if (LocationPrinter::is_valid_obj((void*)untype(addr))) { | |
| to_oop(addr)->print_on(st); | |
| return true; | |
| } | |
| ZPage* const page = ZHeap::page(addr); | |
| zaddress_unsafe base; | |
| if (page->is_relocatable() && page->is_marked() && !ZGeneration::generation(page->generation_id())->is_phase_mark()) { | |
| base = page->find_base((volatile zpointer*) addr); | |
| } else { | |
| // TODO: This part is probably broken, but register printing recovers from crashes | |
| st->print_raw("Unreliable "); | |
| base = page->find_base_unsafe((volatile zpointer*) addr); | |
| } | |
| if (base == zaddress_unsafe::null) { | |
| st->print_raw_cr("Cannot find base"); | |
| return false; | |
| } | |
| if (untype(base) == untype(addr)) { | |
| st->print_raw_cr("Bad mark info/base"); | |
| return false; | |
| } | |
| st->print_raw_cr("Internal address"); | |
| print_location(st, untype(base)); | |
| return true; | |
| } | |
| bool ZHeap::print_location(outputStream* st, zpointer ptr) const { | |
| assert(is_valid(ptr), "must be"); | |
| st->print(PTR_FORMAT " is %s zpointer: ", untype(ptr), | |
| ZPointer::is_load_good(ptr) ? "a good" : "a bad"); | |
| if (!ZPointer::is_load_good(ptr)) { | |
| st->print_cr("decoded " PTR_FORMAT, untype(ZPointer::uncolor_unsafe(ptr))); | |
| // ptr is not load good but let us still investigate the uncolored address | |
| return print_location(st, untype(ZPointer::uncolor_unsafe(ptr))); | |
| } | |
| const zaddress addr = ZPointer::uncolor(ptr); | |
| if (addr == zaddress::null) { | |
| st->print_raw_cr("null"); | |
| return true; | |
| } | |
| if (LocationPrinter::is_valid_obj((void*)untype(addr))) { | |
| to_oop(addr)->print_on(st); | |
| return true; | |
| } | |
| st->print_cr("invalid object " PTR_FORMAT, untype(addr)); | |
| return false; | |
| } |