8332362: Implement os::committed_in_range for MacOS and AIX
Reviewed-by: stuefe
This commit is contained in:
parent
5ac2149b7b
commit
e825ccfe66
src/hotspot
test/hotspot
@ -3525,81 +3525,6 @@ static address get_stack_commited_bottom(address bottom, size_t size) {
|
||||
return nbot;
|
||||
}
|
||||
|
||||
bool os::committed_in_range(address start, size_t size, address& committed_start, size_t& committed_size) {
|
||||
int mincore_return_value;
|
||||
const size_t stripe = 1024; // query this many pages each time
|
||||
unsigned char vec[stripe + 1];
|
||||
// set a guard
|
||||
vec[stripe] = 'X';
|
||||
|
||||
const size_t page_sz = os::vm_page_size();
|
||||
uintx pages = size / page_sz;
|
||||
|
||||
assert(is_aligned(start, page_sz), "Start address must be page aligned");
|
||||
assert(is_aligned(size, page_sz), "Size must be page aligned");
|
||||
|
||||
committed_start = nullptr;
|
||||
|
||||
int loops = checked_cast<int>((pages + stripe - 1) / stripe);
|
||||
int committed_pages = 0;
|
||||
address loop_base = start;
|
||||
bool found_range = false;
|
||||
|
||||
for (int index = 0; index < loops && !found_range; index ++) {
|
||||
assert(pages > 0, "Nothing to do");
|
||||
uintx pages_to_query = (pages >= stripe) ? stripe : pages;
|
||||
pages -= pages_to_query;
|
||||
|
||||
// Get stable read
|
||||
while ((mincore_return_value = mincore(loop_base, pages_to_query * page_sz, vec)) == -1 && errno == EAGAIN);
|
||||
|
||||
// During shutdown, some memory goes away without properly notifying NMT,
|
||||
// E.g. ConcurrentGCThread/WatcherThread can exit without deleting thread object.
|
||||
// Bailout and return as not committed for now.
|
||||
if (mincore_return_value == -1 && errno == ENOMEM) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If mincore is not supported.
|
||||
if (mincore_return_value == -1 && errno == ENOSYS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
assert(vec[stripe] == 'X', "overflow guard");
|
||||
assert(mincore_return_value == 0, "Range must be valid");
|
||||
// Process this stripe
|
||||
for (uintx vecIdx = 0; vecIdx < pages_to_query; vecIdx ++) {
|
||||
if ((vec[vecIdx] & 0x01) == 0) { // not committed
|
||||
// End of current contiguous region
|
||||
if (committed_start != nullptr) {
|
||||
found_range = true;
|
||||
break;
|
||||
}
|
||||
} else { // committed
|
||||
// Start of region
|
||||
if (committed_start == nullptr) {
|
||||
committed_start = loop_base + page_sz * vecIdx;
|
||||
}
|
||||
committed_pages ++;
|
||||
}
|
||||
}
|
||||
|
||||
loop_base += pages_to_query * page_sz;
|
||||
}
|
||||
|
||||
if (committed_start != nullptr) {
|
||||
assert(committed_pages > 0, "Must have committed region");
|
||||
assert(committed_pages <= int(size / page_sz), "Can not commit more than it has");
|
||||
assert(committed_start >= start && committed_start < start + size, "Out of range");
|
||||
committed_size = page_sz * committed_pages;
|
||||
return true;
|
||||
} else {
|
||||
assert(committed_pages == 0, "Should not have committed region");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Linux uses a growable mapping for the stack, and if the mapping for
|
||||
// the stack guard pages is not removed when we detach a thread the
|
||||
// stack cannot grow beyond the pages where the stack guard was
|
||||
|
@ -93,6 +93,9 @@
|
||||
#define MAP_ANONYMOUS MAP_ANON
|
||||
#endif
|
||||
|
||||
/* Input/Output types for mincore(2) */
|
||||
typedef LINUX_ONLY(unsigned) char mincore_vec_t;
|
||||
|
||||
static jlong initial_time_count = 0;
|
||||
|
||||
static int clock_tics_per_sec = 100;
|
||||
@ -146,6 +149,94 @@ void os::check_dump_limit(char* buffer, size_t bufferSize) {
|
||||
VMError::record_coredump_status(buffer, success);
|
||||
}
|
||||
|
||||
bool os::committed_in_range(address start, size_t size, address& committed_start, size_t& committed_size) {
|
||||
|
||||
#ifdef _AIX
|
||||
committed_start = start;
|
||||
committed_size = size;
|
||||
return true;
|
||||
#else
|
||||
|
||||
int mincore_return_value;
|
||||
constexpr size_t stripe = 1024; // query this many pages each time
|
||||
mincore_vec_t vec [stripe + 1];
|
||||
|
||||
// set a guard
|
||||
DEBUG_ONLY(vec[stripe] = 'X');
|
||||
|
||||
size_t page_sz = os::vm_page_size();
|
||||
uintx pages = size / page_sz;
|
||||
|
||||
assert(is_aligned(start, page_sz), "Start address must be page aligned");
|
||||
assert(is_aligned(size, page_sz), "Size must be page aligned");
|
||||
|
||||
committed_start = nullptr;
|
||||
|
||||
int loops = checked_cast<int>((pages + stripe - 1) / stripe);
|
||||
int committed_pages = 0;
|
||||
address loop_base = start;
|
||||
bool found_range = false;
|
||||
|
||||
for (int index = 0; index < loops && !found_range; index ++) {
|
||||
assert(pages > 0, "Nothing to do");
|
||||
uintx pages_to_query = (pages >= stripe) ? stripe : pages;
|
||||
pages -= pages_to_query;
|
||||
|
||||
// Get stable read
|
||||
int fail_count = 0;
|
||||
while ((mincore_return_value = mincore(loop_base, pages_to_query * page_sz, vec)) == -1 && errno == EAGAIN){
|
||||
if (++fail_count == 1000){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// During shutdown, some memory goes away without properly notifying NMT,
|
||||
// E.g. ConcurrentGCThread/WatcherThread can exit without deleting thread object.
|
||||
// Bailout and return as not committed for now.
|
||||
if (mincore_return_value == -1 && errno == ENOMEM) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If mincore is not supported.
|
||||
if (mincore_return_value == -1 && errno == ENOSYS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
assert(vec[stripe] == 'X', "overflow guard");
|
||||
assert(mincore_return_value == 0, "Range must be valid");
|
||||
// Process this stripe
|
||||
for (uintx vecIdx = 0; vecIdx < pages_to_query; vecIdx ++) {
|
||||
if ((vec[vecIdx] & 0x01) == 0) { // not committed
|
||||
// End of current contiguous region
|
||||
if (committed_start != nullptr) {
|
||||
found_range = true;
|
||||
break;
|
||||
}
|
||||
} else { // committed
|
||||
// Start of region
|
||||
if (committed_start == nullptr) {
|
||||
committed_start = loop_base + page_sz * vecIdx;
|
||||
}
|
||||
committed_pages ++;
|
||||
}
|
||||
}
|
||||
|
||||
loop_base += pages_to_query * page_sz;
|
||||
}
|
||||
|
||||
if (committed_start != nullptr) {
|
||||
assert(committed_pages > 0, "Must have committed region");
|
||||
assert(committed_pages <= int(size / page_sz), "Can not commit more than it has");
|
||||
assert(committed_start >= start && committed_start < start + size, "Out of range");
|
||||
committed_size = page_sz * committed_pages;
|
||||
return true;
|
||||
} else {
|
||||
assert(committed_pages == 0, "Should not have committed region");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
int os::get_native_stack(address* stack, int frames, int toSkip) {
|
||||
int frame_idx = 0;
|
||||
int num_of_frames; // number of frames captured
|
||||
|
@ -276,13 +276,6 @@ bool os::dll_build_name(char* buffer, size_t size, const char* fname) {
|
||||
return (n != -1);
|
||||
}
|
||||
|
||||
#if !defined(LINUX) && !defined(_WINDOWS)
|
||||
bool os::committed_in_range(address start, size_t size, address& committed_start, size_t& committed_size) {
|
||||
committed_start = start;
|
||||
committed_size = size;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Helper for dll_locate_lib.
|
||||
// Pass buffer and printbuffer as we already printed the path to buffer
|
||||
|
@ -196,6 +196,42 @@ public:
|
||||
|
||||
os::release_memory(base, size);
|
||||
}
|
||||
|
||||
static void test_committed_in_range(size_t num_pages, size_t pages_to_touch) {
|
||||
bool result;
|
||||
size_t committed_size;
|
||||
address committed_start;
|
||||
size_t index;
|
||||
|
||||
const size_t page_sz = os::vm_page_size();
|
||||
const size_t size = num_pages * page_sz;
|
||||
|
||||
char* base = os::reserve_memory(size, !ExecMem, mtTest);
|
||||
ASSERT_NE(base, (char*)nullptr);
|
||||
|
||||
result = os::commit_memory(base, size, !ExecMem);
|
||||
ASSERT_TRUE(result);
|
||||
|
||||
result = os::committed_in_range((address)base, size, committed_start, committed_size);
|
||||
ASSERT_FALSE(result);
|
||||
|
||||
// Touch pages
|
||||
for (index = 0; index < pages_to_touch; index ++) {
|
||||
base[index * page_sz] = 'a';
|
||||
}
|
||||
|
||||
result = os::committed_in_range((address)base, size, committed_start, committed_size);
|
||||
ASSERT_TRUE(result);
|
||||
ASSERT_EQ(pages_to_touch * page_sz, committed_size);
|
||||
ASSERT_EQ(committed_start, (address)base);
|
||||
|
||||
os::uncommit_memory(base, size, false);
|
||||
|
||||
result = os::committed_in_range((address)base, size, committed_start, committed_size);
|
||||
ASSERT_FALSE(result);
|
||||
|
||||
os::release_memory(base, size);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_VM(CommittedVirtualMemoryTracker, test_committed_virtualmemory_region) {
|
||||
@ -214,3 +250,10 @@ TEST_VM(CommittedVirtualMemoryTracker, test_committed_virtualmemory_region) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#if !defined(_WINDOWS) && !defined(_AIX)
|
||||
TEST_VM(CommittedVirtualMemory, test_committed_in_range){
|
||||
CommittedVirtualMemoryTest::test_committed_in_range(1024, 1024);
|
||||
CommittedVirtualMemoryTest::test_committed_in_range(2, 1);
|
||||
}
|
||||
#endif
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2022 SAP SE. All rights reserved.
|
||||
* Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2022, 2024, 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
|
||||
@ -32,14 +32,27 @@ import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.concurrent.CyclicBarrier;
|
||||
|
||||
import static jdk.test.lib.Platform.isLinux;
|
||||
import static jdk.test.lib.Platform.isWindows;
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @test id=preTouch
|
||||
* @summary Test AlwaysPreTouchThreadStacks
|
||||
* @requires os.family != "aix"
|
||||
* @library /test/lib
|
||||
* @modules java.base/jdk.internal.misc
|
||||
* java.management
|
||||
* @run driver TestAlwaysPreTouchStacks
|
||||
* @run driver TestAlwaysPreTouchStacks preTouch
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test id=noPreTouch
|
||||
* @summary Test that only touched committed memory is reported as thread stack usage.
|
||||
* @requires os.family != "aix"
|
||||
* @library /test/lib
|
||||
* @modules java.base/jdk.internal.misc
|
||||
* java.management
|
||||
* @run driver TestAlwaysPreTouchStacks noPreTouch
|
||||
*/
|
||||
|
||||
public class TestAlwaysPreTouchStacks {
|
||||
@ -90,12 +103,22 @@ public class TestAlwaysPreTouchStacks {
|
||||
// should show up with fully - or almost fully - committed thread stacks.
|
||||
|
||||
} else {
|
||||
boolean preTouch;
|
||||
if (args.length == 1 && args[0].equals("noPreTouch")){
|
||||
preTouch = false;
|
||||
} else if (args.length == 1 && args[0].equals("preTouch")){
|
||||
preTouch = true;
|
||||
} else {
|
||||
throw new RuntimeException("Invalid test input. Must be 'preTouch' or 'noPreTouch'.");
|
||||
}
|
||||
ArrayList<String> vmArgs = new ArrayList<>();
|
||||
Collections.addAll(vmArgs,
|
||||
"-XX:+UnlockDiagnosticVMOptions",
|
||||
"-Xmx100M",
|
||||
"-XX:+AlwaysPreTouchStacks",
|
||||
"-XX:NativeMemoryTracking=summary", "-XX:+PrintNMTStatistics");
|
||||
if (preTouch){
|
||||
vmArgs.add("-XX:+AlwaysPreTouchStacks");
|
||||
}
|
||||
if (System.getProperty("os.name").contains("Linux")) {
|
||||
vmArgs.add("-XX:-UseMadvPopulateWrite");
|
||||
}
|
||||
@ -110,8 +133,8 @@ public class TestAlwaysPreTouchStacks {
|
||||
output.shouldContain("Alive: " + i);
|
||||
}
|
||||
|
||||
// We want to see, in the final NMT printout, a committed thread stack size very close to reserved
|
||||
// stack size. Like this:
|
||||
// If using -XX:+AlwaysPreTouchStacks, we want to see, in the final NMT printout,
|
||||
// a committed thread stack size very close to reserved stack size. Like this:
|
||||
// - Thread (reserved=10332400KB, committed=10284360KB)
|
||||
// (thread #10021)
|
||||
// (stack: reserved=10301560KB, committed=10253520KB) <<<<
|
||||
@ -135,8 +158,10 @@ public class TestAlwaysPreTouchStacks {
|
||||
// as thread stack. But without pre-touching, the thread stacks would be committed to about 1/5th
|
||||
// of their reserved size. Requiring them to be committed for over 3/4th shows that pretouch is
|
||||
// really working.
|
||||
if ((double)committed < ((double)reserved * 0.75)) {
|
||||
if (preTouch && (double)committed < ((double)reserved * 0.75)) {
|
||||
throw new RuntimeException("Expected a higher ratio between stack committed and reserved.");
|
||||
} else if (!preTouch && (double)committed > ((double)reserved * 0.50)){
|
||||
throw new RuntimeException("Expected a lower ratio between stack committed and reserved.");
|
||||
}
|
||||
// Added sanity tests: we expect our test threads to be still alive when NMT prints its final
|
||||
// report, so their stacks should dominate the NMT-reported total stack size.
|
||||
|
Loading…
x
Reference in New Issue
Block a user