jdk/test/hotspot/gtest/runtime/test_os.cpp
Robert Toyonaga 700ff1b561 Use VirtualAlloc2 to fix races
test working. need to focus on map

refactor and one more test

new test, comments, fix aix split memory accounting, use unified logging instead of warning()

remove map API code. Rename to PlacholderRegion. Fix os::release

remove map_memory API code

fix race and simplify AOTMetaspace::reserve_address_space_for_archives

clean up

comments

test: map_memory_to_file_aligned_larger

remove old test and modify tests

clean up

fix aix and comments

cleanup

gate fix

anothe rgate fix

fix windows test

account for wish address

fix missing NMT accounting

comments

small cleanup

trailing whitespaces
2026-03-16 15:46:55 -04:00

1332 lines
49 KiB
C++

/*
* Copyright (c) 2016, 2026, 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 "memory/allocation.hpp"
#include "memory/resourceArea.hpp"
#include "nmt/memTracker.hpp"
#include "runtime/frame.inline.hpp"
#include "runtime/globals.hpp"
#include "runtime/os.inline.hpp"
#include "runtime/thread.hpp"
#include "runtime/threads.hpp"
#include "testutils.hpp"
#include "utilities/align.hpp"
#include "utilities/globalDefinitions.hpp"
#include "utilities/macros.hpp"
#include "utilities/ostream.hpp"
#include "unittest.hpp"
#ifdef _WIN32
#include "os_windows.hpp"
#endif
using testing::HasSubstr;
static size_t small_page_size() {
return os::vm_page_size();
}
static size_t large_page_size() {
const size_t large_page_size_example = 4 * M;
return os::page_size_for_region_aligned(large_page_size_example, 1);
}
TEST_VM(os, page_size_for_region) {
size_t large_page_example = 4 * M;
size_t large_page = os::page_size_for_region_aligned(large_page_example, 1);
size_t small_page = os::vm_page_size();
if (large_page > small_page) {
size_t num_small_in_large = large_page / small_page;
size_t page = os::page_size_for_region_aligned(large_page, num_small_in_large);
ASSERT_EQ(page, small_page) << "Did not get a small page";
}
}
TEST_VM(os, page_size_for_region_aligned) {
if (UseLargePages) {
const size_t small_page = small_page_size();
const size_t large_page = large_page_size();
if (large_page > small_page) {
size_t num_small_pages_in_large = large_page / small_page;
size_t page = os::page_size_for_region_aligned(large_page, num_small_pages_in_large);
ASSERT_EQ(page, small_page);
}
}
}
TEST_VM(os, page_size_for_region_alignment) {
if (UseLargePages) {
const size_t small_page = small_page_size();
const size_t large_page = large_page_size();
if (large_page > small_page) {
const size_t unaligned_region = large_page + 17;
size_t page = os::page_size_for_region_aligned(unaligned_region, 1);
ASSERT_EQ(page, small_page);
const size_t num_pages = 5;
const size_t aligned_region = large_page * num_pages;
page = os::page_size_for_region_aligned(aligned_region, num_pages);
ASSERT_EQ(page, large_page);
}
}
}
TEST_VM(os, page_size_for_region_unaligned) {
if (UseLargePages) {
// Given exact page size, should return that page size.
for (size_t s = os::page_sizes().largest(); s != 0; s = os::page_sizes().next_smaller(s)) {
size_t actual = os::page_size_for_region_unaligned(s, 1);
ASSERT_EQ(s, actual);
}
// Given slightly larger size than a page size, return the page size.
for (size_t s = os::page_sizes().largest(); s != 0; s = os::page_sizes().next_smaller(s)) {
size_t actual = os::page_size_for_region_unaligned(s + 17, 1);
ASSERT_EQ(s, actual);
}
// Given a slightly smaller size than a page size,
// return the next smaller page size.
for (size_t s = os::page_sizes().largest(); s != 0; s = os::page_sizes().next_smaller(s)) {
const size_t expected = os::page_sizes().next_smaller(s);
if (expected != 0) {
size_t actual = os::page_size_for_region_unaligned(s - 17, 1);
ASSERT_EQ(actual, expected);
}
}
// Return small page size for values less than a small page.
size_t small_page = os::page_sizes().smallest();
size_t actual = os::page_size_for_region_unaligned(small_page - 17, 1);
ASSERT_EQ(small_page, actual);
}
}
TEST(os, test_random) {
const double m = 2147483647;
double mean = 0.0, variance = 0.0, t;
const int reps = 10000;
unsigned int seed = 1;
// tty->print_cr("seed %ld for %ld repeats...", seed, reps);
int num;
for (int k = 0; k < reps; k++) {
// Use next_random so the calculation is stateless.
num = seed = os::next_random(seed);
double u = (double)num / m;
ASSERT_TRUE(u >= 0.0 && u <= 1.0) << "bad random number!";
// calculate mean and variance of the random sequence
mean += u;
variance += (u*u);
}
mean /= reps;
variance /= (reps - 1);
ASSERT_EQ(num, 1043618065) << "bad seed";
// tty->print_cr("mean of the 1st 10000 numbers: %f", mean);
int intmean = (int)(mean*100);
ASSERT_EQ(intmean, 50);
// tty->print_cr("variance of the 1st 10000 numbers: %f", variance);
int intvariance = (int)(variance*100);
ASSERT_EQ(intvariance, 33);
const double eps = 0.0001;
t = fabsd(mean - 0.5018);
ASSERT_LT(t, eps) << "bad mean";
t = (variance - 0.3355) < 0.0 ? -(variance - 0.3355) : variance - 0.3355;
ASSERT_LT(t, eps) << "bad variance";
}
#ifdef ASSERT
TEST_VM_ASSERT_MSG(os, page_size_for_region_with_zero_min_pages,
"assert.min_pages > 0. failed: sanity") {
size_t region_size = 16 * os::vm_page_size();
os::page_size_for_region_aligned(region_size, 0); // should assert
}
#endif
#ifndef AIX
// Test relies on the ability to protect memory allocated with os::reserve_memory. AIX may not be able
// to do that (mprotect won't work on System V shm).
static void do_test_print_hex_dump(const_address from, const_address to, int unitsize, int bytes_per_line,
const_address logical_start, const char* expected) {
char buf[2048];
buf[0] = '\0';
stringStream ss(buf, sizeof(buf));
os::print_hex_dump(&ss, from, to, unitsize, /* print_ascii=*/true, bytes_per_line, logical_start);
EXPECT_STREQ(buf, expected);
}
// version with a highlighted pc location
static void do_test_print_hex_dump_highlighted(const_address from, const_address to, int unitsize, int bytes_per_line,
const_address logical_start, const char* expected, const_address highlight) {
char buf[2048];
buf[0] = '\0';
stringStream ss(buf, sizeof(buf));
os::print_hex_dump(&ss, from, to, unitsize, /* print_ascii=*/true, bytes_per_line, logical_start, highlight);
EXPECT_STREQ(buf, expected);
}
TEST_VM(os, test_print_hex_dump) {
#ifdef _LP64
#define ADDRESS1 "0x0000aaaaaaaaaa00"
#define ADDRESS2 "0x0000aaaaaaaaaa20"
#define ADDRESS3 "0x0000aaaaaaaaaa40"
#else
#define ADDRESS1 "0xaaaaaa00"
#define ADDRESS2 "0xaaaaaa20"
#define ADDRESS3 "0xaaaaaa40"
#endif
#define ASCII_1 "....#.jdk/internal/loader/Native"
#define ASCII_2 "Libraries......."
#define PAT_1 ADDRESS1 ": ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ??\n" \
ADDRESS2 ": ff ff e0 dc 23 00 6a 64 6b 2f 69 6e 74 65 72 6e 61 6c 2f 6c 6f 61 64 65 72 2f 4e 61 74 69 76 65 " ASCII_1 "\n" \
ADDRESS3 ": 4c 69 62 72 61 72 69 65 73 00 00 00 00 00 00 00 " ASCII_2 "\n"
#define PAT_HL_1A "=>" ADDRESS1 ": ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ??\n" \
" " ADDRESS2 ": ff ff e0 dc 23 00 6a 64 6b 2f 69 6e 74 65 72 6e 61 6c 2f 6c 6f 61 64 65 72 2f 4e 61 74 69 76 65 " ASCII_1 "\n" \
" " ADDRESS3 ": 4c 69 62 72 61 72 69 65 73 00 00 00 00 00 00 00 " ASCII_2 "\n"
#define PAT_HL_1B " " ADDRESS1 ": ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ??\n" \
"=>" ADDRESS2 ": ff ff e0 dc 23 00 6a 64 6b 2f 69 6e 74 65 72 6e 61 6c 2f 6c 6f 61 64 65 72 2f 4e 61 74 69 76 65 " ASCII_1 "\n" \
" " ADDRESS3 ": 4c 69 62 72 61 72 69 65 73 00 00 00 00 00 00 00 " ASCII_2 "\n"
#ifdef VM_LITTLE_ENDIAN
#define PAT_HL_1C " " ADDRESS1 ": ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ????\n" \
"=>" ADDRESS2 ": ffff dce0 0023 646a 2f6b 6e69 6574 6e72 6c61 6c2f 616f 6564 2f72 614e 6974 6576 " ASCII_1 "\n" \
" " ADDRESS3 ": 694c 7262 7261 6569 0073 0000 0000 0000 " ASCII_2 "\n"
#else
#define PAT_HL_1C " " ADDRESS1 ": ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ????\n" \
"=>" ADDRESS2 ": ffff e0dc 2300 6a64 6b2f 696e 7465 726e 616c 2f6c 6f61 6465 722f 4e61 7469 7665 " ASCII_1 "\n" \
" " ADDRESS3 ": 4c69 6272 6172 6965 7300 0000 0000 0000 " ASCII_2 "\n"
#endif
#ifdef VM_LITTLE_ENDIAN
#define PAT_2 ADDRESS1 ": ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ????\n" \
ADDRESS2 ": ffff dce0 0023 646a 2f6b 6e69 6574 6e72 6c61 6c2f 616f 6564 2f72 614e 6974 6576 " ASCII_1 "\n" \
ADDRESS3 ": 694c 7262 7261 6569 0073 0000 0000 0000 " ASCII_2 "\n"
#define PAT_4 ADDRESS1 ": ???????? ???????? ???????? ???????? ???????? ???????? ???????? ????????\n" \
ADDRESS2 ": dce0ffff 646a0023 6e692f6b 6e726574 6c2f6c61 6564616f 614e2f72 65766974 " ASCII_1 "\n" \
ADDRESS3 ": 7262694c 65697261 00000073 00000000 " ASCII_2 "\n"
#define PAT_8 ADDRESS1 ": ???????????????? ???????????????? ???????????????? ????????????????\n" \
ADDRESS2 ": 646a0023dce0ffff 6e7265746e692f6b 6564616f6c2f6c61 65766974614e2f72 " ASCII_1 "\n" \
ADDRESS3 ": 656972617262694c 0000000000000073 " ASCII_2 "\n"
#else
#define PAT_2 ADDRESS1 ": ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ???? ????\n" \
ADDRESS2 ": ffff e0dc 2300 6a64 6b2f 696e 7465 726e 616c 2f6c 6f61 6465 722f 4e61 7469 7665 " ASCII_1 "\n" \
ADDRESS3 ": 4c69 6272 6172 6965 7300 0000 0000 0000 " ASCII_2 "\n"
#define PAT_4 ADDRESS1 ": ???????? ???????? ???????? ???????? ???????? ???????? ???????? ????????\n" \
ADDRESS2 ": ffffe0dc 23006a64 6b2f696e 7465726e 616c2f6c 6f616465 722f4e61 74697665 " ASCII_1 "\n" \
ADDRESS3 ": 4c696272 61726965 73000000 00000000 " ASCII_2 "\n"
#define PAT_8 ADDRESS1 ": ???????????????? ???????????????? ???????????????? ????????????????\n" \
ADDRESS2 ": ffffe0dc23006a64 6b2f696e7465726e 616c2f6c6f616465 722f4e6174697665 " ASCII_1 "\n" \
ADDRESS3 ": 4c69627261726965 7300000000000000 " ASCII_2 "\n"
#endif
constexpr uint8_t bytes[] = {
0xff, 0xff, 0xe0, 0xdc, 0x23, 0x00, 0x6a, 0x64, 0x6b, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e,
0x61, 0x6c, 0x2f, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x72, 0x2f, 0x4e, 0x61, 0x74, 0x69, 0x76, 0x65,
0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
// two pages, first one protected.
const size_t ps = os::vm_page_size();
char* two_pages = os::reserve_memory(ps * 2, mtTest);
os::commit_memory(two_pages, ps * 2, false);
os::protect_memory(two_pages, ps, os::MEM_PROT_NONE, true);
memcpy(two_pages + ps, bytes, sizeof(bytes));
// print
const const_address from = (const_address) two_pages + ps - 32;
const const_address to = (const_address) from + 32 + sizeof(bytes);
const const_address logical_start = (const_address) LP64_ONLY(0xAAAAAAAAAA00ULL) NOT_LP64(0xAAAAAA00ULL);
do_test_print_hex_dump(from, to, 1, 32, logical_start, PAT_1);
do_test_print_hex_dump(from, to, 2, 32, logical_start, PAT_2);
do_test_print_hex_dump(from, to, 4, 32, logical_start, PAT_4);
do_test_print_hex_dump(from, to, 8, 32, logical_start, PAT_8);
// unaligned printing, should align to next lower unitsize
do_test_print_hex_dump(from + 1, to, 2, 32, logical_start, PAT_2);
do_test_print_hex_dump(from + 1, to, 4, 32, logical_start, PAT_4);
do_test_print_hex_dump(from + 1, to, 8, 32, logical_start, PAT_8);
// print with highlighted address
do_test_print_hex_dump_highlighted(from, to, 1, 32, logical_start, PAT_HL_1A, from+5);
do_test_print_hex_dump_highlighted(from, to, 1, 32, logical_start, PAT_HL_1B, from+32);
do_test_print_hex_dump_highlighted(from, to, 1, 32, logical_start, PAT_HL_1B, from+60);
do_test_print_hex_dump_highlighted(from, to, 2, 32, logical_start, PAT_HL_1C, from+60);
os::release_memory(two_pages, ps * 2);
}
#endif // not AIX
//////////////////////////////////////////////////////////////////////////////
// Test os::vsnprintf and friends.
static void check_snprintf_result(int expected, size_t limit, int actual, bool expect_count) {
if (expect_count || ((size_t)expected < limit)) {
ASSERT_EQ(expected, actual);
} else {
ASSERT_GT(0, actual);
}
}
// PrintFn is expected to be int (*)(char*, size_t, const char*, ...).
// But jio_snprintf is a C-linkage function with that signature, which
// has a different type on some platforms (like Solaris).
template<typename PrintFn>
static void test_snprintf(PrintFn pf, bool expect_count) {
const char expected[] = "abcdefghijklmnopqrstuvwxyz";
const int expected_len = sizeof(expected) - 1;
const size_t padding_size = 10;
char buffer[2 * (sizeof(expected) + padding_size)];
char check_buffer[sizeof(buffer)];
const char check_char = '1'; // Something not in expected.
memset(check_buffer, check_char, sizeof(check_buffer));
const size_t sizes_to_test[] = {
sizeof(buffer) - padding_size, // Fits, with plenty of space to spare.
sizeof(buffer)/2, // Fits, with space to spare.
sizeof(buffer)/4, // Doesn't fit.
sizeof(expected) + padding_size + 1, // Fits, with a little room to spare
sizeof(expected) + padding_size, // Fits exactly.
sizeof(expected) + padding_size - 1, // Doesn't quite fit.
2, // One char + terminating NUL.
1, // Only space for terminating NUL.
0 }; // No space at all.
for (unsigned i = 0; i < ARRAY_SIZE(sizes_to_test); ++i) {
memset(buffer, check_char, sizeof(buffer)); // To catch stray writes.
size_t test_size = sizes_to_test[i];
ResourceMark rm;
stringStream s;
s.print("test_size: %zu", test_size);
SCOPED_TRACE(s.as_string());
size_t prefix_size = padding_size;
guarantee(test_size <= (sizeof(buffer) - prefix_size), "invariant");
size_t write_size = MIN2(sizeof(expected), test_size);
size_t suffix_size = sizeof(buffer) - prefix_size - write_size;
char* write_start = buffer + prefix_size;
char* write_end = write_start + write_size;
int result = pf(write_start, test_size, "%s", expected);
check_snprintf_result(expected_len, test_size, result, expect_count);
// Verify expected output.
if (test_size > 0) {
ASSERT_EQ(0, strncmp(write_start, expected, write_size - 1));
// Verify terminating NUL of output.
ASSERT_EQ('\0', write_start[write_size - 1]);
} else {
guarantee(test_size == 0, "invariant");
guarantee(write_size == 0, "invariant");
guarantee(prefix_size + suffix_size == sizeof(buffer), "invariant");
guarantee(write_start == write_end, "invariant");
}
// Verify no scribbling on prefix or suffix.
ASSERT_EQ(0, strncmp(buffer, check_buffer, prefix_size));
ASSERT_EQ(0, strncmp(write_end, check_buffer, suffix_size));
}
// Special case of 0-length buffer with empty (except for terminator) output.
check_snprintf_result(0, 0, pf(nullptr, 0, "%s", ""), expect_count);
check_snprintf_result(0, 0, pf(nullptr, 0, ""), expect_count);
}
// This is probably equivalent to os::snprintf, but we're being
// explicit about what we're testing here.
static int vsnprintf_wrapper(char* buf, size_t len, const char* fmt, ...) {
va_list args;
va_start(args, fmt);
int result = os::vsnprintf(buf, len, fmt, args);
va_end(args);
return result;
}
TEST_VM(os, vsnprintf) {
test_snprintf(vsnprintf_wrapper, true);
}
TEST_VM(os, snprintf) {
test_snprintf(os::snprintf, true);
}
// These are declared in jvm.h; test here, with related functions.
extern "C" {
int jio_vsnprintf(char*, size_t, const char*, va_list);
int jio_snprintf(char*, size_t, const char*, ...);
}
// This is probably equivalent to jio_snprintf, but we're being
// explicit about what we're testing here.
static int jio_vsnprintf_wrapper(char* buf, size_t len, const char* fmt, ...) {
va_list args;
va_start(args, fmt);
int result = jio_vsnprintf(buf, len, fmt, args);
va_end(args);
return result;
}
TEST_VM(os, jio_vsnprintf) {
test_snprintf(jio_vsnprintf_wrapper, false);
}
TEST_VM(os, jio_snprintf) {
test_snprintf(jio_snprintf, false);
}
#ifndef MAX_PATH
#define MAX_PATH (2 * K)
#endif
TEST_VM(os, realpath) {
// POSIX requires that the file exists; Windows tests for a valid drive letter
// but may or may not test if the file exists. */
static const char* nosuchpath = "/1234567890123456789";
static const char* tmppath = "/tmp";
char buffer[MAX_PATH];
// Test a non-existant path, but provide a short buffer.
errno = 0;
const char* returnedBuffer = os::realpath(nosuchpath, buffer, sizeof(nosuchpath) - 2);
// Reports ENOENT on Linux, ENAMETOOLONG on Windows.
EXPECT_TRUE(returnedBuffer == nullptr);
#ifdef _WINDOWS
EXPECT_TRUE(errno == ENAMETOOLONG);
#else
EXPECT_TRUE(errno == ENOENT);
#endif
// Test a non-existant path, but provide an adequate buffer.
errno = 0;
buffer[0] = 0;
returnedBuffer = os::realpath(nosuchpath, buffer, sizeof(nosuchpath) + 3);
// Reports ENOENT on Linux, may return 0 (and report an error) or buffer on some versions of Windows.
#ifdef _WINDOWS
if (returnedBuffer != nullptr) {
EXPECT_TRUE(returnedBuffer == buffer);
} else {
EXPECT_TRUE(errno != 0);
}
#else
EXPECT_TRUE(returnedBuffer == nullptr);
EXPECT_TRUE(errno == ENOENT);
#endif
// Test an existing path using a large buffer.
errno = 0;
returnedBuffer = os::realpath(tmppath, buffer, MAX_PATH);
EXPECT_TRUE(returnedBuffer == buffer);
// Test an existing path using a buffer that is too small on a normal macOS install.
errno = 0;
returnedBuffer = os::realpath(tmppath, buffer, strlen(tmppath) + 3);
// On MacOS, /tmp is a symlink to /private/tmp, so doesn't fit in a small buffer.
#ifndef __APPLE__
EXPECT_TRUE(returnedBuffer == buffer);
#else
EXPECT_TRUE(returnedBuffer == nullptr);
EXPECT_TRUE(errno == ENAMETOOLONG);
#endif
// Test an existing path using a buffer that is too small.
errno = 0;
returnedBuffer = os::realpath(tmppath, buffer, strlen(tmppath) - 1);
EXPECT_TRUE(returnedBuffer == nullptr);
EXPECT_TRUE(errno == ENAMETOOLONG);
// The following tests cause an assert inside os::realpath() in fastdebug mode:
#ifndef ASSERT
errno = 0;
returnedBuffer = os::realpath(nullptr, buffer, sizeof(buffer));
EXPECT_TRUE(returnedBuffer == nullptr);
EXPECT_TRUE(errno == EINVAL);
errno = 0;
returnedBuffer = os::realpath(tmppath, nullptr, sizeof(buffer));
EXPECT_TRUE(returnedBuffer == nullptr);
EXPECT_TRUE(errno == EINVAL);
errno = 0;
returnedBuffer = os::realpath(tmppath, buffer, 0);
EXPECT_TRUE(returnedBuffer == nullptr);
EXPECT_TRUE(errno == EINVAL);
#endif
}
#ifdef __APPLE__
// Not all macOS versions can use os::reserve_memory (i.e. anon_mmap) API
// to reserve executable memory, so before attempting to use it,
// we need to verify that we can do so by asking for a tiny executable
// memory chunk.
static inline bool can_reserve_executable_memory(void) {
bool executable = true;
size_t len = 128;
char* p = os::reserve_memory(len, mtTest, executable);
bool exec_supported = (p != nullptr);
if (exec_supported) {
os::release_memory(p, len);
}
return exec_supported;
}
#endif
// Test that os::release_memory() can deal with areas containing multiple mappings.
#define PRINT_MAPPINGS(s) { tty->print_cr("%s", s); os::print_memory_mappings((char*)p, total_range_len, tty); tty->cr(); }
//#define PRINT_MAPPINGS
#ifndef _AIX
// Release a range allocated with reserve_multiple carefully, to not trip mapping
// asserts on Windows in os::release_memory()
static void carefully_release_multiple(address start, int num_stripes, size_t stripe_len) {
for (int stripe = 0; stripe < num_stripes; stripe++) {
address q = start + (stripe * stripe_len);
os::release_memory((char*)q, stripe_len);
}
}
// Reserve an area consisting of multiple mappings
// (from multiple calls to os::reserve_memory)
static address reserve_multiple(int num_stripes, size_t stripe_len) {
assert(is_aligned(stripe_len, os::vm_allocation_granularity()), "Sanity");
#ifdef __APPLE__
// Workaround: try reserving executable memory to figure out
// if such operation is supported on this macOS version
const bool exec_supported = can_reserve_executable_memory();
#endif
address p = nullptr;
for (int tries = 0; tries < 256 && p == nullptr; tries ++) {
size_t total_range_len = num_stripes * stripe_len;
// Reserve a large contiguous area to get the address space...
p = (address)os::reserve_memory(total_range_len, mtTest);
EXPECT_NE(p, (address)nullptr);
// .. release it...
os::release_memory((char*)p, total_range_len);
// ... re-reserve in the same spot multiple areas...
for (int stripe = 0; stripe < num_stripes; stripe++) {
address q = p + (stripe * stripe_len);
// Commit, alternatingly with or without exec permission,
// to prevent kernel from folding these mappings.
#ifdef __APPLE__
const bool executable = exec_supported ? (stripe % 2 == 0) : false;
#else
const bool executable = stripe % 2 == 0;
#endif
q = (address)os::attempt_reserve_memory_at((char*)q, stripe_len, mtTest, executable);
if (q == nullptr) {
// Someone grabbed that area concurrently. Cleanup, then retry.
tty->print_cr("reserve_multiple: retry (%d)...", stripe);
carefully_release_multiple(p, stripe, stripe_len);
p = nullptr;
} else {
EXPECT_TRUE(os::commit_memory((char*)q, stripe_len, executable));
}
}
}
return p;
}
#endif // !AIX
// Reserve an area with a single call to os::reserve_memory,
// with multiple committed and uncommitted regions
static address reserve_one_commit_multiple(int num_stripes, size_t stripe_len) {
assert(is_aligned(stripe_len, os::vm_allocation_granularity()), "Sanity");
size_t total_range_len = num_stripes * stripe_len;
address p = (address)os::reserve_memory(total_range_len, mtTest);
EXPECT_NE(p, (address)nullptr);
for (int stripe = 0; stripe < num_stripes; stripe++) {
address q = p + (stripe * stripe_len);
if (stripe % 2 == 0) {
EXPECT_TRUE(os::commit_memory((char*)q, stripe_len, false));
}
}
return p;
}
#ifdef _WIN32
struct NUMASwitcher {
const bool _b;
NUMASwitcher(bool v): _b(UseNUMAInterleaving) { UseNUMAInterleaving = v; }
~NUMASwitcher() { UseNUMAInterleaving = _b; }
};
#endif
#ifndef _AIX // JDK-8257041
TEST_VM(os, release_multi_mappings) {
// With NMT enabled, this will trigger JDK-8263464. For now disable the test if NMT=on.
if (MemTracker::tracking_level() > NMT_off) {
return;
}
// Test that we can release an area created with multiple reservation calls
// What we do:
// A) we reserve 6 small segments (stripes) adjacent to each other. We commit
// them with alternating permissions to prevent the kernel from folding them into
// a single segment.
// -stripe-stripe-stripe-stripe-stripe-stripe-
// B) we release the middle four stripes with a single os::release_memory call. This
// tests that os::release_memory indeed works across multiple segments created with
// multiple os::reserve calls.
// -stripe-___________________________-stripe-
// C) Into the now vacated address range between the first and the last stripe, we
// re-reserve a new memory range. We expect this to work as a proof that the address
// range was really released by the single release call (B).
//
// Note that this is inherently racy. Between (B) and (C), some other thread may have
// reserved something into the hole in the meantime. Therefore we keep that range small and
// entrenched between the first and last stripe, which reduces the chance of some concurrent
// thread grabbing that memory.
const size_t stripe_len = os::vm_allocation_granularity();
const int num_stripes = 6;
const size_t total_range_len = stripe_len * num_stripes;
// reserve address space...
address p = reserve_multiple(num_stripes, stripe_len);
ASSERT_NE(p, (address)nullptr);
PRINT_MAPPINGS("A");
// .. release the middle stripes...
address p_middle_stripes = p + stripe_len;
const size_t middle_stripe_len = (num_stripes - 2) * stripe_len;
{
// On Windows, temporarily switch on UseNUMAInterleaving to allow release_memory to release
// multiple mappings in one go (otherwise we assert, which we test too, see death test below).
WINDOWS_ONLY(NUMASwitcher b(true);)
os::release_memory((char*)p_middle_stripes, middle_stripe_len);
}
PRINT_MAPPINGS("B");
// ...re-reserve the middle stripes. This should work unless release silently failed.
address p2 = (address)os::attempt_reserve_memory_at((char*)p_middle_stripes, middle_stripe_len, mtTest);
ASSERT_EQ(p2, p_middle_stripes);
PRINT_MAPPINGS("C");
// Clean up. Release all mappings.
{
WINDOWS_ONLY(NUMASwitcher b(true);) // allow release_memory to release multiple regions
os::release_memory((char*)p, total_range_len);
}
}
#endif // !AIX
#ifdef _WIN32
// On Windows, test that we recognize bad ranges.
// On debug this would assert. Test that too.
// On other platforms, we are unable to recognize bad ranges.
#ifdef ASSERT
#define TEST_RELEASE_RANGE_ERROR(name) TEST_VM_ASSERT_MSG(os, name, ".*bad release")
#else
#define TEST_RELEASE_RANGE_ERROR(name) TEST_VM_FATAL_ERROR_MSG(os, name, ".*Failed to release.*")
#endif
static char* setup_release_test_memory() {
char* p = os::reserve_memory(4 * M, mtTest);
EXPECT_NE(p, (char*)nullptr);
return p;
}
TEST_RELEASE_RANGE_ERROR(release_bad_range_start) {
char* p = setup_release_test_memory();
os::release_memory(p, M); // Release part of the range
}
TEST_RELEASE_RANGE_ERROR(release_bad_range_middle) {
char* p = setup_release_test_memory();
os::release_memory(p + M, M); // Release middle part
}
// Release more than the range (explicitly switch off NUMA here
// to make os::release_memory() test more strict and to not
// accidentally release neighbors)
TEST_RELEASE_RANGE_ERROR(release_beyond_range1) {
char* p = setup_release_test_memory();
NUMASwitcher b(false);
os::release_memory(p, M * 5);
}
TEST_RELEASE_RANGE_ERROR(release_beyond_range2) {
char* p = setup_release_test_memory();
NUMASwitcher b(false);
os::release_memory(p - M, M * 5);
}
TEST_RELEASE_RANGE_ERROR(release_beyond_range3) {
char* p = setup_release_test_memory();
NUMASwitcher b(false);
os::release_memory(p - M, M * 6);
}
TEST_RELEASE_RANGE_ERROR(release_already_released) {
char* p = setup_release_test_memory();
os::release_memory(p, 4 * M); // Release for real
os::release_memory(p, 4 * M); // Again, should fail
}
#endif // _WIN32
TEST_VM(os, release_one_mapping_multi_commits) {
// Test that we can release an area consisting of interleaved
// committed and uncommitted regions:
const size_t stripe_len = os::vm_allocation_granularity();
const int num_stripes = 6;
const size_t total_range_len = stripe_len * num_stripes;
// reserve address space...
address p = reserve_one_commit_multiple(num_stripes, stripe_len);
PRINT_MAPPINGS("A");
ASSERT_NE(p, (address)nullptr);
// // make things even more difficult by trying to reserve at the border of the region
address border = p + num_stripes * stripe_len;
address p2 = (address)os::attempt_reserve_memory_at((char*)border, stripe_len, mtTest);
PRINT_MAPPINGS("B");
ASSERT_TRUE(p2 == nullptr || p2 == border);
os::release_memory((char*)p, total_range_len);
PRINT_MAPPINGS("C");
if (p2 != nullptr) {
os::release_memory((char*)p2, stripe_len);
PRINT_MAPPINGS("D");
}
}
static void test_show_mappings(address start, size_t size) {
// Note: should this overflow, thats okay. stream will silently truncate. Does not matter for the test.
const size_t buflen = 4 * M;
char* buf = NEW_C_HEAP_ARRAY(char, buflen, mtInternal);
buf[0] = '\0';
stringStream ss(buf, buflen);
if (start != nullptr) {
os::print_memory_mappings((char*)start, size, &ss);
} else {
os::print_memory_mappings(&ss); // prints full address space
}
// Still an empty implementation on MacOS and AIX
#if defined(LINUX) || defined(_WIN32)
EXPECT_NE(buf[0], '\0');
#endif
// buf[buflen - 1] = '\0';
// tty->print_raw(buf);
FREE_C_HEAP_ARRAY(char, buf);
}
TEST_VM(os, show_mappings_small_range) {
test_show_mappings((address)0x100000, 2 * G);
}
TEST_VM(os, show_mappings_full_range) {
// Reserve a small range and fill it with a marker string, should show up
// on implementations displaying range snippets
char* p = os::reserve_memory(1 * M, mtTest);
if (p != nullptr) {
if (os::commit_memory(p, 1 * M, false)) {
strcpy(p, "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
}
}
test_show_mappings(nullptr, 0);
if (p != nullptr) {
os::release_memory(p, 1 * M);
}
}
#ifdef _WIN32
// Test os::win32::find_mapping
TEST_VM(os, find_mapping_simple) {
const size_t total_range_len = 4 * M;
os::win32::mapping_info_t mapping_info;
// Some obvious negatives
ASSERT_FALSE(os::win32::find_mapping((address)nullptr, &mapping_info));
ASSERT_FALSE(os::win32::find_mapping((address)4711, &mapping_info));
// A simple allocation
{
address p = (address)os::reserve_memory(total_range_len, mtTest);
ASSERT_NE(p, (address)nullptr);
PRINT_MAPPINGS("A");
for (size_t offset = 0; offset < total_range_len; offset += 4711) {
ASSERT_TRUE(os::win32::find_mapping(p + offset, &mapping_info));
ASSERT_EQ(mapping_info.base, p);
ASSERT_EQ(mapping_info.regions, 1);
ASSERT_EQ(mapping_info.size, total_range_len);
ASSERT_EQ(mapping_info.committed_size, 0);
}
// Test just outside the allocation
if (os::win32::find_mapping(p - 1, &mapping_info)) {
ASSERT_NE(mapping_info.base, p);
}
if (os::win32::find_mapping(p + total_range_len, &mapping_info)) {
ASSERT_NE(mapping_info.base, p);
}
os::release_memory((char*)p, total_range_len);
PRINT_MAPPINGS("B");
ASSERT_FALSE(os::win32::find_mapping(p, &mapping_info));
}
}
TEST_VM(os, find_mapping_2) {
// A more complex allocation, consisting of multiple regions.
const size_t total_range_len = 4 * M;
os::win32::mapping_info_t mapping_info;
const size_t stripe_len = total_range_len / 4;
address p = reserve_one_commit_multiple(4, stripe_len);
ASSERT_NE(p, (address)nullptr);
PRINT_MAPPINGS("A");
for (size_t offset = 0; offset < total_range_len; offset += 4711) {
ASSERT_TRUE(os::win32::find_mapping(p + offset, &mapping_info));
ASSERT_EQ(mapping_info.base, p);
ASSERT_EQ(mapping_info.regions, 4);
ASSERT_EQ(mapping_info.size, total_range_len);
ASSERT_EQ(mapping_info.committed_size, total_range_len / 2);
}
// Test just outside the allocation
if (os::win32::find_mapping(p - 1, &mapping_info)) {
ASSERT_NE(mapping_info.base, p);
}
if (os::win32::find_mapping(p + total_range_len, &mapping_info)) {
ASSERT_NE(mapping_info.base, p);
}
os::release_memory((char*)p, total_range_len);
PRINT_MAPPINGS("B");
ASSERT_FALSE(os::win32::find_mapping(p, &mapping_info));
}
TEST_VM(os, find_mapping_3) {
const size_t total_range_len = 4 * M;
os::win32::mapping_info_t mapping_info;
// A more complex case, consisting of multiple allocations.
{
const size_t stripe_len = total_range_len / 4;
address p = reserve_multiple(4, stripe_len);
ASSERT_NE(p, (address)nullptr);
PRINT_MAPPINGS("E");
for (int stripe = 0; stripe < 4; stripe++) {
ASSERT_TRUE(os::win32::find_mapping(p + (stripe * stripe_len), &mapping_info));
ASSERT_EQ(mapping_info.base, p + (stripe * stripe_len));
ASSERT_EQ(mapping_info.regions, 1);
ASSERT_EQ(mapping_info.size, stripe_len);
ASSERT_EQ(mapping_info.committed_size, stripe_len);
}
carefully_release_multiple(p, 4, stripe_len);
PRINT_MAPPINGS("F");
ASSERT_FALSE(os::win32::find_mapping(p, &mapping_info));
}
}
#endif // _WIN32
TEST_VM(os, os_pagesizes) {
ASSERT_EQ(os::min_page_size(), 4 * K);
ASSERT_LE(os::min_page_size(), os::vm_page_size());
// The vm_page_size should be the smallest in the set of allowed page sizes
// (contract says "default" page size but a lot of code actually assumes
// this to be the smallest page size; notable, deliberate exception is
// AIX which can have smaller page sizes but those are not part of the
// page_sizes() set).
ASSERT_EQ(os::page_sizes().smallest(), os::vm_page_size());
// The large page size, if it exists, shall be part of the set
if (UseLargePages) {
ASSERT_GT(os::large_page_size(), os::vm_page_size());
ASSERT_TRUE(os::page_sizes().contains(os::large_page_size()));
}
os::page_sizes().print_on(tty);
tty->cr();
}
static const int min_page_size_log2 = exact_log2(os::min_page_size());
static const int max_page_size_log2 = (int)BitsPerWord;
TEST_VM(os, pagesizes_test_range) {
for (int bit = min_page_size_log2; bit < max_page_size_log2; bit++) {
for (int bit2 = min_page_size_log2; bit2 < max_page_size_log2; bit2++) {
const size_t s = (size_t)1 << bit;
const size_t s2 = (size_t)1 << bit2;
os::PageSizes pss;
ASSERT_EQ((size_t)0, pss.smallest());
ASSERT_EQ((size_t)0, pss.largest());
// one size set
pss.add(s);
ASSERT_TRUE(pss.contains(s));
ASSERT_EQ(s, pss.smallest());
ASSERT_EQ(s, pss.largest());
ASSERT_EQ(pss.next_larger(s), (size_t)0);
ASSERT_EQ(pss.next_smaller(s), (size_t)0);
// two set
pss.add(s2);
ASSERT_TRUE(pss.contains(s2));
if (s2 < s) {
ASSERT_EQ(s2, pss.smallest());
ASSERT_EQ(s, pss.largest());
ASSERT_EQ(pss.next_larger(s2), (size_t)s);
ASSERT_EQ(pss.next_smaller(s2), (size_t)0);
ASSERT_EQ(pss.next_larger(s), (size_t)0);
ASSERT_EQ(pss.next_smaller(s), (size_t)s2);
} else if (s2 > s) {
ASSERT_EQ(s, pss.smallest());
ASSERT_EQ(s2, pss.largest());
ASSERT_EQ(pss.next_larger(s), (size_t)s2);
ASSERT_EQ(pss.next_smaller(s), (size_t)0);
ASSERT_EQ(pss.next_larger(s2), (size_t)0);
ASSERT_EQ(pss.next_smaller(s2), (size_t)s);
}
for (int bit3 = min_page_size_log2; bit3 < max_page_size_log2; bit3++) {
const size_t s3 = (size_t)1 << bit3;
ASSERT_EQ(s3 == s || s3 == s2, pss.contains(s3));
}
}
}
}
TEST_VM(os, pagesizes_test_print) {
os::PageSizes pss;
const size_t sizes[] = { 16 * K, 64 * K, 128 * K, 1 * M, 4 * M, 1 * G, 2 * G, 0 };
static const char* const expected = "16k, 64k, 128k, 1M, 4M, 1G, 2G";
for (int i = 0; sizes[i] != 0; i++) {
pss.add(sizes[i]);
}
char buffer[256];
stringStream ss(buffer, sizeof(buffer));
pss.print_on(&ss);
EXPECT_STREQ(expected, buffer);
}
TEST_VM(os, dll_address_to_function_and_library_name) {
char tmp[1024];
char output[1024];
stringStream st(output, sizeof(output));
#define EXPECT_CONTAINS(haystack, needle) \
EXPECT_THAT(haystack, HasSubstr(needle));
#define EXPECT_DOES_NOT_CONTAIN(haystack, needle) \
EXPECT_THAT(haystack, Not(HasSubstr(needle)));
// #define LOG(...) tty->print_cr(__VA_ARGS__); // enable if needed
#define LOG(...)
// Invalid addresses
LOG("os::print_function_and_library_name(st, -1) expects FALSE.");
address addr = (address)(intptr_t)-1;
EXPECT_FALSE(os::print_function_and_library_name(&st, addr));
LOG("os::print_function_and_library_name(st, nullptr) expects FALSE.");
addr = nullptr;
EXPECT_FALSE(os::print_function_and_library_name(&st, addr));
// Valid addresses
// Test with or without shorten-paths, demangle, and scratch buffer
for (int i = 0; i < 16; i++) {
const bool shorten_paths = (i & 1) != 0;
const bool demangle = (i & 2) != 0;
const bool strip_arguments = (i & 4) != 0;
const bool provide_scratch_buffer = (i & 8) != 0;
LOG("shorten_paths=%d, demangle=%d, strip_arguments=%d, provide_scratch_buffer=%d",
shorten_paths, demangle, strip_arguments, provide_scratch_buffer);
// Should show Threads::create_vm in libjvm
addr = CAST_FROM_FN_PTR(address, Threads::create_vm);
st.reset();
EXPECT_TRUE(os::print_function_and_library_name(&st, addr,
provide_scratch_buffer ? tmp : nullptr,
sizeof(tmp),
shorten_paths, demangle,
strip_arguments));
#ifdef _WINDOWS
// On Windows, if no full .pdb file is available, the output can be something like "0x... in ..."
if (strncmp(output, "0x", 2) != 0) {
#endif
EXPECT_CONTAINS(output, "Threads");
EXPECT_CONTAINS(output, "create_vm");
#ifdef _WINDOWS
}
#endif
EXPECT_CONTAINS(output, "jvm"); // "jvm.dll" or "libjvm.so" or similar
LOG("%s", output);
// Test truncation on scratch buffer
if (provide_scratch_buffer) {
st.reset();
tmp[10] = 'X';
EXPECT_TRUE(os::print_function_and_library_name(&st, addr, tmp, 10,
shorten_paths, demangle));
EXPECT_EQ(tmp[10], 'X');
LOG("%s", output);
}
}
}
// Not a regex! Very primitive, just match:
// "d" - digit
// "a" - ascii
// "." - everything
// rest must match
static bool very_simple_string_matcher(const char* pattern, const char* s) {
const size_t lp = strlen(pattern);
const size_t ls = strlen(s);
if (ls < lp) {
return false;
}
for (size_t i = 0; i < lp; i ++) {
switch (pattern[i]) {
case '.': continue;
case 'd': if (!isdigit(s[i])) return false; break;
case 'a': if (!isascii(s[i])) return false; break;
default: if (s[i] != pattern[i]) return false; break;
}
}
return true;
}
TEST_VM(os, iso8601_time) {
char buffer[os::iso8601_timestamp_size + 1]; // + space for canary
buffer[os::iso8601_timestamp_size] = 'X'; // canary
const char* result = nullptr;
// YYYY-MM-DDThh:mm:ss.mmm+zzzz
const char* const pattern_utc = "dddd-dd-dd.dd:dd:dd.ddd.0000";
const char* const pattern_local = "dddd-dd-dd.dd:dd:dd.ddd.dddd";
result = os::iso8601_time(buffer, sizeof(buffer), true);
tty->print_cr("%s", result);
EXPECT_EQ(result, buffer);
EXPECT_TRUE(very_simple_string_matcher(pattern_utc, result));
result = os::iso8601_time(buffer, sizeof(buffer), false);
tty->print_cr("%s", result);
EXPECT_EQ(result, buffer);
EXPECT_TRUE(very_simple_string_matcher(pattern_local, result));
// Test with explicit timestamps
result = os::iso8601_time(0, buffer, sizeof(buffer), true);
tty->print_cr("%s", result);
EXPECT_EQ(result, buffer);
EXPECT_TRUE(very_simple_string_matcher("1970-01-01.00:00:00.000+0000", result));
result = os::iso8601_time(17, buffer, sizeof(buffer), true);
tty->print_cr("%s", result);
EXPECT_EQ(result, buffer);
EXPECT_TRUE(very_simple_string_matcher("1970-01-01.00:00:00.017+0000", result));
// Canary should still be intact
EXPECT_EQ(buffer[os::iso8601_timestamp_size], 'X');
}
TEST_VM(os, is_first_C_frame) {
#if !defined(_WIN32) && !defined(ZERO) && !defined(__thumb__)
frame invalid_frame;
EXPECT_TRUE(os::is_first_C_frame(&invalid_frame)); // the frame has zeroes for all values
frame cur_frame = os::current_frame(); // this frame has to have a sender
EXPECT_FALSE(os::is_first_C_frame(&cur_frame));
#endif // _WIN32
}
#ifdef __GLIBC__
TEST_VM(os, trim_native_heap) {
EXPECT_TRUE(os::can_trim_native_heap());
os::size_change_t sc;
sc.before = sc.after = (size_t)-1;
EXPECT_TRUE(os::trim_native_heap(&sc));
tty->print_cr("%zu->%zu", sc.before, sc.after);
// Regardless of whether we freed memory, both before and after
// should be somewhat believable numbers (RSS).
const size_t min = 5 * M;
const size_t max = LP64_ONLY(20 * G) NOT_LP64(3 * G);
ASSERT_LE(min, sc.before);
ASSERT_GT(max, sc.before);
ASSERT_LE(min, sc.after);
ASSERT_GT(max, sc.after);
// Should also work
EXPECT_TRUE(os::trim_native_heap());
}
#else
TEST_VM(os, trim_native_heap) {
EXPECT_FALSE(os::can_trim_native_heap());
}
#endif // __GLIBC__
TEST_VM(os, open_O_CLOEXEC) {
#if !defined(_WIN32)
int fd = os::open("test_file.txt", O_RDWR | O_CREAT | O_TRUNC, 0666); // open will use O_CLOEXEC
EXPECT_TRUE(fd > 0);
int flags = ::fcntl(fd, F_GETFD);
EXPECT_TRUE((flags & FD_CLOEXEC) != 0); // if O_CLOEXEC worked, then FD_CLOEXEC should be ON
::close(fd);
#endif
}
TEST_VM(os, reserve_at_wish_address_shall_not_replace_mappings_smallpages) {
char* p1 = os::reserve_memory(M, mtTest);
ASSERT_NE(p1, nullptr);
char* p2 = os::attempt_reserve_memory_at(p1, M, mtTest);
ASSERT_EQ(p2, nullptr); // should have failed
os::release_memory(p1, M);
}
TEST_VM(os, reserve_at_wish_address_shall_not_replace_mappings_largepages) {
if (UseLargePages && !os::can_commit_large_page_memory()) { // aka special
const size_t lpsz = os::large_page_size();
char* p1 = os::reserve_memory_aligned(lpsz, lpsz, mtTest);
ASSERT_NE(p1, nullptr);
char* p2 = os::reserve_memory_special(lpsz, lpsz, lpsz, p1, false);
ASSERT_EQ(p2, nullptr); // should have failed
os::release_memory(p1, M);
} else {
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
}
#if !defined(_WINDOWS) && !defined(_AIX)
TEST_VM(os, free_without_uncommit) {
const size_t page_sz = os::vm_page_size();
const size_t pages = 64;
const size_t size = pages * page_sz;
char* base = os::reserve_memory(size, mtTest);
ASSERT_NE(base, (char*) nullptr);
ASSERT_TRUE(os::commit_memory(base, size, false));
for (size_t index = 0; index < pages; index++) {
base[index * page_sz] = 'a';
}
os::disclaim_memory(base, size);
// Ensure we can still use the memory without having to recommit.
for (size_t index = 0; index < pages; index++) {
base[index * page_sz] = 'a';
}
os::release_memory(base, size);
}
#endif
TEST_VM(os, commit_memory_or_exit) {
const size_t page_sz = os::vm_page_size();
const size_t size = 16 * page_sz;
const char* letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
char* base = os::reserve_memory(size, mtTest, false);
ASSERT_NOT_NULL(base);
os::commit_memory_or_exit(base, size, false, "Commit failed.");
strcpy(base, letters);
os::uncommit_memory(base, size, false);
os::commit_memory_or_exit(base, size, page_sz, false, "Commit with alignment hint failed.");
strcpy(base, letters);
os::uncommit_memory(base, size, false);
os::release_memory(base, size);
}
#if !defined(_AIX)
TEST_VM(os, map_memory_to_file) {
const char* letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const size_t size = strlen(letters) +1;
int fd = os::open("map_memory_to_file.txt", O_RDWR | O_CREAT, 0666);
EXPECT_TRUE(fd > 0);
EXPECT_TRUE(os::write(fd, letters, size));
char* result = os::map_memory_to_file(size, fd, mtTest);
ASSERT_NOT_NULL(result);
EXPECT_EQ(strcmp(letters, result), 0);
os::unmap_memory(result, size);
::close(fd);
}
TEST_VM(os, map_unmap_memory) {
const char* letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const char* path = "map_unmap_memory.txt";
const size_t size = strlen(letters) + 1;
int fd = os::open(path, O_RDWR | O_CREAT, 0666);
EXPECT_TRUE(fd > 0);
EXPECT_TRUE(os::write(fd, letters, size));
::close(fd);
fd = os::open(path, O_RDONLY, 0666);
char* result = os::map_memory(fd, path, 0, nullptr, size, mtTest, true, false);
ASSERT_NOT_NULL(result);
EXPECT_EQ(strcmp(letters, result), 0);
os::unmap_memory(result, size);
::close(fd);
}
TEST_VM(os, map_memory_to_file_aligned) {
const char* letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const size_t content_size = strlen(letters) + 1;
const size_t granularity = os::vm_allocation_granularity();
const size_t alignments[] = { granularity, 2 * granularity, 4 * granularity, 16 * granularity, 1 * M };
int fd = os::open("map_memory_to_file.txt", O_RDWR | O_CREAT, 0666);
EXPECT_TRUE(fd > 0);
ASSERT_TRUE(os::write(fd, letters, content_size));
const size_t size = granularity;
for (size_t alignment : alignments) {
char* result = os::map_memory_to_file_aligned(size, alignment, fd, mtTest);
ASSERT_NOT_NULL(result) << "Mapping failed for alignment=" << alignment;
EXPECT_TRUE(is_aligned(result, alignment)) << "Failed to aligned to " << alignment;
EXPECT_EQ(strcmp(letters, result), 0) << "Text mismatch at alignment=" << alignment;
os::unmap_memory(result, size);
}
::close(fd);
}
#endif // !defined(_AIX)
TEST_VM(os, dll_load_null_error_buf) {
// This should not crash.
void* lib = os::dll_load("NoSuchLib", nullptr, 0);
ASSERT_NULL(lib);
}
// --- Splittable Memory API tests ---
#define SKIP_IF_SPLITTABLE_NOT_SUPPORTED() \
WINDOWS_ONLY(if (os::win32::VirtualAlloc2 == nullptr) GTEST_SKIP() << "VirtualAlloc2 not available";)
TEST_VM(os, splittable_reserve_and_convert) {
SKIP_IF_SPLITTABLE_NOT_SUPPORTED();
const size_t size = 4 * os::vm_allocation_granularity();
os::PlaceholderRegion region = os::reserve_placeholder_memory(size, mtTest);
ASSERT_FALSE(region.is_empty());
ASSERT_EQ(region.size(), size);
ASSERT_NE(region.base(), (char*)nullptr);
char* reserved = os::convert_to_reserved(region);
ASSERT_EQ(reserved, region.base());
ASSERT_TRUE(os::commit_memory(reserved, size, false));
// Touch the memory to confirm it's usable.
memset(reserved, 0xAB, size);
EXPECT_EQ((unsigned char)reserved[0], 0xAB);
EXPECT_EQ((unsigned char)reserved[size - 1], 0xAB);
os::release_memory(reserved, size);
}
TEST_VM(os, splittable_split_two_way) {
SKIP_IF_SPLITTABLE_NOT_SUPPORTED();
const size_t granularity = os::vm_allocation_granularity();
const size_t total = 4 * granularity;
const size_t split_offset = 3 * granularity;
os::PlaceholderRegion region = os::reserve_placeholder_memory(total, mtTest);
ASSERT_FALSE(region.is_empty());
char* original_base = region.base();
os::PlaceholderRegion leading = os::split_memory(region, split_offset);
// Leading piece: [base, base+split_offset)
ASSERT_EQ(leading.base(), original_base);
ASSERT_EQ(leading.size(), split_offset);
// Trailing piece (region): [base+split_offset, base+total)
ASSERT_EQ(region.base(), original_base + split_offset);
ASSERT_EQ(region.size(), total - split_offset);
// Convert both and commit.
char* addr1 = os::convert_to_reserved(leading);
char* addr2 = os::convert_to_reserved(region);
ASSERT_EQ(addr1, original_base);
ASSERT_EQ(addr2, original_base + split_offset);
ASSERT_TRUE(os::commit_memory(addr1, split_offset, false));
ASSERT_TRUE(os::commit_memory(addr2, total - split_offset, false));
// Touch the memory to confirm it's usable.
memset(addr1, 0x11, split_offset);
memset(addr2, 0x22, total - split_offset);
EXPECT_EQ((unsigned char)addr1[0], 0x11);
EXPECT_EQ((unsigned char)addr2[0], 0x22);
// Verify we can release the parts separately.
os::release_memory(addr1, split_offset);
os::release_memory(addr2, total - split_offset);
}
// --- Aligned allocation tests ---
TEST_VM(os, reserve_memory_aligned_basic) {
const size_t granularity = os::vm_allocation_granularity();
const size_t alignments[] = { granularity, 2 * granularity, 4 * granularity, 16 * granularity };
for (size_t alignment : alignments) {
const size_t size = alignment;
char* result = os::reserve_memory_aligned(size, alignment, mtTest);
ASSERT_NE(result, (char*)nullptr) << "reserve_memory_aligned failed for alignment=" << alignment;
EXPECT_TRUE(is_aligned(result, alignment)) << "Result " << result << " not aligned to " << alignment;
ASSERT_TRUE(os::commit_memory(result, size, false));
memset(result, 0xCD, size);
EXPECT_EQ((unsigned char)result[0], 0xCD);
os::release_memory(result, size);
}
}
TEST_VM(os, reserve_memory_aligned_large) {
const size_t alignment = 1 * M;
const size_t size = alignment;
char* result = os::reserve_memory_aligned(size, alignment, mtTest);
ASSERT_NE(result, (char*)nullptr);
EXPECT_TRUE(is_aligned(result, alignment));
ASSERT_TRUE(os::commit_memory(result, size, false));
memset(result, 0xEF, size);
EXPECT_EQ((unsigned char)result[size - 1], 0xEF);
os::release_memory(result, size);
}