| /* | |
| * 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 "gc/z/zGlobals.hpp" | |
| #include "gc/z/zHeap.inline.hpp" | |
| #include "gc/z/zHeuristics.hpp" | |
| #include "gc/z/zLock.inline.hpp" | |
| #include "gc/z/zObjectAllocator.hpp" | |
| #include "gc/z/zPage.inline.hpp" | |
| #include "gc/z/zPageTable.inline.hpp" | |
| #include "gc/z/zStat.hpp" | |
| #include "gc/z/zValue.inline.hpp" | |
| #include "logging/log.hpp" | |
| #include "runtime/atomic.hpp" | |
| #include "runtime/safepoint.hpp" | |
| #include "runtime/thread.hpp" | |
| #include "utilities/align.hpp" | |
| #include "utilities/debug.hpp" | |
| static const ZStatCounter ZCounterUndoObjectAllocationSucceeded("Memory", "Undo Object Allocation Succeeded", ZStatUnitOpsPerSecond); | |
| static const ZStatCounter ZCounterUndoObjectAllocationFailed("Memory", "Undo Object Allocation Failed", ZStatUnitOpsPerSecond); | |
| ZObjectAllocator::ZObjectAllocator(ZPageAge age) | |
| : _age(age), | |
| _use_per_cpu_shared_small_pages(ZHeuristics::use_per_cpu_shared_small_pages()), | |
| _shared_small_page(nullptr), | |
| _shared_medium_page(nullptr), | |
| _medium_page_alloc_lock() {} | |
| ZPage** ZObjectAllocator::shared_small_page_addr() { | |
| return _use_per_cpu_shared_small_pages ? _shared_small_page.addr() : _shared_small_page.addr(0); | |
| } | |
| ZPage* const* ZObjectAllocator::shared_small_page_addr() const { | |
| return _use_per_cpu_shared_small_pages ? _shared_small_page.addr() : _shared_small_page.addr(0); | |
| } | |
| ZPage* ZObjectAllocator::alloc_page(ZPageType type, size_t size, ZAllocationFlags flags) { | |
| return ZHeap::heap()->alloc_page(type, size, flags, _age); | |
| } | |
| ZPage* ZObjectAllocator::alloc_page_for_relocation(ZPageType type, size_t size, ZAllocationFlags flags) { | |
| return ZHeap::heap()->alloc_page(type, size, flags, _age); | |
| } | |
| void ZObjectAllocator::undo_alloc_page(ZPage* page) { | |
| ZHeap::heap()->undo_alloc_page(page); | |
| } | |
| zaddress ZObjectAllocator::alloc_object_in_shared_page(ZPage** shared_page, | |
| ZPageType page_type, | |
| size_t page_size, | |
| size_t size, | |
| ZAllocationFlags flags) { | |
| zaddress addr = zaddress::null; | |
| ZPage* page = Atomic::load_acquire(shared_page); | |
| if (page != nullptr) { | |
| addr = page->alloc_object_atomic(size); | |
| } | |
| if (is_null(addr)) { | |
| // Allocate new page | |
| ZPage* const new_page = alloc_page(page_type, page_size, flags); | |
| if (new_page != nullptr) { | |
| // Allocate object before installing the new page | |
| addr = new_page->alloc_object(size); | |
| retry: | |
| // Install new page | |
| ZPage* const prev_page = Atomic::cmpxchg(shared_page, page, new_page); | |
| if (prev_page != page) { | |
| if (prev_page == nullptr) { | |
| // Previous page was retired, retry installing the new page | |
| page = prev_page; | |
| goto retry; | |
| } | |
| // Another page already installed, try allocation there first | |
| const zaddress prev_addr = prev_page->alloc_object_atomic(size); | |
| if (is_null(prev_addr)) { | |
| // Allocation failed, retry installing the new page | |
| page = prev_page; | |
| goto retry; | |
| } | |
| // Allocation succeeded in already installed page | |
| addr = prev_addr; | |
| // Undo new page allocation | |
| undo_alloc_page(new_page); | |
| } | |
| } | |
| } | |
| return addr; | |
| } | |
| zaddress ZObjectAllocator::alloc_object_in_medium_page(size_t size, | |
| ZAllocationFlags flags) { | |
| zaddress addr = zaddress::null; | |
| ZPage** shared_medium_page = _shared_medium_page.addr(); | |
| ZPage* page = Atomic::load_acquire(shared_medium_page); | |
| if (page != nullptr) { | |
| addr = page->alloc_object_atomic(size); | |
| } | |
| if (is_null(addr)) { | |
| // When a new medium page is required, we synchronize the allocation of the | |
| // new page using a lock. This is to avoid having multiple threads allocate | |
| // medium pages when we know only one of them will succeed in installing | |
| // the page at this layer. | |
| ZLocker<ZLock> locker(&_medium_page_alloc_lock); | |
| // When holding the lock we can't allow the page allocator to stall, | |
| // which in the common case it won't. The page allocation is thus done | |
| // in a non-blocking fashion and only if this fails we below (while not | |
| // holding the lock) do the blocking page allocation. | |
| ZAllocationFlags non_blocking_flags = flags; | |
| non_blocking_flags.set_non_blocking(); | |
| if (ZPageSizeMediumMin != ZPageSizeMediumMax) { | |
| assert(ZPageSizeMediumEnabled, "must be enabled"); | |
| // We attempt a fast medium allocations first. Which will only succeed | |
| // if a page in the range [ZPageSizeMediumMin, ZPageSizeMediumMax] can | |
| // be allocated without any expensive syscalls, directly from the cache. | |
| ZAllocationFlags fast_medium_flags = non_blocking_flags; | |
| fast_medium_flags.set_fast_medium(); | |
| addr = alloc_object_in_shared_page(shared_medium_page, ZPageType::medium, ZPageSizeMediumMax, size, fast_medium_flags); | |
| } | |
| if (is_null(addr)) { | |
| addr = alloc_object_in_shared_page(shared_medium_page, ZPageType::medium, ZPageSizeMediumMax, size, non_blocking_flags); | |
| } | |
| } | |
| if (is_null(addr) && !flags.non_blocking()) { | |
| // The above allocation attempts failed and this allocation should stall | |
| // until memory is available. Redo the allocation with blocking enabled. | |
| addr = alloc_object_in_shared_page(shared_medium_page, ZPageType::medium, ZPageSizeMediumMax, size, flags); | |
| } | |
| return addr; | |
| } | |
| zaddress ZObjectAllocator::alloc_large_object(size_t size, ZAllocationFlags flags) { | |
| zaddress addr = zaddress::null; | |
| // Allocate new large page | |
| const size_t page_size = align_up(size, ZGranuleSize); | |
| ZPage* const page = alloc_page(ZPageType::large, page_size, flags); | |
| if (page != nullptr) { | |
| // Allocate the object | |
| addr = page->alloc_object(size); | |
| } | |
| return addr; | |
| } | |
| zaddress ZObjectAllocator::alloc_medium_object(size_t size, ZAllocationFlags flags) { | |
| return alloc_object_in_medium_page(size, flags); | |
| } | |
| zaddress ZObjectAllocator::alloc_small_object(size_t size, ZAllocationFlags flags) { | |
| return alloc_object_in_shared_page(shared_small_page_addr(), ZPageType::small, ZPageSizeSmall, size, flags); | |
| } | |
| zaddress ZObjectAllocator::alloc_object(size_t size, ZAllocationFlags flags) { | |
| if (size <= ZObjectSizeLimitSmall) { | |
| // Small | |
| return alloc_small_object(size, flags); | |
| } else if (size <= ZObjectSizeLimitMedium) { | |
| // Medium | |
| return alloc_medium_object(size, flags); | |
| } else { | |
| // Large | |
| return alloc_large_object(size, flags); | |
| } | |
| } | |
| zaddress ZObjectAllocator::alloc_object(size_t size) { | |
| const ZAllocationFlags flags; | |
| return alloc_object(size, flags); | |
| } | |
| zaddress ZObjectAllocator::alloc_object_for_relocation(size_t size) { | |
| ZAllocationFlags flags; | |
| flags.set_non_blocking(); | |
| return alloc_object(size, flags); | |
| } | |
| void ZObjectAllocator::undo_alloc_object_for_relocation(zaddress addr, size_t size) { | |
| ZPage* const page = ZHeap::heap()->page(addr); | |
| if (page->is_large()) { | |
| undo_alloc_page(page); | |
| ZStatInc(ZCounterUndoObjectAllocationSucceeded); | |
| } else { | |
| if (page->undo_alloc_object_atomic(addr, size)) { | |
| ZStatInc(ZCounterUndoObjectAllocationSucceeded); | |
| } else { | |
| ZStatInc(ZCounterUndoObjectAllocationFailed); | |
| } | |
| } | |
| } | |
| ZPageAge ZObjectAllocator::age() const { | |
| return _age; | |
| } | |
| size_t ZObjectAllocator::remaining() const { | |
| assert(Thread::current()->is_Java_thread(), "Should be a Java thread"); | |
| const ZPage* const page = Atomic::load_acquire(shared_small_page_addr()); | |
| if (page != nullptr) { | |
| return page->remaining(); | |
| } | |
| return 0; | |
| } | |
| void ZObjectAllocator::retire_pages() { | |
| assert(SafepointSynchronize::is_at_safepoint(), "Should be at safepoint"); | |
| // Reset allocation pages | |
| _shared_medium_page.set(nullptr); | |
| _shared_small_page.set_all(nullptr); | |
| } |