mirror of
https://github.com/openjdk/jdk.git
synced 2026-07-13 12:38:07 +00:00
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:
parent
dd64a4a483
commit
89d18ea40f
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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()) {
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.") \
|
||||
\
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
47
src/hotspot/share/utilities/fastrand.hpp
Normal file
47
src/hotspot/share/utilities/fastrand.hpp
Normal 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
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
346
test/hotspot/gtest/runtime/test_os_reserve_between.cpp
Normal file
346
test/hotspot/gtest/runtime/test_os_reserve_between.cpp
Normal 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
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user