8312018: Improve reservation of class space and CDS

8313669: Reduced chance for zero-based nKlass encoding since JDK-8296565

Reviewed-by: iklam, adinn
This commit is contained in:
Thomas Stuefe 2023-08-30 17:51:28 +00:00
parent dd64a4a483
commit 89d18ea40f
17 changed files with 746 additions and 114 deletions

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -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()) {

View File

@ -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<address> 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.

View File

@ -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);

View File

@ -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.

View File

@ -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

View File

@ -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.

View File

@ -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.") \
\

View File

@ -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 <typename T>
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 <typename T>
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);

View File

@ -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);

View File

@ -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

View File

@ -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
}

View File

@ -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<char*, unsigned> 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

View File

@ -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