| /* |
| * 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/shared/gc_globals.hpp" |
| #include "gc/shared/gcLogPrecious.hpp" |
| #include "gc/z/zAddress.inline.hpp" |
| #include "gc/z/zAddressSpaceLimit.hpp" |
| #include "gc/z/zArray.hpp" |
| #include "gc/z/zGlobals.hpp" |
| #include "gc/z/zInitialize.hpp" |
| #include "gc/z/zNMT.hpp" |
| #include "gc/z/zNUMA.inline.hpp" |
| #include "gc/z/zValue.inline.hpp" |
| #include "gc/z/zVirtualMemory.inline.hpp" |
| #include "gc/z/zVirtualMemoryManager.inline.hpp" |
| #include "utilities/align.hpp" |
| #include "utilities/debug.hpp" |
| |
| ZVirtualMemoryReserver::ZVirtualMemoryReserver(size_t size) |
| : _registry(), |
| _reserved(reserve(size)) {} |
| |
| void ZVirtualMemoryReserver::initialize_partition_registry(ZVirtualMemoryRegistry* partition_registry, size_t size) { |
| assert(partition_registry->is_empty(), "Should be empty when initializing"); |
| |
| // Registers the Windows callbacks |
| pd_register_callbacks(partition_registry); |
| |
| _registry.transfer_from_low(partition_registry, size); |
| |
| // Set the limits according to the virtual memory given to this partition |
| partition_registry->anchor_limits(); |
| } |
| |
| void ZVirtualMemoryReserver::unreserve(const ZVirtualMemory& vmem) { |
| const zaddress_unsafe addr = ZOffset::address_unsafe(vmem.start()); |
| |
| // Unregister the reserved memory from NMT |
| ZNMT::unreserve(addr, vmem.size()); |
| |
| // Unreserve address space |
| pd_unreserve(addr, vmem.size()); |
| } |
| |
| void ZVirtualMemoryReserver::unreserve_all() { |
| for (ZVirtualMemory vmem; _registry.unregister_first(&vmem);) { |
| unreserve(vmem); |
| } |
| } |
| |
| bool ZVirtualMemoryReserver::is_empty() const { |
| return _registry.is_empty(); |
| } |
| |
| bool ZVirtualMemoryReserver::is_contiguous() const { |
| return _registry.is_contiguous(); |
| } |
| |
| size_t ZVirtualMemoryReserver::reserved() const { |
| return _reserved; |
| } |
| |
| zoffset_end ZVirtualMemoryReserver::highest_available_address_end() const { |
| return _registry.peak_high_address_end(); |
| } |
| |
| #ifdef ASSERT |
| size_t ZVirtualMemoryReserver::force_reserve_discontiguous(size_t size) { |
| const size_t min_range = calculate_min_range(size); |
| const size_t max_range = MAX2(align_down(size / ZForceDiscontiguousHeapReservations, ZGranuleSize), min_range); |
| size_t reserved = 0; |
| |
| // Try to reserve ZForceDiscontiguousHeapReservations number of virtual memory |
| // ranges. Starting with higher addresses. |
| uintptr_t end = ZAddressOffsetMax; |
| while (reserved < size && end >= max_range) { |
| const size_t remaining = size - reserved; |
| const size_t reserve_size = MIN2(max_range, remaining); |
| const uintptr_t reserve_start = end - reserve_size; |
| |
| if (reserve_contiguous(to_zoffset(reserve_start), reserve_size)) { |
| reserved += reserve_size; |
| } |
| |
| end -= reserve_size * 2; |
| } |
| |
| // If (reserved < size) attempt to reserve the rest via normal divide and conquer |
| uintptr_t start = 0; |
| while (reserved < size && start < ZAddressOffsetMax) { |
| const size_t remaining = MIN2(size - reserved, ZAddressOffsetMax - start); |
| reserved += reserve_discontiguous(to_zoffset(start), remaining, min_range); |
| start += remaining; |
| } |
| |
| return reserved; |
| } |
| #endif |
| |
| size_t ZVirtualMemoryReserver::reserve_discontiguous(zoffset start, size_t size, size_t min_range) { |
| if (size < min_range) { |
| // Too small |
| return 0; |
| } |
| |
| assert(is_aligned(size, ZGranuleSize), "Misaligned"); |
| |
| if (reserve_contiguous(start, size)) { |
| return size; |
| } |
| |
| const size_t half = size / 2; |
| if (half < min_range) { |
| // Too small |
| return 0; |
| } |
| |
| // Divide and conquer |
| const size_t first_part = align_down(half, ZGranuleSize); |
| const size_t second_part = size - first_part; |
| const size_t first_size = reserve_discontiguous(start, first_part, min_range); |
| const size_t second_size = reserve_discontiguous(start + first_part, second_part, min_range); |
| return first_size + second_size; |
| } |
| |
| size_t ZVirtualMemoryReserver::calculate_min_range(size_t size) { |
| // Don't try to reserve address ranges smaller than 1% of the requested size. |
| // This avoids an explosion of reservation attempts in case large parts of the |
| // address space is already occupied. |
| return align_up(size / ZMaxVirtualReservations, ZGranuleSize); |
| } |
| |
| size_t ZVirtualMemoryReserver::reserve_discontiguous(size_t size) { |
| const size_t min_range = calculate_min_range(size); |
| uintptr_t start = 0; |
| size_t reserved = 0; |
| |
| // Reserve size somewhere between [0, ZAddressOffsetMax) |
| while (reserved < size && start < ZAddressOffsetMax) { |
| const size_t remaining = MIN2(size - reserved, ZAddressOffsetMax - start); |
| reserved += reserve_discontiguous(to_zoffset(start), remaining, min_range); |
| start += remaining; |
| } |
| |
| return reserved; |
| } |
| |
| bool ZVirtualMemoryReserver::reserve_contiguous(zoffset start, size_t size) { |
| assert(is_aligned(size, ZGranuleSize), "Must be granule aligned 0x%zx", size); |
| |
| // Reserve address views |
| const zaddress_unsafe addr = ZOffset::address_unsafe(start); |
| |
| // Reserve address space |
| if (!pd_reserve(addr, size)) { |
| return false; |
| } |
| |
| // Register address views with native memory tracker |
| ZNMT::reserve(addr, size); |
| |
| // Register the memory reservation |
| _registry.register_range({start, size}); |
| |
| return true; |
| } |
| |
| bool ZVirtualMemoryReserver::reserve_contiguous(size_t size) { |
| // Allow at most 8192 attempts spread evenly across [0, ZAddressOffsetMax) |
| const size_t unused = ZAddressOffsetMax - size; |
| const size_t increment = MAX2(align_up(unused / 8192, ZGranuleSize), ZGranuleSize); |
| |
| for (uintptr_t start = 0; start + size <= ZAddressOffsetMax; start += increment) { |
| if (reserve_contiguous(to_zoffset(start), size)) { |
| // Success |
| return true; |
| } |
| } |
| |
| // Failed |
| return false; |
| } |
| |
| size_t ZVirtualMemoryReserver::reserve(size_t size) { |
| // Register Windows callbacks |
| pd_register_callbacks(&_registry); |
| |
| // Reserve address space |
| |
| #ifdef ASSERT |
| if (ZForceDiscontiguousHeapReservations > 0) { |
| return force_reserve_discontiguous(size); |
| } |
| #endif |
| |
| // Prefer a contiguous address space |
| if (reserve_contiguous(size)) { |
| return size; |
| } |
| |
| // Fall back to a discontiguous address space |
| return reserve_discontiguous(size); |
| } |
| |
| ZVirtualMemoryManager::ZVirtualMemoryManager(size_t max_capacity) |
| : _partition_registries(), |
| _multi_partition_registry(), |
| _is_multi_partition_enabled(false), |
| _initialized(false) { |
| |
| assert(max_capacity <= ZAddressOffsetMax, "Too large max_capacity"); |
| |
| ZAddressSpaceLimit::print_limits(); |
| |
| const size_t limit = MIN2(ZAddressOffsetMax, ZAddressSpaceLimit::heap()); |
| |
| const size_t desired_for_partitions = max_capacity * ZVirtualToPhysicalRatio; |
| const size_t desired_for_multi_partition = ZNUMA::count() > 1 ? desired_for_partitions : 0; |
| |
| const size_t desired = desired_for_partitions + desired_for_multi_partition; |
| const size_t requested = desired <= limit |
| ? desired |
| : MIN2(desired_for_partitions, limit); |
| |
| // Reserve virtual memory for the heap |
| ZVirtualMemoryReserver reserver(requested); |
| |
| const size_t reserved = reserver.reserved(); |
| const bool is_contiguous = reserver.is_contiguous(); |
| |
| log_debug_p(gc, init)("Reserved Space: limit " EXACTFMT ", desired " EXACTFMT ", requested " EXACTFMT, |
| EXACTFMTARGS(limit), EXACTFMTARGS(desired), EXACTFMTARGS(requested)); |
| |
| if (reserved < max_capacity) { |
| ZInitialize::error_d("Failed to reserve " EXACTFMT " address space for Java heap", EXACTFMTARGS(max_capacity)); |
| return; |
| } |
| |
| // Set ZAddressOffsetMax to the highest address end available after reservation |
| ZAddressOffsetMax = untype(reserver.highest_available_address_end()); |
| |
| const size_t size_for_partitions = MIN2(reserved, desired_for_partitions); |
| |
| // Divide size_for_partitions virtual memory over the NUMA nodes |
| initialize_partitions(&reserver, size_for_partitions); |
| |
| // Set up multi-partition or unreserve the surplus memory |
| if (desired_for_multi_partition > 0 && reserved == desired) { |
| // Enough left to setup the multi-partition memory reservation |
| reserver.initialize_partition_registry(&_multi_partition_registry, desired_for_multi_partition); |
| _is_multi_partition_enabled = true; |
| } else { |
| // Failed to reserve enough memory for multi-partition, unreserve unused memory |
| reserver.unreserve_all(); |
| } |
| |
| assert(reserver.is_empty(), "Must have handled all reserved memory"); |
| |
| log_info_p(gc, init)("Reserved Space Type: %s/%s/%s", |
| (is_contiguous ? "Contiguous" : "Discontiguous"), |
| (requested == desired ? "Unrestricted" : "Restricted"), |
| (reserved == desired ? "Complete" : ((reserved < desired_for_partitions) ? "Degraded" : "NUMA-Degraded"))); |
| log_info_p(gc, init)("Reserved Space Size: " EXACTFMT, EXACTFMTARGS(reserved)); |
| |
| // Successfully initialized |
| _initialized = true; |
| } |
| |
| void ZVirtualMemoryManager::initialize_partitions(ZVirtualMemoryReserver* reserver, size_t size_for_partitions) { |
| precond(is_aligned(size_for_partitions, ZGranuleSize)); |
| |
| // If the capacity consist of less granules than the number of partitions |
| // some partitions will be empty. Distribute these shares on the none empty |
| // partitions. |
| const uint32_t first_empty_numa_id = MIN2(static_cast<uint32_t>(size_for_partitions >> ZGranuleSizeShift), ZNUMA::count()); |
| const uint32_t ignore_count = ZNUMA::count() - first_empty_numa_id; |
| |
| // Install reserved memory into registry(s) |
| uint32_t numa_id; |
| ZPerNUMAIterator<ZVirtualMemoryRegistry> iter(&_partition_registries); |
| for (ZVirtualMemoryRegistry* registry; iter.next(®istry, &numa_id);) { |
| if (numa_id == first_empty_numa_id) { |
| break; |
| } |
| |
| // Calculate how much reserved memory this partition gets |
| const size_t reserved_for_partition = ZNUMA::calculate_share(numa_id, size_for_partitions, ZGranuleSize, ignore_count); |
| |
| // Transfer reserved memory |
| reserver->initialize_partition_registry(registry, reserved_for_partition); |
| } |
| } |
| |
| bool ZVirtualMemoryManager::is_initialized() const { |
| return _initialized; |
| } |
| |
| ZVirtualMemoryRegistry& ZVirtualMemoryManager::registry(uint32_t partition_id) { |
| return _partition_registries.get(partition_id); |
| } |
| |
| const ZVirtualMemoryRegistry& ZVirtualMemoryManager::registry(uint32_t partition_id) const { |
| return _partition_registries.get(partition_id); |
| } |
| |
| zoffset ZVirtualMemoryManager::lowest_available_address(uint32_t partition_id) const { |
| return registry(partition_id).peek_low_address(); |
| } |
| |
| void ZVirtualMemoryManager::insert(const ZVirtualMemory& vmem, uint32_t partition_id) { |
| assert(partition_id == lookup_partition_id(vmem), "wrong partition_id for vmem"); |
| registry(partition_id).insert(vmem); |
| } |
| |
| void ZVirtualMemoryManager::insert_multi_partition(const ZVirtualMemory& vmem) { |
| _multi_partition_registry.insert(vmem); |
| } |
| |
| size_t ZVirtualMemoryManager::remove_from_low_many_at_most(size_t size, uint32_t partition_id, ZArray<ZVirtualMemory>* vmems_out) { |
| return registry(partition_id).remove_from_low_many_at_most(size, vmems_out); |
| } |
| |
| ZVirtualMemory ZVirtualMemoryManager::remove_from_low(size_t size, uint32_t partition_id) { |
| return registry(partition_id).remove_from_low(size); |
| } |
| |
| ZVirtualMemory ZVirtualMemoryManager::remove_from_low_multi_partition(size_t size) { |
| return _multi_partition_registry.remove_from_low(size); |
| } |
| |
| void ZVirtualMemoryManager::insert_and_remove_from_low_many(const ZVirtualMemory& vmem, uint32_t partition_id, ZArray<ZVirtualMemory>* vmems_out) { |
| registry(partition_id).insert_and_remove_from_low_many(vmem, vmems_out); |
| } |
| |
| ZVirtualMemory ZVirtualMemoryManager::insert_and_remove_from_low_exact_or_many(size_t size, uint32_t partition_id, ZArray<ZVirtualMemory>* vmems_in_out) { |
| return registry(partition_id).insert_and_remove_from_low_exact_or_many(size, vmems_in_out); |
| } |