| /* |
| * 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); |
| } |