diff --git a/src/hotspot/os/aix/os_aix.cpp b/src/hotspot/os/aix/os_aix.cpp
index f373ab12bd0..e0a403b26f8 100644
--- a/src/hotspot/os/aix/os_aix.cpp
+++ b/src/hotspot/os/aix/os_aix.cpp
@@ -2166,6 +2166,15 @@ char* os::pd_attempt_reserve_memory_at(char* requested_addr, size_t bytes, bool
return addr;
}
+size_t os::vm_min_address() {
+ // On AIX, we need to make sure we don't block the sbrk. However, this is
+ // done at actual reservation time, where we honor a "no-mmap" area following
+ // the break. See MaxExpectedDataSegmentSize. So we can return a very low
+ // address here.
+ assert(is_aligned(_vm_min_address_default, os::vm_allocation_granularity()), "Sanity");
+ return _vm_min_address_default;
+}
+
// Used to convert frequent JVM_Yield() to nops
bool os::dont_yield() {
return DontYieldALot;
diff --git a/src/hotspot/os/bsd/os_bsd.cpp b/src/hotspot/os/bsd/os_bsd.cpp
index ae4bc6e42a9..054c89f5554 100644
--- a/src/hotspot/os/bsd/os_bsd.cpp
+++ b/src/hotspot/os/bsd/os_bsd.cpp
@@ -1822,6 +1822,17 @@ char* os::pd_attempt_reserve_memory_at(char* requested_addr, size_t bytes, bool
return nullptr;
}
+size_t os::vm_min_address() {
+#ifdef __APPLE__
+ // On MacOS, the lowest 4G are denied to the application (see "PAGEZERO" resp.
+ // -pagezero_size linker option).
+ return 4 * G;
+#else
+ assert(is_aligned(_vm_min_address_default, os::vm_allocation_granularity()), "Sanity");
+ return _vm_min_address_default;
+#endif
+}
+
// Used to convert frequent JVM_Yield() to nops
bool os::dont_yield() {
return DontYieldALot;
diff --git a/src/hotspot/os/linux/os_linux.cpp b/src/hotspot/os/linux/os_linux.cpp
index 2563fbf8f5c..3a1785b27b5 100644
--- a/src/hotspot/os/linux/os_linux.cpp
+++ b/src/hotspot/os/linux/os_linux.cpp
@@ -70,6 +70,7 @@
#include "services/runtimeService.hpp"
#include "utilities/align.hpp"
#include "utilities/checkedCast.hpp"
+#include "utilities/debug.hpp"
#include "utilities/decoder.hpp"
#include "utilities/defaultStream.hpp"
#include "utilities/events.hpp"
@@ -4244,6 +4245,23 @@ char* os::pd_attempt_reserve_memory_at(char* requested_addr, size_t bytes, bool
return nullptr;
}
+size_t os::vm_min_address() {
+ // Determined by sysctl vm.mmap_min_addr. It exists as a safety zone to prevent
+ // NULL pointer dereferences.
+ // Most distros set this value to 64 KB. It *can* be zero, but rarely is. Here,
+ // we impose a minimum value if vm.mmap_min_addr is too low, for increased protection.
+ static size_t value = 0;
+ if (value == 0) {
+ assert(is_aligned(_vm_min_address_default, os::vm_allocation_granularity()), "Sanity");
+ FILE* f = fopen("/proc/sys/vm/mmap_min_addr", "r");
+ if (fscanf(f, "%zu", &value) != 1) {
+ value = _vm_min_address_default;
+ }
+ value = MAX2(_vm_min_address_default, value);
+ }
+ return value;
+}
+
// Used to convert frequent JVM_Yield() to nops
bool os::dont_yield() {
return DontYieldALot;
diff --git a/src/hotspot/os/windows/os_windows.cpp b/src/hotspot/os/windows/os_windows.cpp
index ad88e714bfc..5563169151b 100644
--- a/src/hotspot/os/windows/os_windows.cpp
+++ b/src/hotspot/os/windows/os_windows.cpp
@@ -3435,6 +3435,11 @@ char* os::pd_attempt_reserve_memory_at(char* addr, size_t bytes, bool exec) {
return res;
}
+size_t os::vm_min_address() {
+ assert(is_aligned(_vm_min_address_default, os::vm_allocation_granularity()), "Sanity");
+ return _vm_min_address_default;
+}
+
char* os::pd_attempt_map_memory_to_file_at(char* requested_addr, size_t bytes, int file_desc) {
assert(file_desc >= 0, "file_desc is not valid");
return map_memory_to_file(requested_addr, bytes, file_desc);
diff --git a/src/hotspot/share/cds/metaspaceShared.cpp b/src/hotspot/share/cds/metaspaceShared.cpp
index e99ef29ef4e..e5bfb742840 100644
--- a/src/hotspot/share/cds/metaspaceShared.cpp
+++ b/src/hotspot/share/cds/metaspaceShared.cpp
@@ -1328,8 +1328,12 @@ char* MetaspaceShared::reserve_address_space_for_archives(FileMapInfo* static_ma
total_space_rs = ReservedSpace(total_range_size, archive_space_alignment,
os::vm_page_size(), (char*) base_address);
} else {
- // Reserve at any address, but leave it up to the platform to choose a good one.
- total_space_rs = Metaspace::reserve_address_space_for_compressed_classes(total_range_size);
+ // We did not manage to reserve at the preferred address, or were instructed to relocate. In that
+ // case we reserve whereever possible, but the start address needs to be encodable as narrow Klass
+ // encoding base since the archived heap objects contain nKlass IDs precalculated toward the start
+ // of the shared Metaspace. That prevents us from using zero-based encoding and therefore we won't
+ // try allocating in low-address regions.
+ total_space_rs = Metaspace::reserve_address_space_for_compressed_classes(total_range_size, false /* try_in_low_address_ranges */);
}
if (!total_space_rs.is_reserved()) {
diff --git a/src/hotspot/share/memory/metaspace.cpp b/src/hotspot/share/memory/metaspace.cpp
index a149deedd23..d8d01e7fd73 100644
--- a/src/hotspot/share/memory/metaspace.cpp
+++ b/src/hotspot/share/memory/metaspace.cpp
@@ -59,6 +59,7 @@
#include "utilities/debug.hpp"
#include "utilities/formatBuffer.hpp"
#include "utilities/globalDefinitions.hpp"
+#include "virtualspace.hpp"
using metaspace::ChunkManager;
using metaspace::CommitLimiter;
@@ -581,86 +582,71 @@ bool Metaspace::class_space_is_initialized() {
return MetaspaceContext::context_class() != nullptr;
}
-// Reserve a range of memory at an address suitable for en/decoding narrow
-// Klass pointers (see: CompressedClassPointers::is_valid_base()).
-// The returned address shall both be suitable as a compressed class pointers
-// base, and aligned to Metaspace::reserve_alignment (which is equal to or a
-// multiple of allocation granularity).
-// On error, returns an unreserved space.
-ReservedSpace Metaspace::reserve_address_space_for_compressed_classes(size_t size) {
+// Reserve a range of memory that is to contain narrow Klass IDs. If "try_in_low_address_ranges"
+// is true, we will attempt to reserve memory suitable for zero-based encoding.
+ReservedSpace Metaspace::reserve_address_space_for_compressed_classes(size_t size, bool try_in_low_address_ranges) {
-#if defined(AARCH64) || defined(PPC64)
- const size_t alignment = Metaspace::reserve_alignment();
+ char* result = nullptr;
+ const bool randomize = RandomizeClassSpaceLocation;
- // AArch64: Try to align metaspace class space so that we can decode a
- // compressed klass with a single MOVK instruction. We can do this iff the
- // compressed class base is a multiple of 4G.
- // Additionally, above 32G, ensure the lower LogKlassAlignmentInBytes bits
- // of the upper 32-bits of the address are zero so we can handle a shift
- // when decoding.
-
- // PPC64: smaller heaps up to 2g will be mapped just below 4g. Then the
- // attempt to place the compressed class space just after the heap fails on
- // Linux 4.1.42 and higher because the launcher is loaded at 4g
- // (ELF_ET_DYN_BASE). In that case we reach here and search the address space
- // below 32g to get a zerobased CCS. For simplicity we reuse the search
- // strategy for AARCH64.
-
- static const struct {
- address from;
- address to;
- size_t increment;
- } search_ranges[] = {
- { (address)(4*G), (address)(32*G), 4*G, },
- { (address)(32*G), (address)(1024*G), (4 << LogKlassAlignmentInBytes) * G },
- { nullptr, nullptr, 0 }
- };
-
- // Calculate a list of all possible values for the starting address for the
- // compressed class space.
- ResourceMark rm;
- GrowableArray
list(36);
- for (int i = 0; search_ranges[i].from != nullptr; i ++) {
- address a = search_ranges[i].from;
- assert(CompressedKlassPointers::is_valid_base(a), "Sanity");
- while (a < search_ranges[i].to) {
- list.append(a);
- a += search_ranges[i].increment;
+ // First try to reserve in low address ranges.
+ if (try_in_low_address_ranges) {
+ constexpr uintptr_t unscaled_max = ((uintptr_t)UINT_MAX + 1);
+ log_debug(metaspace, map)("Trying below " SIZE_FORMAT_X " for unscaled narrow Klass encoding", unscaled_max);
+ result = os::attempt_reserve_memory_between(nullptr, (char*)unscaled_max,
+ size, Metaspace::reserve_alignment(), randomize);
+ if (result == nullptr) {
+ constexpr uintptr_t zerobased_max = unscaled_max << LogKlassAlignmentInBytes;
+ log_debug(metaspace, map)("Trying below " SIZE_FORMAT_X " for zero-based narrow Klass encoding", zerobased_max);
+ result = os::attempt_reserve_memory_between((char*)unscaled_max, (char*)zerobased_max,
+ size, Metaspace::reserve_alignment(), randomize);
}
+ } // end: low-address reservation
+
+#if defined(AARCH64) || defined(PPC64) || defined(S390)
+ if (result == nullptr) {
+ // Failing zero-based allocation, or in strict_base mode, try to come up with
+ // an optimized start address that is amenable to JITs that use 16-bit moves to
+ // load the encoding base as a short immediate.
+ // Therefore we try here for an address that when right-shifted by
+ // LogKlassAlignmentInBytes has only 1s in the third 16-bit quadrant.
+ //
+ // Example: for shift=3, the address space searched would be
+ // [0x0080_0000_0000 - 0xFFF8_0000_0000].
+
+ // Number of least significant bits that should be zero
+ constexpr int lo_zero_bits = 32 + LogKlassAlignmentInBytes;
+ // Number of most significant bits that should be zero
+ constexpr int hi_zero_bits = 16;
+
+ constexpr size_t alignment = nth_bit(lo_zero_bits);
+ assert(alignment >= Metaspace::reserve_alignment(), "Sanity");
+ constexpr uint64_t min = alignment;
+ constexpr uint64_t max = nth_bit(64 - hi_zero_bits);
+
+ log_debug(metaspace, map)("Trying between " UINT64_FORMAT_X " and " UINT64_FORMAT_X
+ " with " SIZE_FORMAT_X " alignment", min, max, alignment);
+ result = os::attempt_reserve_memory_between((char*)min, (char*)max, size, alignment, randomize);
+ }
+#endif // defined(AARCH64) || defined(PPC64) || defined(S390)
+
+ if (result == nullptr) {
+ // Fallback: reserve anywhere and hope the resulting block is usable.
+ log_debug(metaspace, map)("Trying anywhere...");
+ result = os::reserve_memory_aligned(size, Metaspace::reserve_alignment(), false);
}
- int len = list.length();
- int r = 0;
- if (!DumpSharedSpaces) {
- // Starting from a random position in the list. If the address cannot be reserved
- // (the OS already assigned it for something else), go to the next position, wrapping
- // around if necessary, until we exhaust all the items.
- os::init_random((int)os::javaTimeNanos());
- r = os::random();
- log_info(metaspace)("Randomizing compressed class space: start from %d out of %d locations",
- r % len, len);
+ // Wrap resulting range in ReservedSpace
+ ReservedSpace rs;
+ if (result != nullptr) {
+ assert(is_aligned(result, Metaspace::reserve_alignment()), "Alignment too small for metaspace");
+ rs = ReservedSpace::space_for_range(result, size, Metaspace::reserve_alignment(),
+ os::vm_page_size(), false, false);
+ } else {
+ rs = ReservedSpace();
}
- for (int i = 0; i < len; i++) {
- address a = list.at((i + r) % len);
- ReservedSpace rs(size, Metaspace::reserve_alignment(),
- os::vm_page_size(), (char*)a);
- if (rs.is_reserved()) {
- assert(a == (address)rs.base(), "Sanity");
- return rs;
- }
- }
-#endif // defined(AARCH64) || defined(PPC64)
-
-#ifdef AARCH64
- // Note: on AARCH64, if the code above does not find any good placement, we
- // have no recourse. We return an empty space and the VM will exit.
- return ReservedSpace();
-#else
- // Default implementation: Just reserve anywhere.
- return ReservedSpace(size, Metaspace::reserve_alignment(), os::vm_page_size(), (char*)nullptr);
-#endif // AARCH64
+ return rs;
}
-
#endif // _LP64
size_t Metaspace::reserve_alignment_words() {
@@ -781,14 +767,13 @@ void Metaspace::global_initialize() {
// case (b) (No CDS)
ReservedSpace rs;
const size_t size = align_up(CompressedClassSpaceSize, Metaspace::reserve_alignment());
- address base = nullptr;
// If CompressedClassSpaceBaseAddress is set, we attempt to force-map class space to
// the given address. This is a debug-only feature aiding tests. Due to the ASLR lottery
// this may fail, in which case the VM will exit after printing an appropriate message.
// Tests using this switch should cope with that.
if (CompressedClassSpaceBaseAddress != 0) {
- base = (address)CompressedClassSpaceBaseAddress;
+ const address base = (address)CompressedClassSpaceBaseAddress;
if (!is_aligned(base, Metaspace::reserve_alignment())) {
vm_exit_during_initialization(
err_msg("CompressedClassSpaceBaseAddress=" PTR_FORMAT " invalid "
@@ -806,27 +791,9 @@ void Metaspace::global_initialize() {
}
}
- if (!rs.is_reserved()) {
- // If UseCompressedOops=1 and the java heap has been placed in coops-friendly
- // territory, i.e. its base is under 32G, then we attempt to place ccs
- // right above the java heap.
- // Otherwise the lower 32G are still free. We try to place ccs at the lowest
- // allowed mapping address.
- base = (UseCompressedOops && (uint64_t)CompressedOops::base() < OopEncodingHeapMax) ?
- CompressedOops::end() : (address)HeapBaseMinAddress;
- base = align_up(base, Metaspace::reserve_alignment());
-
- if (base != nullptr) {
- if (CompressedKlassPointers::is_valid_base(base)) {
- rs = ReservedSpace(size, Metaspace::reserve_alignment(),
- os::vm_page_size(), (char*)base);
- }
- }
- }
-
// ...failing that, reserve anywhere, but let platform do optimized placement:
if (!rs.is_reserved()) {
- rs = Metaspace::reserve_address_space_for_compressed_classes(size);
+ rs = Metaspace::reserve_address_space_for_compressed_classes(size, true);
}
// ...failing that, give up.
diff --git a/src/hotspot/share/memory/metaspace.hpp b/src/hotspot/share/memory/metaspace.hpp
index 1efdf980ac8..652a2be35d9 100644
--- a/src/hotspot/share/memory/metaspace.hpp
+++ b/src/hotspot/share/memory/metaspace.hpp
@@ -74,13 +74,9 @@ public:
#ifdef _LP64
- // Reserve a range of memory at an address suitable for en/decoding narrow
- // Klass pointers (see: CompressedClassPointers::is_valid_base()).
- // The returned address shall both be suitable as a compressed class pointers
- // base, and aligned to Metaspace::reserve_alignment (which is equal to or a
- // multiple of allocation granularity).
- // On error, returns an unreserved space.
- static ReservedSpace reserve_address_space_for_compressed_classes(size_t size);
+ // Reserve a range of memory that is to contain narrow Klass IDs. If "try_in_low_address_ranges"
+ // is true, we will attempt to reserve memory suitable for zero-based encoding.
+ static ReservedSpace reserve_address_space_for_compressed_classes(size_t size, bool try_in_low_address_ranges);
// Given a prereserved space, use that to set up the compressed class space list.
static void initialize_class_space(ReservedSpace rs);
diff --git a/src/hotspot/share/memory/virtualspace.cpp b/src/hotspot/share/memory/virtualspace.cpp
index dbbf5a3dd5f..1a99b967436 100644
--- a/src/hotspot/share/memory/virtualspace.cpp
+++ b/src/hotspot/share/memory/virtualspace.cpp
@@ -355,6 +355,17 @@ void ReservedSpace::release() {
}
}
+// Put a ReservedSpace over an existing range
+ReservedSpace ReservedSpace::space_for_range(char* base, size_t size, size_t alignment,
+ size_t page_size, bool special, bool executable) {
+ assert(is_aligned(base, os::vm_allocation_granularity()), "Unaligned base");
+ assert(is_aligned(size, os::vm_page_size()), "Unaligned size");
+ assert(os::page_sizes().contains(page_size), "Invalid pagesize");
+ ReservedSpace space;
+ space.initialize_members(base, size, alignment, page_size, special, executable);
+ return space;
+}
+
static size_t noaccess_prefix_size(size_t alignment) {
return lcm(os::vm_page_size(), alignment);
}
@@ -546,17 +557,7 @@ void ReservedHeapSpace::initialize_compressed_heap(const size_t size, size_t ali
}
// zerobased: Attempt to allocate in the lower 32G.
- // But leave room for the compressed class pointers, which is allocated above
- // the heap.
char *zerobased_max = (char *)OopEncodingHeapMax;
- const size_t class_space = align_up(CompressedClassSpaceSize, alignment);
- // For small heaps, save some space for compressed class pointer
- // space so it can be decoded with no base.
- if (UseCompressedClassPointers && !UseSharedSpaces && !DumpSharedSpaces &&
- OopEncodingHeapMax <= KlassEncodingMetaspaceMax &&
- (uint64_t)(aligned_heap_base_min_address + size + class_space) <= KlassEncodingMetaspaceMax) {
- zerobased_max = (char *)OopEncodingHeapMax - class_space;
- }
// Give it several tries from top of range to bottom.
if (aligned_heap_base_min_address + size <= zerobased_max && // Zerobased theoretical possible.
diff --git a/src/hotspot/share/memory/virtualspace.hpp b/src/hotspot/share/memory/virtualspace.hpp
index 65650db4f69..264ed0d9749 100644
--- a/src/hotspot/share/memory/virtualspace.hpp
+++ b/src/hotspot/share/memory/virtualspace.hpp
@@ -107,6 +107,10 @@ class ReservedSpace {
bool contains(const void* p) const {
return (base() <= ((char*)p)) && (((char*)p) < (base() + size()));
}
+
+ // Put a ReservedSpace over an existing range
+ static ReservedSpace space_for_range(char* base, size_t size, size_t alignment,
+ size_t page_size, bool special, bool executable);
};
ReservedSpace
diff --git a/src/hotspot/share/oops/compressedKlass.cpp b/src/hotspot/share/oops/compressedKlass.cpp
index 3821e967af9..f9750c1b7c0 100644
--- a/src/hotspot/share/oops/compressedKlass.cpp
+++ b/src/hotspot/share/oops/compressedKlass.cpp
@@ -62,7 +62,6 @@ void CompressedKlassPointers::initialize_for_given_encoding(address addr, size_t
// will encounter (and the implicit promise that there will be no Klass
// structures outside this range).
void CompressedKlassPointers::initialize(address addr, size_t len) {
- assert(is_valid_base(addr), "Address must be a valid encoding base");
address const end = addr + len;
address base;
@@ -90,6 +89,8 @@ void CompressedKlassPointers::initialize(address addr, size_t len) {
set_base(base);
set_shift(shift);
set_range(range);
+
+ assert(is_valid_base(_base), "Address must be a valid encoding base");
}
// Given an address p, return true if p can be used as an encoding base.
diff --git a/src/hotspot/share/runtime/globals.hpp b/src/hotspot/share/runtime/globals.hpp
index b61ff2bdaaa..52a4af0aee8 100644
--- a/src/hotspot/share/runtime/globals.hpp
+++ b/src/hotspot/share/runtime/globals.hpp
@@ -1419,6 +1419,9 @@ const int ObjectAlignmentInBytes = 8;
"Force the class space to be allocated at this address or " \
"fails VM initialization (requires -Xshare=off.") \
\
+ develop(bool, RandomizeClassSpaceLocation, true, \
+ "Randomize location of class space.") \
+ \
product(bool, PrintMetaspaceStatisticsAtExit, false, DIAGNOSTIC, \
"Print metaspace statistics upon VM exit.") \
\
diff --git a/src/hotspot/share/runtime/os.cpp b/src/hotspot/share/runtime/os.cpp
index c698a48ffb2..30573591a54 100644
--- a/src/hotspot/share/runtime/os.cpp
+++ b/src/hotspot/share/runtime/os.cpp
@@ -74,6 +74,7 @@
#include "utilities/count_trailing_zeros.hpp"
#include "utilities/defaultStream.hpp"
#include "utilities/events.hpp"
+#include "utilities/fastrand.hpp"
#include "utilities/powerOfTwo.hpp"
#ifndef _WINDOWS
@@ -1811,13 +1812,206 @@ char* os::attempt_reserve_memory_at(char* addr, size_t bytes, bool executable) {
char* result = pd_attempt_reserve_memory_at(addr, bytes, executable);
if (result != nullptr) {
MemTracker::record_virtual_memory_reserve((address)result, bytes, CALLER_PC);
+ log_debug(os)("Reserved memory at " INTPTR_FORMAT " for " SIZE_FORMAT " bytes.", p2i(addr), bytes);
} else {
log_debug(os)("Attempt to reserve memory at " INTPTR_FORMAT " for "
SIZE_FORMAT " bytes failed, errno %d", p2i(addr), bytes, get_last_error());
}
+
return result;
}
+#ifdef ASSERT
+static void print_points(const char* s, unsigned* points, unsigned num) {
+ stringStream ss;
+ for (unsigned i = 0; i < num; i ++) {
+ ss.print("%u ", points[i]);
+ }
+ log_trace(os, map)("%s, %u Points: %s", s, num, ss.base());
+}
+#endif
+
+// Helper for os::attempt_reserve_memory_between
+// Given an array of things, shuffle them (Fisher-Yates)
+template
+static void shuffle_fisher_yates(T* arr, unsigned num, FastRandom& frand) {
+ for (unsigned i = num - 1; i >= 1; i--) {
+ unsigned j = frand.next() % i;
+ swap(arr[i], arr[j]);
+ }
+}
+
+// Helper for os::attempt_reserve_memory_between
+// Given an array of things, do a hemisphere split such that the resulting
+// order is: [first, last, first + 1, last - 1, ...]
+template
+static void hemi_split(T* arr, unsigned num) {
+ T* tmp = (T*)::alloca(sizeof(T) * num);
+ for (unsigned i = 0; i < num; i++) {
+ tmp[i] = arr[i];
+ }
+ for (unsigned i = 0; i < num; i++) {
+ arr[i] = is_even(i) ? tmp[i / 2] : tmp[num - (i / 2) - 1];
+ }
+}
+
+// Given an address range [min, max), attempts to reserve memory within this area, with the given alignment.
+// If randomize is true, the location will be randomized.
+char* os::attempt_reserve_memory_between(char* min, char* max, size_t bytes, size_t alignment, bool randomize) {
+
+ // Please keep the following constants in sync with the companion gtests:
+
+ // Number of mmap attemts we will undertake.
+ constexpr unsigned max_attempts = 32;
+
+ // In randomization mode: We require a minimum number of possible attach points for
+ // randomness. Below that we refuse to reserve anything.
+ constexpr unsigned min_random_value_range = 16;
+
+ // In randomization mode: If the possible value range is below this threshold, we
+ // use a total shuffle without regard for address space fragmentation, otherwise
+ // we attempt to minimize fragmentation.
+ constexpr unsigned total_shuffle_threshold = 1024;
+
+#define ARGSFMT " range [" PTR_FORMAT "-" PTR_FORMAT "), size " SIZE_FORMAT_X ", alignment " SIZE_FORMAT_X ", randomize: %d"
+#define ARGSFMTARGS p2i(min), p2i(max), bytes, alignment, randomize
+
+ log_trace(os, map) ("reserve_between (" ARGSFMT ")", ARGSFMTARGS);
+
+ assert(is_power_of_2(alignment), "alignment invalid (" ARGSFMT ")", ARGSFMTARGS);
+ assert(alignment < SIZE_MAX / 2, "alignment too large (" ARGSFMT ")", ARGSFMTARGS);
+ assert(is_aligned(bytes, os::vm_page_size()), "size not page aligned (" ARGSFMT ")", ARGSFMTARGS);
+ assert(max >= min, "invalid range (" ARGSFMT ")", ARGSFMTARGS);
+
+ char* const absolute_max = (char*)(NOT_LP64(G * 3) LP64_ONLY(G * 128 * 1024));
+ char* const absolute_min = (char*) os::vm_min_address();
+
+ const size_t alignment_adjusted = MAX2(alignment, os::vm_allocation_granularity());
+
+ // Calculate first and last possible attach points:
+ char* const lo_att = align_up(MAX2(absolute_min, min), alignment_adjusted);
+ if (lo_att == nullptr) {
+ return nullptr; // overflow
+ }
+
+ char* const hi_att = align_down(MIN2(max, absolute_max) - bytes, alignment_adjusted);
+ if (hi_att > max) {
+ return nullptr; // overflow
+ }
+
+ // no possible attach points
+ if (hi_att < lo_att) {
+ return nullptr;
+ }
+
+ char* result = nullptr;
+
+ const size_t num_attach_points = (size_t)((hi_att - lo_att) / alignment_adjusted) + 1;
+ assert(num_attach_points > 0, "Sanity");
+
+ // If this fires, the input range is too large for the given alignment (we work
+ // with int below to keep things simple). Since alignment is bound to page size,
+ // and the lowest page size is 4K, this gives us a minimum of 4K*4G=8TB address
+ // range.
+ assert(num_attach_points <= UINT_MAX,
+ "Too many possible attach points - range too large or alignment too small (" ARGSFMT ")", ARGSFMTARGS);
+
+ const unsigned num_attempts = MIN2((unsigned)num_attach_points, max_attempts);
+ unsigned points[max_attempts];
+
+ if (randomize) {
+ FastRandom frand;
+
+ if (num_attach_points < min_random_value_range) {
+ return nullptr;
+ }
+
+ // We pre-calc the attach points:
+ // 1 We divide the attach range into equidistant sections and calculate an attach
+ // point within each section.
+ // 2 We wiggle those attach points around within their section (depends on attach
+ // point granularity)
+ // 3 Should that not be enough to get effective randomization, shuffle all
+ // attach points
+ // 4 Otherwise, re-order them to get an optimized probing sequence.
+ const unsigned stepsize = (unsigned)num_attach_points / num_attempts;
+ const unsigned half = num_attempts / 2;
+
+ // 1+2: pre-calc points
+ for (unsigned i = 0; i < num_attempts; i++) {
+ const unsigned deviation = stepsize > 1 ? (frand.next() % stepsize) : 0;
+ points[i] = (i * stepsize) + deviation;
+ }
+
+ if (num_attach_points < total_shuffle_threshold) {
+ // 3:
+ // The numeber of possible attach points is too low for the "wiggle" from
+ // point 2 to be enough to provide randomization. In that case, shuffle
+ // all attach points at the cost of possible fragmentation (e.g. if we
+ // end up mapping into the middle of the range).
+ shuffle_fisher_yates(points, num_attempts, frand);
+ } else {
+ // 4
+ // We have a large enough number of attach points to satisfy the randomness
+ // goal without. In that case, we optimize probing by sorting the attach
+ // points: We attempt outermost points first, then work ourselves up to
+ // the middle. That reduces address space fragmentation. We also alternate
+ // hemispheres, which increases the chance of successfull mappings if the
+ // previous mapping had been blocked by large maps.
+ hemi_split(points, num_attempts);
+ }
+ } // end: randomized
+ else
+ {
+ // Non-randomized. We just attempt to reserve by probing sequentially. We
+ // alternate between hemispheres, working ourselves up to the middle.
+ const int stepsize = (unsigned)num_attach_points / num_attempts;
+ for (unsigned i = 0; i < num_attempts; i++) {
+ points[i] = (i * stepsize);
+ }
+ hemi_split(points, num_attempts);
+ }
+
+#ifdef ASSERT
+ // Print + check all pre-calculated attach points
+ print_points("before reserve", points, num_attempts);
+ for (unsigned i = 0; i < num_attempts; i++) {
+ assert(points[i] < num_attach_points, "Candidate attach point %d out of range (%u, num_attach_points: %zu) " ARGSFMT,
+ i, points[i], num_attach_points, ARGSFMTARGS);
+ }
+#endif
+
+ // Now reserve
+ for (unsigned i = 0; result == nullptr && i < num_attempts; i++) {
+ const unsigned candidate_offset = points[i];
+ char* const candidate = lo_att + candidate_offset * alignment_adjusted;
+ assert(candidate <= hi_att, "Invalid offset %u (" ARGSFMT ")", candidate_offset, ARGSFMTARGS);
+ result = os::pd_attempt_reserve_memory_at(candidate, bytes, false);
+ if (!result) {
+ log_trace(os, map)("Failed to attach at " PTR_FORMAT, p2i(candidate));
+ }
+ }
+
+ // Sanity checks, logging, NMT stuff:
+ if (result != nullptr) {
+#define ERRFMT "result: " PTR_FORMAT " " ARGSFMT
+#define ERRFMTARGS p2i(result), ARGSFMTARGS
+ assert(result >= min, "OOB min (" ERRFMT ")", ERRFMTARGS);
+ assert((result + bytes) <= max, "OOB max (" ERRFMT ")", ERRFMTARGS);
+ assert(result >= (char*)os::vm_min_address(), "OOB vm.map min (" ERRFMT ")", ERRFMTARGS);
+ assert((result + bytes) <= absolute_max, "OOB vm.map max (" ERRFMT ")", ERRFMTARGS);
+ assert(is_aligned(result, alignment), "alignment invalid (" ERRFMT ")", ERRFMTARGS);
+ log_trace(os, map)(ERRFMT, ERRFMTARGS);
+ log_debug(os, map)("successfully attached at " PTR_FORMAT, p2i(result));
+ MemTracker::record_virtual_memory_reserve((address)result, bytes, CALLER_PC);
+ }
+ return result;
+#undef ARGSFMT
+#undef ERRFMT
+#undef ARGSFMTARGS
+#undef ERRFMTARGS
+}
+
static void assert_nonempty_range(const char* addr, size_t bytes) {
assert(addr != nullptr && bytes > 0, "invalid range [" PTR_FORMAT ", " PTR_FORMAT ")",
p2i(addr), p2i(addr) + bytes);
diff --git a/src/hotspot/share/runtime/os.hpp b/src/hotspot/share/runtime/os.hpp
index e13ce72e886..7069a4705a9 100644
--- a/src/hotspot/share/runtime/os.hpp
+++ b/src/hotspot/share/runtime/os.hpp
@@ -190,6 +190,11 @@ class os: AllStatic {
static OSThread* _starting_thread;
static PageSizes _page_sizes;
+ // The default value for os::vm_min_address() unless the platform knows better. This value
+ // is chosen to give us reasonable protection against NULL pointer dereferences while being
+ // low enough to leave most of the valuable low-4gb address space open.
+ static constexpr size_t _vm_min_address_default = 16 * M;
+
static char* pd_reserve_memory(size_t bytes, bool executable);
static char* pd_attempt_reserve_memory_at(char* addr, size_t bytes, bool executable);
@@ -420,6 +425,9 @@ class os: AllStatic {
static size_t vm_allocation_granularity() { return OSInfo::vm_allocation_granularity(); }
+ // Returns the lowest address the process is allowed to map against.
+ static size_t vm_min_address();
+
inline static size_t cds_core_region_alignment();
// Reserves virtual memory.
@@ -432,6 +440,10 @@ class os: AllStatic {
// Does not overwrite existing mappings.
static char* attempt_reserve_memory_at(char* addr, size_t bytes, bool executable = false);
+ // Given an address range [min, max), attempts to reserve memory within this area, with the given alignment.
+ // If randomize is true, the location will be randomized.
+ static char* attempt_reserve_memory_between(char* min, char* max, size_t bytes, size_t alignment, bool randomize);
+
static bool commit_memory(char* addr, size_t bytes, bool executable);
static bool commit_memory(char* addr, size_t size, size_t alignment_hint,
bool executable);
diff --git a/src/hotspot/share/utilities/fastrand.hpp b/src/hotspot/share/utilities/fastrand.hpp
new file mode 100644
index 00000000000..30626963fef
--- /dev/null
+++ b/src/hotspot/share/utilities/fastrand.hpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, Red Hat, Inc. 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.
+ *
+ */
+
+#ifndef SHARE_UTILITIES_FASTRAND_HPP
+#define SHARE_UTILITIES_FASTRAND_HPP
+
+#include "runtime/os.hpp"
+#include "utilities/globalDefinitions.hpp"
+
+// Simple utility class to generate random numbers for use in a single-threaded
+// context. Since os::random() needs to update the global seed, this is faster
+// when used on within a single thread.
+// Seed initialization happens, similar to os::init_random(), via os::javaTimeNanos());
+
+class FastRandom {
+ unsigned _seed;
+ public:
+ FastRandom () : _seed((unsigned) os::javaTimeNanos()) {}
+ unsigned next() {
+ _seed = os::next_random(_seed);
+ return _seed;
+ }
+};
+
+#endif // SHARE_UTILITIES_FASTRAND_HPP
diff --git a/test/hotspot/gtest/runtime/test_os.cpp b/test/hotspot/gtest/runtime/test_os.cpp
index bcd532d1bcf..9956956423c 100644
--- a/test/hotspot/gtest/runtime/test_os.cpp
+++ b/test/hotspot/gtest/runtime/test_os.cpp
@@ -947,3 +947,14 @@ TEST_VM(os, reserve_at_wish_address_shall_not_replace_mappings_largepages) {
tty->print_cr("Skipped.");
}
}
+
+TEST_VM(os, vm_min_address) {
+ size_t s = os::vm_min_address();
+ ASSERT_GE(s, M);
+ // Test upper limit. On Linux, its adjustable, so we just test for absurd values to prevent errors
+ // with high vm.mmap_min_addr settings.
+#if defined(_LP64)
+ ASSERT_LE(s, NOT_LINUX(G * 4) LINUX_ONLY(G * 1024));
+#endif
+}
+
diff --git a/test/hotspot/gtest/runtime/test_os_reserve_between.cpp b/test/hotspot/gtest/runtime/test_os_reserve_between.cpp
new file mode 100644
index 00000000000..a6e1ed1f239
--- /dev/null
+++ b/test/hotspot/gtest/runtime/test_os_reserve_between.cpp
@@ -0,0 +1,346 @@
+/*
+ * Copyright (c) 2023, Red Hat, Inc. All rights reserved.
+ * Copyright (c) 2023, 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 "precompiled.hpp"
+#include "logging/log.hpp"
+#include "memory/resourceArea.hpp"
+#include "runtime/os.hpp"
+#include "utilities/align.hpp"
+#include "utilities/globalDefinitions.hpp"
+#include "utilities/macros.hpp"
+#include "utilities/resourceHash.hpp"
+
+#define LOG_PLEASE
+#include "testutils.hpp"
+#include "unittest.hpp"
+
+// On AIX, these tests make no sense as long as JDK-8315321 remains unfixed since the attach
+// addresses are not predictable.
+#ifndef AIX
+
+// Must be the same as in os::attempt_reserve_memory_between()
+struct ARMB_constants {
+ static constexpr uintptr_t absolute_max = NOT_LP64(G * 3) LP64_ONLY(G * 128 * 1024);
+ static constexpr unsigned max_attempts = 32;
+ static constexpr unsigned min_random_value_range = 16;
+ static constexpr unsigned total_shuffle_threshold = 1024;
+};
+
+// Testing os::attempt_reserve_memory_between()
+
+static void release_if_needed(char* p, size_t s) {
+ if (p != nullptr) {
+ os::release_memory(p, s);
+ }
+}
+
+#define ERRINFO "addr: " << ((void*)addr) << " min: " << ((void*)min) << " max: " << ((void*)max) \
+ << " bytes: " << bytes << " alignment: " << alignment << " randomized: " << randomized
+
+static char* call_attempt_reserve_memory_between(char* min, char* max, size_t bytes, size_t alignment, bool randomized) {
+ char* const addr = os::attempt_reserve_memory_between(min, max, bytes, alignment, randomized);
+ if (addr != nullptr) {
+ EXPECT_TRUE(is_aligned(addr, alignment)) << ERRINFO;
+ EXPECT_TRUE(is_aligned(addr, os::vm_allocation_granularity())) << ERRINFO;
+ EXPECT_LE(addr, max - bytes) << ERRINFO;
+ EXPECT_LE(addr, (char*)ARMB_constants::absolute_max - bytes) << ERRINFO;
+ EXPECT_GE(addr, min) << ERRINFO;
+ EXPECT_GE(addr, (char*)os::vm_min_address()) << ERRINFO;
+ }
+ return addr;
+}
+
+class Expect {
+ const bool _expect_success;
+ const bool _expect_failure;
+ const char* const _expected_result; // if _expect_success
+public:
+ Expect(bool expect_success, bool expect_failure, char* expected_result)
+ : _expect_success(expect_success), _expect_failure(expect_failure), _expected_result(expected_result)
+ {
+ assert(!expect_success || !expect_failure, "make up your mind");
+ }
+ bool check_reality(char* result) const {
+ if (_expect_failure) {
+ return result == nullptr;
+ }
+ if (_expect_success) {
+ return (_expected_result == nullptr) ? result != nullptr : result == _expected_result;
+ }
+ return true;
+ }
+ static Expect failure() { return Expect(false, true, nullptr); }
+ static Expect success_any() { return Expect(true, false, nullptr); }
+ static Expect success(char* addr) { return Expect(true, false, addr); }
+ static Expect dontcare() { return Expect(false, false, nullptr); }
+};
+
+static void test_attempt_reserve_memory_between(char* min, char* max, size_t bytes, size_t alignment, bool randomized,
+ Expect expectation, int line = -1) {
+ char* const addr = call_attempt_reserve_memory_between(min, max, bytes, alignment, randomized);
+ EXPECT_TRUE(expectation.check_reality(addr)) << ERRINFO << " L" << line;
+ release_if_needed(addr, bytes);
+}
+#undef ERRINFO
+
+// Helper for attempt_reserve_memory_between tests to
+// reserve an area with a hole in the middle
+struct SpaceWithHole {
+ char* _base;
+ const size_t _len;
+ const size_t _hole_offset;
+ const size_t _hole_size;
+
+
+ static constexpr size_t _p1_offset = 0;
+ const size_t _p1_size;
+ const size_t _p2_offset;
+ const size_t _p2_size;
+
+ char* _p1;
+ char* _p2;
+
+ size_t p1size() const { return hole_offset(); }
+ size_t p2size() const { return _len - hole_size() - hole_offset(); }
+
+public:
+
+ char* base() const { return _base; }
+ char* end() const { return _base + _len; }
+ char* hole() const { return _base + hole_offset(); }
+ char* hole_end() const { return hole() + hole_size(); }
+
+ size_t hole_size() const { return _hole_size; }
+ size_t hole_offset() const { return _hole_offset; }
+
+ SpaceWithHole(size_t total_size, size_t hole_offset, size_t hole_size) :
+ _base(nullptr), _len(total_size), _hole_offset(hole_offset), _hole_size(hole_size),
+ _p1_size(hole_offset), _p2_offset(hole_offset + hole_size), _p2_size(total_size - hole_offset - hole_size),
+ _p1(nullptr), _p2(nullptr)
+ {
+ assert(_p1_size > 0 && _p2_size > 0, "Cannot have holes at the border");
+ }
+
+ bool reserve() {
+ // We cannot create a hole by punching, since NMT cannot cope with releases
+ // crossing reservation boundaries. Therefore we first reserve the total,
+ // release it again, reserve the parts.
+ for (int i = 56; _base == nullptr && i > 32; i--) {
+ // We reserve at weird outlier addresses, in order to minimize the chance of concurrent mmaps grabbing
+ // the hole.
+ const uintptr_t candidate = nth_bit(i);
+ if ((candidate + _len) <= ARMB_constants::absolute_max) {
+ _base = os::attempt_reserve_memory_at((char*)candidate, _len);
+ }
+ }
+ if (_base == nullptr) {
+ return false;
+ }
+ // Release total mapping, remap the individual non-holy parts
+ os::release_memory(_base, _len);
+ _p1 = os::attempt_reserve_memory_at(_base + _p1_offset, _p1_size);
+ _p2 = os::attempt_reserve_memory_at(_base + _p2_offset, _p2_size);
+ if (_p1 == nullptr || _p2 == nullptr) {
+ return false;
+ }
+ LOG_HERE("SpaceWithHole: [" PTR_FORMAT " ... [" PTR_FORMAT " ... " PTR_FORMAT ") ... " PTR_FORMAT ")",
+ p2i(base()), p2i(hole()), p2i(hole_end()), p2i(end()));
+ return true;
+ }
+
+ ~SpaceWithHole() {
+ release_if_needed(_p1, _p1_size);
+ release_if_needed(_p2, _p2_size);
+ }
+};
+
+// Test that, when reserving in a range randomly, we get random results
+static void test_attempt_reserve_memory_between_random_distribution(unsigned num_possible_attach_points) {
+
+ const size_t ag = os::vm_allocation_granularity();
+
+ // Create a space that is mostly a hole bordered by two small stripes of reserved memory, with
+ // as many attach points as we need.
+ SpaceWithHole space((2 + num_possible_attach_points) * ag, ag, num_possible_attach_points * ag);
+ if (!space.reserve()) {
+ tty->print_cr("Failed to reserve holed space, skipping.");
+ return;
+ }
+
+ const size_t bytes = ag;
+ const size_t alignment = ag;
+
+ // Below this threshold the API should never return memory since the randomness is too weak.
+ const bool expect_failure = (num_possible_attach_points < ARMB_constants::min_random_value_range);
+
+ // Below this threshold we expect values to be completely random, otherwise they randomized but still ordered.
+ const bool total_shuffled = (num_possible_attach_points < ARMB_constants::total_shuffle_threshold);
+
+ // Allocate n times within that hole (with subsequent deletions) and remember unique addresses returned.
+ constexpr unsigned num_tries_per_attach_point = 100;
+ ResourceMark rm;
+ ResourceHashtable ht;
+ const unsigned num_tries = expect_failure ? 3 : (num_possible_attach_points * num_tries_per_attach_point);
+ unsigned num_uniq = 0; // Number of uniq addresses returned
+
+ // In "total shuffle" mode, all possible attach points are randomized; outside that mode, the API
+ // attempts to limit fragmentation by favouring the ends of the ranges.
+ const unsigned expected_variance =
+ total_shuffled ? num_possible_attach_points : (num_possible_attach_points / ARMB_constants::max_attempts);
+
+ // Its not easy to find a good threshold for automated tests to test randomness
+ // that rules out intermittent errors. We apply a generous fudge factor.
+ constexpr double fudge_factor = 0.25f;
+ const unsigned expected_variance_with_fudge = MAX2(2u, (unsigned)((double)expected_variance * fudge_factor));
+
+#define ERRINFO " num_possible_attach_points: " << num_possible_attach_points << " total_shuffle? " << total_shuffled \
+ << " expected variance: " << expected_variance << " with fudge: " << expected_variance_with_fudge \
+ << " alignment: " << alignment << " bytes: " << bytes;
+
+ for (unsigned i = 0; i < num_tries &&
+ num_uniq < expected_variance_with_fudge; // Stop early if we confirmed enough variance.
+ i ++) {
+ char* p = call_attempt_reserve_memory_between(space.base(), space.end(), bytes, alignment, true);
+ if (p != nullptr) {
+ ASSERT_GE(p, space.hole()) << ERRINFO;
+ ASSERT_LE(p + bytes, space.hole_end()) << ERRINFO;
+ release_if_needed(p, bytes);
+ bool created = false;
+ unsigned* num = ht.put_if_absent(p, 0, &created);
+ (*num) ++;
+ num_uniq = (unsigned)ht.number_of_entries();
+ }
+ }
+
+ ASSERT_LE(num_uniq, num_possible_attach_points) << num_uniq << ERRINFO;
+
+ if (!expect_failure) {
+ ASSERT_GE(num_uniq, expected_variance_with_fudge) << ERRINFO;
+ }
+#undef ERRINFO
+}
+
+#define RANDOMIZED_RANGE_TEST(num) \
+ TEST_VM(os, attempt_reserve_memory_between_random_distribution_ ## num ## _attach_points) { \
+ test_attempt_reserve_memory_between_random_distribution(num); \
+}
+
+RANDOMIZED_RANGE_TEST(2)
+RANDOMIZED_RANGE_TEST(15)
+RANDOMIZED_RANGE_TEST(16)
+RANDOMIZED_RANGE_TEST(712)
+RANDOMIZED_RANGE_TEST(12000)
+
+// Test that, given a smallish range - not many attach points - with a hole, we attach within that hole.
+TEST_VM(os, attempt_reserve_memory_randomization_threshold) {
+
+ constexpr int threshold = ARMB_constants::min_random_value_range;
+ const size_t ps = os::vm_page_size();
+ const size_t ag = os::vm_allocation_granularity();
+
+ SpaceWithHole space(ag * (threshold + 2), ag, ag * threshold);
+ if (!space.reserve()) {
+ tty->print_cr("Failed to reserve holed space, skipping.");
+ return;
+ }
+
+ // Test with a range that only allows for (threshold - 1) reservations
+ test_attempt_reserve_memory_between(space.hole(), space.hole_end() - ag, ps, ag, true, Expect::failure());
+
+ // Test with a range just above the threshold. Should succeed.
+ test_attempt_reserve_memory_between(space.hole(), space.hole_end(), ps, ag, true, Expect::success_any());
+}
+
+// Test all possible combos
+TEST_VM(os, attempt_reserve_memory_between_combos) {
+ const size_t large_end = NOT_LP64(G) LP64_ONLY(64 * G);
+ for (size_t range_size = os::vm_allocation_granularity(); range_size <= large_end; range_size *= 2) {
+ for (size_t start_offset = 0; start_offset <= large_end; start_offset += (large_end / 2)) {
+ char* const min = (char*)(uintptr_t)start_offset;
+ char* const max = min + range_size;
+ for (size_t bytes = os::vm_page_size(); bytes < large_end; bytes *= 2) {
+ for (size_t alignment = os::vm_allocation_granularity(); alignment < large_end; alignment *= 2) {
+ test_attempt_reserve_memory_between(min, max, bytes, alignment, true, Expect::dontcare(), __LINE__);
+ test_attempt_reserve_memory_between(min, max, bytes, alignment, false, Expect::dontcare(), __LINE__);
+ }
+ }
+ }
+ }
+}
+
+TEST_VM(os, attempt_reserve_memory_randomization_cornercases) {
+ const size_t ps = os::vm_page_size();
+ const size_t ag = os::vm_allocation_granularity();
+ constexpr size_t quarter_address_space = NOT_LP64(nth_bit(30)) LP64_ONLY(nth_bit(62));
+
+ // Zero-sized range
+ test_attempt_reserve_memory_between(nullptr, nullptr, ps, ag, false, Expect::failure());
+ test_attempt_reserve_memory_between((char*)(3 * G), (char*)(3 * G), ps, ag, false, Expect::dontcare(), __LINE__);
+ test_attempt_reserve_memory_between((char*)SIZE_MAX, (char*)SIZE_MAX, ps, ag, false, Expect::failure(), __LINE__);
+
+ test_attempt_reserve_memory_between(nullptr, nullptr, ps, ag, true, Expect::failure());
+ test_attempt_reserve_memory_between((char*)(3 * G), (char*)(3 * G), ps, ag, true, Expect::dontcare(), __LINE__);
+ test_attempt_reserve_memory_between((char*)(3 * G), (char*)(3 * G), ps, ag, true, Expect::dontcare(), __LINE__);
+ test_attempt_reserve_memory_between((char*)SIZE_MAX, (char*)SIZE_MAX, ps, ag, true, Expect::failure(), __LINE__);
+
+ // Full size
+ // Note: paradoxically, success is not guaranteed here, since a significant portion of the attach points
+ // could be located in un-allocatable territory.
+ test_attempt_reserve_memory_between(nullptr, (char*)SIZE_MAX, ps, quarter_address_space / 8, false, Expect::dontcare(), __LINE__);
+ test_attempt_reserve_memory_between(nullptr, (char*)SIZE_MAX, ps, quarter_address_space / 8, true, Expect::dontcare(), __LINE__);
+
+ // Very small range at start
+ test_attempt_reserve_memory_between(nullptr, (char*)ag, ps, ag, false, Expect::dontcare(), __LINE__);
+ test_attempt_reserve_memory_between(nullptr, (char*)ag, ps, ag, true, Expect::dontcare(), __LINE__);
+
+ // Very small range at end
+ test_attempt_reserve_memory_between((char*)(SIZE_MAX - (ag * 2)), (char*)(SIZE_MAX), ps, ag, false, Expect::dontcare(), __LINE__);
+ test_attempt_reserve_memory_between((char*)(SIZE_MAX - (ag * 2)), (char*)(SIZE_MAX), ps, ag, true, Expect::dontcare(), __LINE__);
+
+ // At start, high alignment, check if we run into neg. overflow problems
+ test_attempt_reserve_memory_between(nullptr, (char*)G, ps, G, false, Expect::dontcare(), __LINE__);
+ test_attempt_reserve_memory_between(nullptr, (char*)G, ps, G, true, Expect::dontcare(), __LINE__);
+
+ // At start, very high alignment, check if we run into neg. overflow problems
+ test_attempt_reserve_memory_between((char*)quarter_address_space, (char*)SIZE_MAX, ps, quarter_address_space, false, Expect::dontcare(), __LINE__);
+ test_attempt_reserve_memory_between((char*)quarter_address_space, (char*)SIZE_MAX, ps, quarter_address_space, true, Expect::dontcare(), __LINE__);
+}
+
+// Test that, regardless where the hole is in the [min, max) range, if we probe nonrandomly, we will fill that hole
+// as long as the range size is smaller than the number of probe attempts
+TEST_VM(os, attempt_reserve_memory_between_small_range_fill_hole) {
+ const size_t ps = os::vm_page_size();
+ const size_t ag = os::vm_allocation_granularity();
+ constexpr int num = ARMB_constants::max_attempts;
+ for (int i = 0; i < num; i ++) {
+ SpaceWithHole space(ag * (num + 2), ag * (i + 1), ag);
+ if (!space.reserve()) {
+ tty->print_cr("Failed to reserve holed space, skipping.");
+ } else {
+ test_attempt_reserve_memory_between(space.base() + ag, space.end() - ag, space.hole_size(), space.hole_size(), false, Expect::success(space.hole()), __LINE__);
+ }
+ }
+}
+
+#endif // AIX
diff --git a/test/hotspot/gtest/testutils.hpp b/test/hotspot/gtest/testutils.hpp
index 6b3d42f1127..c4c093fff23 100644
--- a/test/hotspot/gtest/testutils.hpp
+++ b/test/hotspot/gtest/testutils.hpp
@@ -68,4 +68,7 @@ public:
#define LOG_HERE(s, ...)
#endif
+// handy for error analysis
+#define PING { printf("%s:%d\n", __FILE__, __LINE__); fflush(stdout); }
+
#endif // TESTUTILS_HPP