8256844: Make NMT late-initializable

Reviewed-by: coleenp, zgu
This commit is contained in:
Thomas Stuefe 2021-08-04 12:19:02 +00:00
parent 4df1bc4bc6
commit eec64f5587
25 changed files with 1439 additions and 435 deletions

View File

@ -53,7 +53,7 @@
#include "runtime/safepointMechanism.hpp"
#include "runtime/vm_version.hpp"
#include "services/management.hpp"
#include "services/memTracker.hpp"
#include "services/nmtCommon.hpp"
#include "utilities/align.hpp"
#include "utilities/defaultStream.hpp"
#include "utilities/macros.hpp"
@ -1991,17 +1991,6 @@ bool Arguments::check_vm_args_consistency() {
status = false;
}
if (PrintNMTStatistics) {
#if INCLUDE_NMT
if (MemTracker::tracking_level() == NMT_off) {
#endif // INCLUDE_NMT
warning("PrintNMTStatistics is disabled, because native memory tracking is not enabled");
PrintNMTStatistics = false;
#if INCLUDE_NMT
}
#endif
}
status = CompilerConfig::check_args_consistency(status);
#if INCLUDE_JVMCI
if (status && EnableJVMCI) {
@ -3708,29 +3697,6 @@ jint Arguments::match_special_option_and_act(const JavaVMInitArgs* args,
JVMFlag::printFlags(tty, false);
vm_exit(0);
}
if (match_option(option, "-XX:NativeMemoryTracking", &tail)) {
#if INCLUDE_NMT
// The launcher did not setup nmt environment variable properly.
if (!MemTracker::check_launcher_nmt_support(tail)) {
warning("Native Memory Tracking did not setup properly, using wrong launcher?");
}
// Verify if nmt option is valid.
if (MemTracker::verify_nmt_option()) {
// Late initialization, still in single-threaded mode.
if (MemTracker::tracking_level() >= NMT_summary) {
MemTracker::init();
}
} else {
vm_exit_during_initialization("Syntax error, expecting -XX:NativeMemoryTracking=[off|summary|detail]", NULL);
}
continue;
#else
jio_fprintf(defaultStream::error_stream(),
"Native Memory Tracking is not supported in this VM\n");
return JNI_ERR;
#endif
}
#ifndef PRODUCT
if (match_option(option, "-XX:+PrintFlagsWithComments")) {
@ -3982,6 +3948,26 @@ jint Arguments::parse(const JavaVMInitArgs* initial_cmd_args) {
no_shared_spaces("CDS Disabled");
#endif // INCLUDE_CDS
#if INCLUDE_NMT
// Verify NMT arguments
const NMT_TrackingLevel lvl = NMTUtil::parse_tracking_level(NativeMemoryTracking);
if (lvl == NMT_unknown) {
jio_fprintf(defaultStream::error_stream(),
"Syntax error, expecting -XX:NativeMemoryTracking=[off|summary|detail]", NULL);
return JNI_ERR;
}
if (PrintNMTStatistics && lvl == NMT_off) {
warning("PrintNMTStatistics is disabled, because native memory tracking is not enabled");
FLAG_SET_DEFAULT(PrintNMTStatistics, false);
}
#else
if (!FLAG_IS_DEFAULT(NativeMemoryTracking) || PrintNMTStatistics) {
warning("Native Memory Tracking is not supported in this VM");
FLAG_SET_DEFAULT(NativeMemoryTracking, "off");
FLAG_SET_DEFAULT(PrintNMTStatistics, false);
}
#endif // INCLUDE_NMT
if (TraceDependencies && VerifyDependencies) {
if (!FLAG_IS_DEFAULT(TraceDependencies)) {
warning("TraceDependencies results may be inflated by VerifyDependencies");

View File

@ -64,6 +64,7 @@
#include "services/attachListener.hpp"
#include "services/mallocTracker.hpp"
#include "services/memTracker.hpp"
#include "services/nmtPreInit.hpp"
#include "services/nmtCommon.hpp"
#include "services/threadService.hpp"
#include "utilities/align.hpp"
@ -646,6 +647,15 @@ void* os::malloc(size_t size, MEMFLAGS memflags, const NativeCallStack& stack) {
NOT_PRODUCT(inc_stat_counter(&num_mallocs, 1));
NOT_PRODUCT(inc_stat_counter(&alloc_bytes, size));
#if INCLUDE_NMT
{
void* rc = NULL;
if (NMTPreInit::handle_malloc(&rc, size)) {
return rc;
}
}
#endif
// Since os::malloc can be called when the libjvm.{dll,so} is
// first loaded and we don't have a thread yet we must accept NULL also here.
assert(!os::ThreadCrashProtection::is_crash_protected(Thread::current_or_null()),
@ -705,6 +715,15 @@ void* os::realloc(void *memblock, size_t size, MEMFLAGS flags) {
void* os::realloc(void *memblock, size_t size, MEMFLAGS memflags, const NativeCallStack& stack) {
#if INCLUDE_NMT
{
void* rc = NULL;
if (NMTPreInit::handle_realloc(&rc, memblock, size)) {
return rc;
}
}
#endif
// For the test flag -XX:MallocMaxTestWords
if (has_reached_max_malloc_test_peak(size)) {
return NULL;
@ -755,6 +774,13 @@ void* os::realloc(void *memblock, size_t size, MEMFLAGS memflags, const NativeCa
// handles NULL pointers
void os::free(void *memblock) {
#if INCLUDE_NMT
if (NMTPreInit::handle_free(memblock)) {
return;
}
#endif
NOT_PRODUCT(inc_stat_counter(&num_frees, 1));
#ifdef ASSERT
if (memblock == NULL) return;

View File

@ -2707,6 +2707,11 @@ jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) {
jint parse_result = Arguments::parse(args);
if (parse_result != JNI_OK) return parse_result;
#if INCLUDE_NMT
// Initialize NMT right after argument parsing to keep the pre-NMT-init window small.
MemTracker::initialize();
#endif // INCLUDE_NMT
os::init_before_ergo();
jint ergo_result = Arguments::apply_ergo();

View File

@ -23,8 +23,11 @@
*/
#include "precompiled.hpp"
#include "jvm.h"
#include "logging/log.hpp"
#include "logging/logStream.hpp"
#include "memory/metaspaceUtils.hpp"
#include "runtime/atomic.hpp"
#include "runtime/globals.hpp"
#include "runtime/orderAccess.hpp"
#include "runtime/vmThread.hpp"
#include "runtime/vmOperations.hpp"
@ -32,6 +35,8 @@
#include "services/memReporter.hpp"
#include "services/mallocTracker.inline.hpp"
#include "services/memTracker.hpp"
#include "services/nmtCommon.hpp"
#include "services/nmtPreInit.hpp"
#include "services/threadStackTracker.hpp"
#include "utilities/debug.hpp"
#include "utilities/defaultStream.hpp"
@ -45,79 +50,44 @@ volatile NMT_TrackingLevel MemTracker::_tracking_level = NMT_unknown;
NMT_TrackingLevel MemTracker::_cmdline_tracking_level = NMT_unknown;
MemBaseline MemTracker::_baseline;
bool MemTracker::_is_nmt_env_valid = true;
static const size_t buffer_size = 64;
void MemTracker::initialize() {
bool rc = true;
assert(_tracking_level == NMT_unknown, "only call once");
NMT_TrackingLevel level = NMTUtil::parse_tracking_level(NativeMemoryTracking);
// Should have been validated before in arguments.cpp
assert(level == NMT_off || level == NMT_summary || level == NMT_detail,
"Invalid setting for NativeMemoryTracking (%s)", NativeMemoryTracking);
NMT_TrackingLevel MemTracker::init_tracking_level() {
// Memory type is encoded into tracking header as a byte field,
// make sure that we don't overflow it.
STATIC_ASSERT(mt_number_of_types <= max_jubyte);
char nmt_env_variable[buffer_size];
jio_snprintf(nmt_env_variable, sizeof(nmt_env_variable), "NMT_LEVEL_%d", os::current_process_id());
const char* nmt_env_value;
#ifdef _WINDOWS
// Read the NMT environment variable from the PEB instead of the CRT
char value[buffer_size];
nmt_env_value = GetEnvironmentVariable(nmt_env_variable, value, (DWORD)sizeof(value)) != 0 ? value : NULL;
#else
nmt_env_value = ::getenv(nmt_env_variable);
#endif
NMT_TrackingLevel level = NMT_off;
if (nmt_env_value != NULL) {
if (strcmp(nmt_env_value, "summary") == 0) {
level = NMT_summary;
} else if (strcmp(nmt_env_value, "detail") == 0) {
level = NMT_detail;
} else if (strcmp(nmt_env_value, "off") != 0) {
// The value of the environment variable is invalid
_is_nmt_env_valid = false;
}
// Remove the environment variable to avoid leaking to child processes
os::unsetenv(nmt_env_variable);
}
if (!MallocTracker::initialize(level) ||
!VirtualMemoryTracker::initialize(level)) {
level = NMT_off;
}
return level;
}
void MemTracker::init() {
NMT_TrackingLevel level = tracking_level();
if (level >= NMT_summary) {
if (!VirtualMemoryTracker::late_initialize(level) ||
!ThreadStackTracker::late_initialize(level)) {
shutdown();
if (level > NMT_off) {
if (!MallocTracker::initialize(level) ||
!VirtualMemoryTracker::initialize(level) ||
!ThreadStackTracker::initialize(level)) {
assert(false, "NMT initialization failed");
level = NMT_off;
log_warning(nmt)("NMT initialization failed. NMT disabled.");
return;
}
}
}
bool MemTracker::check_launcher_nmt_support(const char* value) {
if (strcmp(value, "=detail") == 0) {
if (MemTracker::tracking_level() != NMT_detail) {
return false;
}
} else if (strcmp(value, "=summary") == 0) {
if (MemTracker::tracking_level() != NMT_summary) {
return false;
}
} else if (strcmp(value, "=off") == 0) {
if (MemTracker::tracking_level() != NMT_off) {
return false;
}
} else {
_is_nmt_env_valid = false;
NMTPreInit::pre_to_post();
_tracking_level = _cmdline_tracking_level = level;
// Log state right after NMT initialization
if (log_is_enabled(Info, nmt)) {
LogTarget(Info, nmt) lt;
LogStream ls(lt);
ls.print_cr("NMT initialized: %s", NMTUtil::tracking_level_to_string(_tracking_level));
ls.print_cr("Preinit state: ");
NMTPreInit::print_state(&ls);
ls.cr();
}
return true;
}
bool MemTracker::verify_nmt_option() {
return _is_nmt_env_valid;
}
void* MemTracker::malloc_base(void* memblock) {
@ -174,6 +144,8 @@ bool MemTracker::transition_to(NMT_TrackingLevel level) {
void MemTracker::error_report(outputStream* output) {
if (tracking_level() >= NMT_summary) {
report(true, output, MemReporterBase::default_scale); // just print summary for error case.
output->print("Preinit state:");
NMTPreInit::print_state(output);
}
}
@ -214,9 +186,14 @@ void MemTracker::report(bool summary_only, outputStream* output, size_t scale) {
void MemTracker::tuning_statistics(outputStream* out) {
// NMT statistics
out->print_cr("Native Memory Tracking Statistics:");
out->print_cr("State: %s", NMTUtil::tracking_level_to_string(_tracking_level));
out->print_cr("Malloc allocation site table size: %d", MallocSiteTable::hash_buckets());
out->print_cr(" Tracking stack depth: %d", NMT_TrackingStackDepth);
NOT_PRODUCT(out->print_cr("Peak concurrent access: %d", MallocSiteTable::access_peak_count());)
out->cr();
MallocSiteTable::print_tuning_statistics(out);
out->cr();
out->print_cr("Preinit state:");
NMTPreInit::print_state(out);
out->cr();
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2013, 2021, 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
@ -115,42 +115,33 @@ class Tracker : public StackObj {
class MemTracker : AllStatic {
friend class VirtualMemoryTrackerTest;
// Helper; asserts that we are in post-NMT-init phase
static void assert_post_init() {
assert(is_initialized(), "NMT not yet initialized.");
}
public:
// Initializes NMT to whatever -XX:NativeMemoryTracking says.
// - Can only be called once.
// - NativeMemoryTracking must be validated beforehand.
static void initialize();
// Returns true if NMT had been initialized.
static bool is_initialized() {
return _tracking_level != NMT_unknown;
}
static inline NMT_TrackingLevel tracking_level() {
if (_tracking_level == NMT_unknown) {
// No fencing is needed here, since JVM is in single-threaded
// mode.
_tracking_level = init_tracking_level();
_cmdline_tracking_level = _tracking_level;
}
return _tracking_level;
}
// A late initialization, for the stuff(s) can not be
// done in init_tracking_level(), which can NOT malloc
// any memory.
static void init();
// Shutdown native memory tracking
// Shutdown native memory tracking.
// This transitions the tracking level:
// summary -> minimal
// detail -> minimal
static void shutdown();
// Verify native memory tracking command line option.
// This check allows JVM to detect if compatible launcher
// is used.
// If an incompatible launcher is used, NMT may not be
// able to start, even it is enabled by command line option.
// A warning message should be given if it is encountered.
static bool check_launcher_nmt_support(const char* value);
// This method checks native memory tracking environment
// variable value passed by launcher.
// Launcher only obligated to pass native memory tracking
// option value, but not obligated to validate the value,
// and launcher has option to discard native memory tracking
// option from the command line once it sets up the environment
// variable, so NMT has to catch the bad value here.
static bool verify_nmt_option();
// Transition the tracking level to specified level
static bool transition_to(NMT_TrackingLevel level);
@ -207,8 +198,12 @@ class MemTracker : AllStatic {
MallocTracker::record_arena_size_change(diff, flag);
}
// Note: virtual memory operations should only ever be called after NMT initialization
// (we do not do any reservations before that).
static inline void record_virtual_memory_reserve(void* addr, size_t size, const NativeCallStack& stack,
MEMFLAGS flag = mtNone) {
assert_post_init();
if (tracking_level() < NMT_summary) return;
if (addr != NULL) {
ThreadCritical tc;
@ -220,6 +215,7 @@ class MemTracker : AllStatic {
static inline void record_virtual_memory_reserve_and_commit(void* addr, size_t size,
const NativeCallStack& stack, MEMFLAGS flag = mtNone) {
assert_post_init();
if (tracking_level() < NMT_summary) return;
if (addr != NULL) {
ThreadCritical tc;
@ -231,6 +227,7 @@ class MemTracker : AllStatic {
static inline void record_virtual_memory_commit(void* addr, size_t size,
const NativeCallStack& stack) {
assert_post_init();
if (tracking_level() < NMT_summary) return;
if (addr != NULL) {
ThreadCritical tc;
@ -246,6 +243,7 @@ class MemTracker : AllStatic {
// The two new memory regions will be both registered under stack and
// memory flags of the original region.
static inline void record_virtual_memory_split_reserved(void* addr, size_t size, size_t split) {
assert_post_init();
if (tracking_level() < NMT_summary) return;
if (addr != NULL) {
ThreadCritical tc;
@ -256,6 +254,7 @@ class MemTracker : AllStatic {
}
static inline void record_virtual_memory_type(void* addr, MEMFLAGS flag) {
assert_post_init();
if (tracking_level() < NMT_summary) return;
if (addr != NULL) {
ThreadCritical tc;
@ -265,6 +264,7 @@ class MemTracker : AllStatic {
}
static void record_thread_stack(void* addr, size_t size) {
assert_post_init();
if (tracking_level() < NMT_summary) return;
if (addr != NULL) {
ThreadStackTracker::new_thread_stack((address)addr, size, CALLER_PC);
@ -272,6 +272,7 @@ class MemTracker : AllStatic {
}
static inline void release_thread_stack(void* addr, size_t size) {
assert_post_init();
if (tracking_level() < NMT_summary) return;
if (addr != NULL) {
ThreadStackTracker::delete_thread_stack((address)addr, size);

View File

@ -60,3 +60,27 @@ size_t NMTUtil::scale_from_name(const char* scale) {
return K;
}
const char* NMTUtil::tracking_level_to_string(NMT_TrackingLevel lvl) {
switch(lvl) {
case NMT_unknown: return "unknown"; break;
case NMT_off: return "off"; break;
case NMT_minimal: return "minimal"; break;
case NMT_summary: return "summary"; break;
case NMT_detail: return "detail"; break;
default: return "invalid"; break;
}
}
// Returns the parsed level; NMT_unknown if string is invalid
NMT_TrackingLevel NMTUtil::parse_tracking_level(const char* s) {
if (s != NULL) {
if (strcmp(s, "summary") == 0) {
return NMT_summary;
} else if (strcmp(s, "detail") == 0) {
return NMT_detail;
} else if (strcmp(s, "off") == 0) {
return NMT_off;
}
}
return NMT_unknown;
}

View File

@ -32,12 +32,52 @@
#define CALC_OBJ_SIZE_IN_TYPE(obj, type) (align_up(sizeof(obj), sizeof(type))/sizeof(type))
// Native memory tracking level
//
// The meaning of the different states:
//
// "unknown": pre-init phase (before parsing NMT arguments)
//
// "off": after initialization - NMT confirmed off.
// - nothing is tracked
// - no malloc headers are used
//
// "minimal": after shutdown - NMT had been on at some point but has been switched off
// - nothing is tracked
// - malloc headers are allocated but not initialized not used
//
// "summary": after initialization with NativeMemoryTracking=summary - NMT in summary mode
// - category summaries per tag are tracked
// - thread stacks are tracked
// - malloc headers are used
// - malloc call site table is allocated and used
//
// "detail": after initialization with NativeMemoryTracking=detail - NMT in detail mode
// - category summaries per tag are tracked
// - malloc details per call site are tracked
// - virtual memory mapping info is tracked
// - thread stacks are tracked
// - malloc headers are used
// - malloc call site table is allocated and used
//
// Valid state transitions:
//
// unknown ----> off
// |
// |--> summary --
// | |
// |--> detail --+--> minimal
//
// Please keep relation of numerical values!
// unknown < off < minimal < summary < detail
//
enum NMT_TrackingLevel {
NMT_unknown = 0xFF,
NMT_off = 0x00,
NMT_minimal = 0x01,
NMT_summary = 0x02,
NMT_detail = 0x03
NMT_unknown = 0,
NMT_off = 1,
NMT_minimal = 2,
NMT_summary = 3,
NMT_detail = 4
};
// Number of stack frames to capture. This is a
@ -83,6 +123,14 @@ class NMTUtil : AllStatic {
static size_t amount_in_scale(size_t amount, size_t scale) {
return (amount + scale / 2) / scale;
}
// Parses the tracking level from a string. Returns NMT_unknown if
// string is not a valid level.
static NMT_TrackingLevel parse_tracking_level(const char* s);
// Returns textual representation of a tracking level.
static const char* tracking_level_to_string(NMT_TrackingLevel level);
private:
static const char* _memory_type_names[mt_number_of_types];
};

View File

@ -0,0 +1,194 @@
/*
* Copyright (c) 2021 SAP SE. All rights reserved.
* Copyright (c) 2021, 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 "runtime/os.hpp"
#include "services/nmtPreInit.hpp"
#include "utilities/align.hpp"
#include "utilities/debug.hpp"
#include "utilities/ostream.hpp"
#include "utilities/globalDefinitions.hpp"
#if INCLUDE_NMT
// Obviously we cannot use os::malloc for any dynamic allocation during pre-NMT-init, so we must use
// raw malloc; to make this very clear, wrap them.
static void* raw_malloc(size_t s) { return ::malloc(s); }
static void* raw_realloc(void* old, size_t s) { return ::realloc(old, s); }
static void raw_free(void* p) { ::free(p); }
// We must ensure that the start of the payload area of the nmt lookup table nodes is malloc-aligned
static const size_t malloc_alignment = 2 * sizeof(void*); // could we use max_align_t?
STATIC_ASSERT(is_aligned(sizeof(NMTPreInitAllocation), malloc_alignment));
// --------- NMTPreInitAllocation --------------
NMTPreInitAllocation* NMTPreInitAllocation::do_alloc(size_t payload_size) {
const size_t outer_size = sizeof(NMTPreInitAllocation) + payload_size;
void* p = raw_malloc(outer_size);
NMTPreInitAllocation* a = new(p) NMTPreInitAllocation(payload_size);
return a;
}
NMTPreInitAllocation* NMTPreInitAllocation::do_reallocate(NMTPreInitAllocation* old, size_t new_payload_size) {
assert(old->next == NULL, "unhang from map first");
// We just reallocate the old block, header and all.
const size_t new_outer_size = sizeof(NMTPreInitAllocation) + new_payload_size;
void* p = raw_realloc(old, new_outer_size);
// re-stamp header with new size
NMTPreInitAllocation* a = new(p) NMTPreInitAllocation(new_payload_size);
return a;
}
void NMTPreInitAllocation::do_free(NMTPreInitAllocation* p) {
assert(p->next == NULL, "unhang from map first");
raw_free(p);
}
// --------- NMTPreInitAllocationTable --------------
NMTPreInitAllocationTable::NMTPreInitAllocationTable() {
::memset(_entries, 0, sizeof(_entries));
}
// print a string describing the current state
void NMTPreInitAllocationTable::print_state(outputStream* st) const {
// Collect some statistics and print them
int num_entries = 0;
int num_primary_entries = 0;
int longest_chain = 0;
size_t sum_bytes = 0;
for (int i = 0; i < table_size; i++) {
int chain_len = 0;
for (NMTPreInitAllocation* a = _entries[i]; a != NULL; a = a->next) {
chain_len++;
sum_bytes += a->size;
}
if (chain_len > 0) {
num_primary_entries++;
}
num_entries += chain_len;
longest_chain = MAX2(chain_len, longest_chain);
}
st->print("entries: %d (primary: %d, empties: %d), sum bytes: " SIZE_FORMAT
", longest chain length: %d",
num_entries, num_primary_entries, table_size - num_primary_entries,
sum_bytes, longest_chain);
}
#ifdef ASSERT
void NMTPreInitAllocationTable::print_map(outputStream* st) const {
for (int i = 0; i < table_size; i++) {
st->print("[%d]: ", i);
for (NMTPreInitAllocation* a = _entries[i]; a != NULL; a = a->next) {
st->print( PTR_FORMAT "(" SIZE_FORMAT ") ", p2i(a->payload()), a->size);
}
st->cr();
}
}
void NMTPreInitAllocationTable::verify() const {
// This verifies the buildup of the lookup table, including the load and the chain lengths.
// We should see chain lens of 0-1 under normal conditions. Under artificial conditions
// (20000 VM args) we should see maybe 6-7. From a certain length on we can be sure something
// is broken.
const int longest_acceptable_chain_len = 30;
int num_chains_too_long = 0;
for (index_t i = 0; i < table_size; i++) {
int len = 0;
for (const NMTPreInitAllocation* a = _entries[i]; a != NULL; a = a->next) {
index_t i2 = index_for_key(a->payload());
assert(i2 == i, "wrong hash");
assert(a->size > 0, "wrong size");
len++;
// very paranoid: search for dups
bool found = false;
for (const NMTPreInitAllocation* a2 = _entries[i]; a2 != NULL; a2 = a2->next) {
if (a == a2) {
assert(!found, "dup!");
found = true;
}
}
}
if (len > longest_acceptable_chain_len) {
num_chains_too_long++;
}
}
if (num_chains_too_long > 0) {
assert(false, "NMT preinit lookup table degenerated (%d/%d chains longer than %d)",
num_chains_too_long, table_size, longest_acceptable_chain_len);
}
}
#endif // ASSERT
// --------- NMTPreinit --------------
NMTPreInitAllocationTable* NMTPreInit::_table = NULL;
bool NMTPreInit::_nmt_was_initialized = false;
// Some statistics
unsigned NMTPreInit::_num_mallocs_pre = 0;
unsigned NMTPreInit::_num_reallocs_pre = 0;
unsigned NMTPreInit::_num_frees_pre = 0;
void NMTPreInit::create_table() {
assert(_table == NULL, "just once");
void* p = raw_malloc(sizeof(NMTPreInitAllocationTable));
_table = new(p) NMTPreInitAllocationTable();
}
// Allocate with os::malloc (hidden to prevent having to include os.hpp)
void* NMTPreInit::do_os_malloc(size_t size) {
return os::malloc(size, mtNMT);
}
// Switches from NMT pre-init state to NMT post-init state;
// in post-init, no modifications to the lookup table are possible.
void NMTPreInit::pre_to_post() {
assert(_nmt_was_initialized == false, "just once");
_nmt_was_initialized = true;
DEBUG_ONLY(verify();)
}
#ifdef ASSERT
void NMTPreInit::verify() {
if (_table != NULL) {
_table->verify();
}
assert(_num_reallocs_pre <= _num_mallocs_pre &&
_num_frees_pre <= _num_mallocs_pre, "stats are off");
}
#endif // ASSERT
void NMTPreInit::print_state(outputStream* st) {
if (_table != NULL) {
_table->print_state(st);
st->cr();
}
st->print_cr("pre-init mallocs: %u, pre-init reallocs: %u, pre-init frees: %u",
_num_mallocs_pre, _num_reallocs_pre, _num_frees_pre);
}
#endif // INCLUDE_NMT

View File

@ -0,0 +1,359 @@
/*
* Copyright (c) 2021 SAP SE. All rights reserved.
* Copyright (c) 2021, 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.
*
*/
#ifndef SHARE_SERVICES_NMT_PREINIT_HPP
#define SHARE_SERVICES_NMT_PREINIT_HPP
#include "memory/allStatic.hpp"
#ifdef ASSERT
#include "runtime/atomic.hpp"
#endif
#include "utilities/debug.hpp"
#include "utilities/globalDefinitions.hpp"
#include "utilities/macros.hpp"
#if INCLUDE_NMT
class outputStream;
// NMTPreInit is the solution to a specific problem:
//
// NMT tracks C-heap allocations (os::malloc and friends). Those can happen at all VM life stages,
// including very early during the dynamic C++ initialization of the hotspot, and in CreateJavaVM
// before argument parsing.
//
// However, before the VM parses NMT arguments, we do not know whether NMT is enabled or not. Can we
// just ignore early allocations? If the only problem were statistical correctness, sure: footprint-wise
// they are not really relevant.
//
// But there is one big problem: NMT uses malloc headers to keep meta information of malloced blocks.
// We have to consider those in os::free() when calling free(3).
//
// So:
// 1) NMT off:
// a) pre-NMT-init allocations have no header
// b) post-NMT-init allocations have no header
// 2) NMT on:
// a) pre-NMT-init allocations have no header
// b) post-NMT-init allocations do have a header
//
// The problem is that inside os::free(p), we only get an opaque void* p; we do not know if p had been
// allocated in (a) or (b) phase. Therefore, we do not know if p is preceded by an NMT header which we
// would need to subtract from the pointer before calling free(3). There is no safe way to "guess" here
// without risking C-heap corruption.
//
// To solve this, we need a way to quickly determine, at os::free(p), whether p was a pre-NMT-init
// allocation. There are several ways to do this, see discussion under JDK-8256844.
//
// One of the easiest and most elegant ways is to store early allocation pointers in a lookup table.
// This is what NMTPreInit does.
//
//////////////////////////////////////////////////////////////////////////
//
// VM initialization wrt NMT:
//
//---------------------------------------------------------------
//-> launcher dlopen's libjvm ^
// -> dynamic C++ initialization |
// of libjvm |
// |
//-> launcher starts new thread (maybe) NMT pre-init phase : store allocated pointers in lookup table
// |
//-> launcher invokes CreateJavaVM |
// -> VM initialization before arg parsing |
// -> VM argument parsing v
// -> NMT initialization -------------------------------------
// ^
// ... |
// -> VM life... NMT post-init phase : lookup table is read-only; use it in os::free() and os::realloc().
// ... |
// v
//----------------------------------------------------------------
//
//////////////////////////////////////////////////////////////////////////
//
// Notes:
// - The VM will malloc() and realloc() several thousand times before NMT initialization.
// Starting with a lot of arguments increases this number since argument parsing strdups
// around a lot.
// - However, *surviving* allocations (allocations not freed immediately) are much rarer:
// typically only about 300-500. Again, mainly depending on the number of VM args.
// - There are a few cases of pre-to-post-init reallocs where pre-init allocations get
// reallocated after NMT initialization. Those we need to handle with special care (see
// NMTPreInit::handle_realloc()). Because of them we need to store allocation size with
// every pre-init allocation.
// For the lookup table, design considerations are:
// - lookup speed is paramount since lookup is done for every os::free() call.
// - insert/delete speed only matters for VM startup - after NMT initialization the lookup
// table is readonly
// - memory consumption of the lookup table matters since we always pay for it, NMT on or off.
// - Obviously, nothing here can use *os::malloc*. Any dynamic allocations - if they cannot
// be avoided - should use raw malloc(3).
//
// We use a basic open hashmap, dimensioned generously - hash collisions should be very rare.
// The table is customized for holding malloced pointers. One main point of this map is that we do
// not allocate memory for the nodes themselves. Instead we piggy-back on the user allocation:
// the hashmap entry structure precedes, as a header, the malloced block. That way we avoid extra
// allocations just to hold the map nodes. This keeps runtime/memory overhead as small as possible.
struct NMTPreInitAllocation {
NMTPreInitAllocation* next;
const size_t size; // (inner) payload size without header
// <-- USER ALLOCATION (PAYLOAD) STARTS HERE -->
NMTPreInitAllocation(size_t size) : next(NULL), size(size) {};
// Returns start of the user data area
void* payload() { return this + 1; }
const void* payload() const { return this + 1; }
// These functions do raw-malloc/realloc/free a C-heap block of given payload size,
// preceded with a NMTPreInitAllocation header.
static NMTPreInitAllocation* do_alloc(size_t payload_size);
static NMTPreInitAllocation* do_reallocate(NMTPreInitAllocation* old, size_t new_payload_size);
static void do_free(NMTPreInitAllocation* p);
};
class NMTPreInitAllocationTable {
// Table_size: keep table size a prime and the hash function simple; this
// seems to give a good distribution for malloced pointers on all our libc variants.
// 8000ish is really plenty: normal VM runs have ~500 pre-init allocations to hold,
// VMs with insanely long command lines maybe ~700-1000. Which gives us an expected
// load factor of ~.1. Hash collisions should be very rare.
// ~8000 entries cost us ~64K for this table (64-bit), which is acceptable.
static const int table_size = 7919;
NMTPreInitAllocation* _entries[table_size];
typedef int index_t;
const index_t invalid_index = -1;
static unsigned calculate_hash(const void* p) {
uintptr_t tmp = p2i(p);
unsigned hash = (unsigned)tmp
LP64_ONLY( ^ (unsigned)(tmp >> 32));
return hash;
}
static index_t index_for_key(const void* p) {
const unsigned hash = calculate_hash(p);
return hash % table_size;
}
const NMTPreInitAllocation* const * find_entry(const void* p) const {
return const_cast<NMTPreInitAllocationTable*>(this)->find_entry(p);
}
NMTPreInitAllocation** find_entry(const void* p) {
const unsigned index = index_for_key(p);
NMTPreInitAllocation** aa = (&(_entries[index]));
while ((*aa) != NULL && (*aa)->payload() != p) {
aa = &((*aa)->next);
}
assert((*aa) == NULL || p == (*aa)->payload(),
"retrieve mismatch " PTR_FORMAT " vs " PTR_FORMAT ".",
p2i(p), p2i((*aa)->payload()));
return aa;
}
public:
NMTPreInitAllocationTable();
// Adds an entry to the table
void add(NMTPreInitAllocation* a) {
void* payload = a->payload();
const unsigned index = index_for_key(payload);
assert(a->next == NULL, "entry already in table?");
a->next = _entries[index]; // add to front
_entries[index] = a; // of list
assert(find(payload) == a, "add: reverse lookup error?");
}
// Find - but does not remove - an entry in this map.
// Returns NULL if not found.
const NMTPreInitAllocation* find(const void* p) const {
return *(find_entry(p));
}
// Find and removes an entry from the table. Asserts if not found.
NMTPreInitAllocation* find_and_remove(void* p) {
NMTPreInitAllocation** aa = find_entry(p);
assert((*aa) != NULL, "Entry not found: " PTR_FORMAT, p2i(p));
NMTPreInitAllocation* a = (*aa);
(*aa) = (*aa)->next; // remove from its list
DEBUG_ONLY(a->next = NULL;) // mark as removed
return a;
}
void print_state(outputStream* st) const;
DEBUG_ONLY(void print_map(outputStream* st) const;)
DEBUG_ONLY(void verify() const;)
};
// NMTPreInit is the outside interface to all of NMT preinit handling.
class NMTPreInit : public AllStatic {
static NMTPreInitAllocationTable* _table;
static bool _nmt_was_initialized;
// Some statistics
static unsigned _num_mallocs_pre; // Number of pre-init mallocs
static unsigned _num_reallocs_pre; // Number of pre-init reallocs
static unsigned _num_frees_pre; // Number of pre-init frees
static void create_table();
static void add_to_map(NMTPreInitAllocation* a) {
assert(!_nmt_was_initialized, "lookup map cannot be modified after NMT initialization");
// Only on add, we create the table on demand. Only needed on add, since everything should start
// with a call to os::malloc().
if (_table == NULL) {
create_table();
}
return _table->add(a);
}
static const NMTPreInitAllocation* find_in_map(void* p) {
assert(_table != NULL, "stray allocation?");
return _table->find(p);
}
static NMTPreInitAllocation* find_and_remove_in_map(void* p) {
assert(!_nmt_was_initialized, "lookup map cannot be modified after NMT initialization");
assert(_table != NULL, "stray allocation?");
return _table->find_and_remove(p);
}
// Just a wrapper for os::malloc to avoid including os.hpp here.
static void* do_os_malloc(size_t size);
public:
// Switches from NMT pre-init state to NMT post-init state;
// in post-init, no modifications to the lookup table are possible.
static void pre_to_post();
// Returns true if we are still in pre-init phase, false if post-init
static bool in_preinit_phase() { return _nmt_was_initialized == false; }
// Called from os::malloc.
// Returns true if allocation was handled here; in that case,
// *rc contains the return address.
static bool handle_malloc(void** rc, size_t size) {
size = MAX2((size_t)1, size); // malloc(0)
if (!_nmt_was_initialized) {
// pre-NMT-init:
// Allocate entry and add address to lookup table
NMTPreInitAllocation* a = NMTPreInitAllocation::do_alloc(size);
add_to_map(a);
(*rc) = a->payload();
_num_mallocs_pre++;
return true;
}
return false;
}
// Called from os::realloc.
// Returns true if reallocation was handled here; in that case,
// *rc contains the return address.
static bool handle_realloc(void** rc, void* old_p, size_t new_size) {
if (old_p == NULL) { // realloc(NULL, n)
return handle_malloc(rc, new_size);
}
new_size = MAX2((size_t)1, new_size); // realloc(.., 0)
if (!_nmt_was_initialized) {
// pre-NMT-init:
// - the address must already be in the lookup table
// - find the old entry, remove from table, reallocate, add to table
NMTPreInitAllocation* a = find_and_remove_in_map(old_p);
a = NMTPreInitAllocation::do_reallocate(a, new_size);
add_to_map(a);
(*rc) = a->payload();
_num_reallocs_pre++;
return true;
} else {
// post-NMT-init:
// If the old block was allocated during pre-NMT-init, we must relocate it: the
// new block must be allocated with "normal" os::malloc.
// We do this by:
// - look up (but not remove! lu table is read-only here.) the old entry
// - allocate new memory via os::malloc()
// - manually copy the old content over
// - return the new memory
// - The lu table is readonly so we keep the old address in the table. And we leave
// the old block allocated too, to prevent the libc from returning the same address
// and confusing us.
const NMTPreInitAllocation* a = find_in_map(old_p);
if (a != NULL) { // this was originally a pre-init allocation
void* p_new = do_os_malloc(new_size);
::memcpy(p_new, a->payload(), MIN2(a->size, new_size));
(*rc) = p_new;
return true;
}
}
return false;
}
// Called from os::free.
// Returns true if free was handled here.
static bool handle_free(void* p) {
if (p == NULL) { // free(NULL)
return true;
}
if (!_nmt_was_initialized) {
// pre-NMT-init:
// - the allocation must be in the hash map, since all allocations went through
// NMTPreInit::handle_malloc()
// - find the old entry, unhang from map, free it
NMTPreInitAllocation* a = find_and_remove_in_map(p);
NMTPreInitAllocation::do_free(a);
_num_frees_pre++;
return true;
} else {
// post-NMT-init:
// - look up (but not remove! lu table is read-only here.) the entry
// - if found, we do nothing: the lu table is readonly, so we keep the old address
// in the table. We leave the block allocated to prevent the libc from returning
// the same address and confusing us.
// - if not found, we let regular os::free() handle this pointer
if (find_in_map(p) != NULL) {
return true;
}
}
return false;
}
static void print_state(outputStream* st);
static void print_map(outputStream* st);
DEBUG_ONLY(static void verify();)
};
#endif // INCLUDE_NMT
#endif // SHARE_SERVICES_NMT_PREINIT_HPP

View File

@ -33,7 +33,7 @@
volatile size_t ThreadStackTracker::_thread_count = 0;
SortedLinkedList<SimpleThreadStackSite, ThreadStackTracker::compare_thread_stack_base>* ThreadStackTracker::_simple_thread_stacks = NULL;
bool ThreadStackTracker::late_initialize(NMT_TrackingLevel level) {
bool ThreadStackTracker::initialize(NMT_TrackingLevel level) {
if (level == NMT_detail && !track_as_vm()) {
_simple_thread_stacks = new (std::nothrow, ResourceObj::C_HEAP, mtNMT)
SortedLinkedList<SimpleThreadStackSite, ThreadStackTracker::compare_thread_stack_base>();

View File

@ -71,8 +71,7 @@ private:
static int compare_thread_stack_base(const SimpleThreadStackSite& s1, const SimpleThreadStackSite& s2);
static SortedLinkedList<SimpleThreadStackSite, compare_thread_stack_base>* _simple_thread_stacks;
public:
// Late phase initialization
static bool late_initialize(NMT_TrackingLevel level);
static bool initialize(NMT_TrackingLevel level);
static bool transition(NMT_TrackingLevel from, NMT_TrackingLevel to);
static void new_thread_stack(void* base, size_t size, const NativeCallStack& stack);

View File

@ -320,14 +320,9 @@ address ReservedMemoryRegion::thread_stack_uncommitted_bottom() const {
}
bool VirtualMemoryTracker::initialize(NMT_TrackingLevel level) {
assert(_reserved_regions == NULL, "only call once");
if (level >= NMT_summary) {
VirtualMemorySummary::initialize();
}
return true;
}
bool VirtualMemoryTracker::late_initialize(NMT_TrackingLevel level) {
if (level >= NMT_summary) {
_reserved_regions = new (std::nothrow, ResourceObj::C_HEAP, mtNMT)
SortedLinkedList<ReservedMemoryRegion, compare_reserved_region_base>();
return (_reserved_regions != NULL);

View File

@ -371,9 +371,6 @@ class VirtualMemoryTracker : AllStatic {
public:
static bool initialize(NMT_TrackingLevel level);
// Late phase initialization
static bool late_initialize(NMT_TrackingLevel level);
static bool add_reserved_region (address base_addr, size_t size, const NativeCallStack& stack, MEMFLAGS flag = mtNone);
static bool add_committed_region (address base_addr, size_t size, const NativeCallStack& stack);

View File

@ -108,7 +108,6 @@ static void SetJavaLauncherProp();
static void SetClassPath(const char *s);
static void SetMainModule(const char *s);
static void SelectVersion(int argc, char **argv, char **main_class);
static void SetJvmEnvironment(int argc, char **argv);
static jboolean ParseArguments(int *pargc, char ***pargv,
int *pmode, char **pwhat,
int *pret, const char *jrepath);
@ -284,9 +283,6 @@ JLI_Launch(int argc, char ** argv, /* main argc, argv */
jvmpath, sizeof(jvmpath),
jvmcfg, sizeof(jvmcfg));
/* Set env. Must be done before LoadJavaVM. */
SetJvmEnvironment(argc, argv);
ifn.CreateJavaVM = 0;
ifn.GetDefaultJavaVMInitArgs = 0;
@ -798,84 +794,6 @@ CheckJvmType(int *pargc, char ***argv, jboolean speculative) {
return jvmtype;
}
/*
* This method must be called before the VM is loaded, primarily
* used to parse and set any VM related options or env variables.
* This function is non-destructive leaving the argument list intact.
*/
static void
SetJvmEnvironment(int argc, char **argv) {
static const char* NMT_Env_Name = "NMT_LEVEL_";
const char* NMT_Arg_Name = IsJavaArgs() ? "-J-XX:NativeMemoryTracking=" : "-XX:NativeMemoryTracking=";
int i;
/* process only the launcher arguments */
for (i = 0; i < argc; i++) {
char *arg = argv[i];
/*
* Java launcher (!IsJavaArgs()):
* Since this must be a VM flag we stop processing once we see
* an argument the launcher would not have processed beyond (such
* as -version or -h), or an argument that indicates the following
* arguments are for the application (i.e. the main class name, or
* the -jar argument).
* Other launchers (IsJavaArgs()):
* All arguments have to be scanned to see if it is a -J argument.
*/
if (!IsJavaArgs() && i > 0) {
char *prev = argv[i - 1];
// skip non-dash arg preceded by class path specifiers
if (*arg != '-' && IsWhiteSpaceOption(prev)) {
continue;
}
if (*arg != '-' || isTerminalOpt(arg)) {
return;
}
}
/*
* The following case checks for "-XX:NativeMemoryTracking=value".
* If value is non null, an environmental variable set to this value
* will be created to be used by the JVM.
* The argument is passed to the JVM, which will check validity.
* The JVM is responsible for removing the env variable.
*/
if (JLI_StrCCmp(arg, NMT_Arg_Name) == 0) {
int retval;
// get what follows this parameter, include "="
size_t pnlen = JLI_StrLen(NMT_Arg_Name);
if (JLI_StrLen(arg) > pnlen) {
char* value = arg + pnlen;
size_t pbuflen = pnlen + JLI_StrLen(value) + 10; // 10 max pid digits
/*
* ensures that malloc successful
* DONT JLI_MemFree() pbuf. JLI_PutEnv() uses system call
* that could store the address.
*/
char * pbuf = (char*)JLI_MemAlloc(pbuflen);
JLI_Snprintf(pbuf, pbuflen, "%s%d=%s", NMT_Env_Name, JLI_GetPid(), value);
retval = JLI_PutEnv(pbuf);
if (JLI_IsTraceLauncher()) {
char* envName;
char* envBuf;
// ensures that malloc successful
envName = (char*)JLI_MemAlloc(pbuflen);
JLI_Snprintf(envName, pbuflen, "%s%d", NMT_Env_Name, JLI_GetPid());
printf("TRACER_MARKER: NativeMemoryTracking: env var is %s\n",envName);
printf("TRACER_MARKER: NativeMemoryTracking: putenv arg %s\n",pbuf);
envBuf = getenv(envName);
printf("TRACER_MARKER: NativeMemoryTracking: got value %s\n",envBuf);
free(envName);
}
}
}
}
}
/* copied from HotSpot function "atomll()" */
static int
parse_size(const char *s, jlong *result) {

View File

@ -93,8 +93,6 @@ int JLI_Open(const char* name, int flags);
JNIEXPORT void JNICALL
JLI_CmdToArgs(char *cmdline);
#define JLI_Lseek _lseeki64
#define JLI_PutEnv _putenv
#define JLI_GetPid _getpid
#else /* NIXES */
#include <unistd.h>
#include <strings.h>
@ -102,8 +100,6 @@ JLI_CmdToArgs(char *cmdline);
#define JLI_StrNCaseCmp(p1, p2, p3) strncasecmp((p1), (p2), (p3))
#define JLI_Snprintf snprintf
#define JLI_Open open
#define JLI_PutEnv putenv
#define JLI_GetPid getpid
#ifdef __linux__
#define _LARGFILE64_SOURCE
#define JLI_Lseek lseek64

View File

@ -0,0 +1,131 @@
/*
* Copyright (c) 2021 SAP SE. All rights reserved.
* Copyright (c) 2021, 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 "memory/allocation.hpp"
#include "runtime/os.hpp"
#include "services/nmtPreInit.hpp"
#include "utilities/debug.hpp"
#include "utilities/ostream.hpp"
#include "unittest.hpp"
// convenience log. switch on if debugging tests. Don't use tty, plain stdio only.
//#define LOG(...) { printf(__VA_ARGS__); printf("\n"); fflush(stdout); }
#define LOG(...)
// This tests the ability of the NMT pre-init system to deal with various combinations
// of pre- and post-init-allocations.
// The tests consist of two phases:
// 1) before NMT initialization (pre-NMT-init) we allocate and reallocate a bunch of
// blocks via os::malloc() and os::realloc(), and free some of them via os::free()
// 2) after NMT initialization, we reallocate some more, then free all of them.
//
// The intent is to check that blocks allocated in pre-init phase and potentially realloced
// in pre-init phase are handled correctly if further realloc'ed or free'd post-init.
// We manage to run tests in different phases with this technique:
// - for the pre-init phase, we start the tests in the constructor of a global object; that constructor will
// run as part of the dyn. C++ initialization of the gtestlauncher binary. Since the gtestlauncher links
// *statically* against the libjvm, gtestlauncher and libjvm initialization fold into one and are the same.
// - for the post-init phase, we just start it inside a TEST_VM scope, which needs to create the VM for
// us. So inside that scope VM initialization ran and with it the NMT initialization.
// To be sure, we assert those assumptions.
#if INCLUDE_NMT
// Some shorts to save writing out the flags every time
static void* os_malloc(size_t s) { return os::malloc(s, mtTest); }
static void* os_realloc(void* old, size_t s) { return os::realloc(old, s, mtTest); }
static void log_state() {
// Don't use tty! the only safe thing to use at all times is stringStream.
char tmp[256];
stringStream ss(tmp, sizeof(tmp));
NMTPreInit::print_state(&ss);
LOG("%s", tmp);
}
class TestAllocations {
void* p1, *p2, *p3, *p4;
public:
TestAllocations() {
test_pre();
}
void test_pre() {
// Note that this part will run every time a gtestlauncher execs (so, for every TEST_OTHER_VM).
assert(NMTPreInit::in_preinit_phase(),
"This should be run in pre-init phase (as part of C++ dyn. initialization)");
LOG("corner cases, pre-init (%d)", os::current_process_id());
log_state();
p1 = os_malloc(100); // normal allocation
os::free(os_malloc(0)); // 0-sized allocation, should be free-able
p2 = os_realloc(os_malloc(10), 20); // realloc, growing
p3 = os_realloc(os_malloc(20), 10); // realloc, shrinking
p4 = os_realloc(NULL, 10); // realloc with NULL pointer
os_realloc(os_realloc(os_malloc(20), 0), 30); // realloc to size 0 and back up again
os::free(os_malloc(20)); // malloc, free
os::free(os_realloc(os_malloc(20), 30)); // malloc, realloc, free
os::free(NULL); // free(null)
DEBUG_ONLY(NMTPreInit::verify();)
log_state();
}
void test_post() {
assert(NMTPreInit::in_preinit_phase() == false,
"This should be run in post-init phase (from inside a TEST_VM test)");
LOG("corner cases, post-init (%d)", os::current_process_id());
log_state();
p1 = os_realloc(p1, 140); // realloc from pre-init-phase, growing
p2 = os_realloc(p2, 150); // realloc from pre-init-phase, growing
p3 = os_realloc(p3, 50); // realloc from pre-init-phase, growing
p4 = os_realloc(p4, 8); // realloc from pre-init-phase, shrinking
DEBUG_ONLY(NMTPreInit::verify();)
log_state();
}
void free_all() {
assert(NMTPreInit::in_preinit_phase() == false,
"This should be run in post-init phase (from inside a TEST_VM test)");
LOG("corner cases, free-all (%d)", os::current_process_id());
log_state();
os::free(p1); os::free(p2); os::free(p3); os::free(p4);
DEBUG_ONLY(NMTPreInit::verify();)
log_state();
}
};
static TestAllocations g_test_allocations;
TEST_VM(NMTPreInit, pre_to_post_allocs) {
g_test_allocations.test_post();
g_test_allocations.free_all();
}
#endif // INCLUDE_NMT

View File

@ -0,0 +1,136 @@
/*
* Copyright (c) 2021 SAP SE. All rights reserved.
* Copyright (c) 2021, 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 "jvm_io.h"
#include "memory/allocation.hpp"
#include "runtime/os.hpp"
#include "services/nmtPreInit.hpp"
#include "utilities/debug.hpp"
#include "utilities/ostream.hpp"
#include "unittest.hpp"
#if INCLUDE_NMT
// This tests the NMTPreInitAllocationTable hash table used to store C-heap allocations before NMT initialization ran.
static size_t small_random_nonzero_size() {
// We keep the sizes random but not too random; the more regular the sizes, the
// more regular the malloc return pointers and the better we see how our hash
// function copes in the NMT preinit lu table.
switch (os::random() % 4) {
case 0: return 0x10;
case 1: return 0x42;
case 2: return 0x20;
case 3: return 0x80;
}
return 1;
}
//#define VERBOSE
static void print_and_check_table(NMTPreInitAllocationTable& table, int expected_num_entries) {
char tmp[256];
stringStream ss(tmp, sizeof(tmp));
char expected[256];
jio_snprintf(expected, sizeof(expected), "entries: %d", expected_num_entries);
table.print_state(&ss);
EXPECT_EQ(::strncmp(tmp, expected, strlen(expected)), 0);
#ifdef VERBOSE
tty->print_raw(tmp);
tty->cr();
#endif
DEBUG_ONLY(table.verify();)
}
TEST_VM(NMTPreInit, stress_test_map) {
NMTPreInitAllocationTable table;
const int num_allocs = 32 * K; // about factor 100 more than normally expected
NMTPreInitAllocation** allocations = NEW_C_HEAP_ARRAY(NMTPreInitAllocation*, num_allocs, mtTest);
// Fill table with allocations
for (int i = 0; i < num_allocs; i++) {
NMTPreInitAllocation* a = NMTPreInitAllocation::do_alloc(small_random_nonzero_size());
table.add(a);
allocations[i] = a;
}
print_and_check_table(table, num_allocs);
// look them all up
for (int i = 0; i < num_allocs; i++) {
const NMTPreInitAllocation* a = table.find(allocations[i]->payload());
ASSERT_EQ(a, allocations[i]);
}
// Randomly realloc
for (int j = 0; j < num_allocs/2; j++) {
int pos = os::random() % num_allocs;
NMTPreInitAllocation* a1 = allocations[pos];
NMTPreInitAllocation* a2 = table.find_and_remove(a1->payload());
ASSERT_EQ(a1, a2);
NMTPreInitAllocation* a3 = NMTPreInitAllocation::do_reallocate(a2, small_random_nonzero_size());
table.add(a3);
allocations[pos] = a3;
}
print_and_check_table(table, num_allocs);
// look them all up
for (int i = 0; i < num_allocs; i++) {
const NMTPreInitAllocation* a = table.find(allocations[i]->payload());
ASSERT_EQ(a, allocations[i]);
}
// free all
for (int i = 0; i < num_allocs; i++) {
NMTPreInitAllocation* a = table.find_and_remove(allocations[i]->payload());
ASSERT_EQ(a, allocations[i]);
NMTPreInitAllocation::do_free(a);
allocations[i] = NULL;
}
print_and_check_table(table, 0);
FREE_C_HEAP_ARRAY(NMTPreInitAllocation*, allocations);
}
#ifdef ASSERT
// Test that we will assert if the lookup table is seriously over-booked.
TEST_VM_ASSERT_MSG(NMTPreInit, assert_on_lu_table_overflow, ".*NMT preinit lookup table degenerated.*") {
NMTPreInitAllocationTable table;
const int num_allocs = 400 * 1000; // anything above ~250K entries should trigger the assert (note: normal number of entries is ~500)
for (int i = 0; i < num_allocs; i++) {
NMTPreInitAllocation* a = NMTPreInitAllocation::do_alloc(1);
table.add(a);
}
#ifdef VERBOSE
table.print_state(tty);
tty->cr();
#endif
table.verify();
}
#endif // ASSERT
#endif // INCLUDE_NMT

View File

@ -208,12 +208,20 @@ public:
};
TEST_VM(CommittedVirtualMemoryTracker, test_committed_virtualmemory_region) {
VirtualMemoryTracker::initialize(NMT_detail);
VirtualMemoryTracker::late_initialize(NMT_detail);
CommittedVirtualMemoryTest::test();
CommittedVirtualMemoryTest::test_committed_region();
CommittedVirtualMemoryTest::test_partial_region();
// This tests the VM-global NMT facility. The test must *not* modify global state,
// since that interferes with other tests!
// The gtestLauncher are called with and without -XX:NativeMemoryTracking during jtreg-controlled
// gtests.
if (MemTracker::tracking_level() >= NMT_detail) {
CommittedVirtualMemoryTest::test();
CommittedVirtualMemoryTest::test_committed_region();
CommittedVirtualMemoryTest::test_partial_region();
} else {
tty->print_cr("skipped.");
}
}
#endif // INCLUDE_NMT

View File

@ -26,6 +26,7 @@
#include "memory/resourceArea.hpp"
#include "runtime/os.hpp"
#include "runtime/thread.hpp"
#include "services/memTracker.hpp"
#include "utilities/globalDefinitions.hpp"
#include "utilities/macros.hpp"
#include "utilities/ostream.hpp"
@ -412,11 +413,13 @@ struct NUMASwitcher {
#endif
#ifndef _AIX // JDK-8257041
#if defined(__APPLE__) && !defined(AARCH64) // JDK-8267339
TEST_VM(os, DISABLED_release_multi_mappings) {
#else
TEST_VM(os, release_multi_mappings) {
#endif
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
const size_t stripe_len = 4 * M;
const int num_stripes = 4;

View File

@ -21,6 +21,15 @@
* questions.
*/
// Tests here test the VM-global NMT facility.
// The tests must *not* modify global state! E.g. switch NMT on or off. Instead, they
// should work passively with whatever setting the gtestlauncher had been started with
// - if NMT is enabled, test NMT, otherwise do whatever minimal tests make sense if NMT
// is off.
//
// The gtestLauncher then are called with various levels of -XX:NativeMemoryTracking during
// jtreg-controlled gtests (see test/hotspot/jtreg/gtest/NMTGtests.java)
#include "precompiled.hpp"
// Included early because the NMT flags don't include it.
@ -28,10 +37,15 @@
#if INCLUDE_NMT
#include "memory/virtualspace.hpp"
#include "services/memTracker.hpp"
#include "services/virtualMemoryTracker.hpp"
#include "utilities/globalDefinitions.hpp"
#include "unittest.hpp"
#include <stdio.h>
// #define LOG(...) printf(__VA_ARGS__); printf("\n"); fflush(stdout);
#define LOG(...)
namespace {
struct R {
@ -47,11 +61,22 @@ namespace {
check_inner((rmr), NULL, 0, __FILE__, __LINE__); \
} while (false)
static void diagnostic_print(ReservedMemoryRegion* rmr) {
CommittedRegionIterator iter = rmr->iterate_committed_regions();
LOG("In reserved region " PTR_FORMAT ", size " SIZE_FORMAT_HEX ":", p2i(rmr->base()), rmr->size());
for (const CommittedMemoryRegion* region = iter.next(); region != NULL; region = iter.next()) {
LOG(" committed region: " PTR_FORMAT ", size " SIZE_FORMAT_HEX, p2i(region->base()), region->size());
}
}
static void check_inner(ReservedMemoryRegion* rmr, R* regions, size_t regions_size, const char* file, int line) {
CommittedRegionIterator iter = rmr->iterate_committed_regions();
size_t i = 0;
size_t size = 0;
// Helpful log
diagnostic_print(rmr);
#define WHERE " from " << file << ":" << line
for (const CommittedMemoryRegion* region = iter.next(); region != NULL; region = iter.next()) {
@ -69,11 +94,10 @@ static void check_inner(ReservedMemoryRegion* rmr, R* regions, size_t regions_si
class VirtualMemoryTrackerTest {
public:
static void test_add_committed_region_adjacent() {
VirtualMemoryTracker::initialize(NMT_detail);
VirtualMemoryTracker::late_initialize(NMT_detail);
address addr = (address)0x10000000;
size_t size = 0x01000000;
ReservedSpace rs(size);
address addr = (address)rs.base();
address frame1 = (address)0x1234;
address frame2 = (address)0x1235;
@ -81,10 +105,7 @@ public:
NativeCallStack stack(&frame1, 1);
NativeCallStack stack2(&frame2, 1);
// Add the reserved memory
VirtualMemoryTracker::add_reserved_region(addr, size, stack, mtTest);
// Fetch the added RMR added above
// Fetch the added RMR for the space
ReservedMemoryRegion* rmr = VirtualMemoryTracker::_reserved_regions->find(ReservedMemoryRegion(addr, size));
ASSERT_EQ(rmr->size(), size);
@ -147,11 +168,10 @@ public:
}
static void test_add_committed_region_adjacent_overlapping() {
VirtualMemoryTracker::initialize(NMT_detail);
VirtualMemoryTracker::late_initialize(NMT_detail);
address addr = (address)0x10000000;
size_t size = 0x01000000;
ReservedSpace rs(size);
address addr = (address)rs.base();
address frame1 = (address)0x1234;
address frame2 = (address)0x1235;
@ -162,7 +182,7 @@ public:
// Add the reserved memory
VirtualMemoryTracker::add_reserved_region(addr, size, stack, mtTest);
// Fetch the added RMR added above
// Fetch the added RMR for the space
ReservedMemoryRegion* rmr = VirtualMemoryTracker::_reserved_regions->find(ReservedMemoryRegion(addr, size));
ASSERT_EQ(rmr->size(), size);
@ -235,11 +255,10 @@ public:
}
static void test_add_committed_region_overlapping() {
VirtualMemoryTracker::initialize(NMT_detail);
VirtualMemoryTracker::late_initialize(NMT_detail);
address addr = (address)0x10000000;
size_t size = 0x01000000;
ReservedSpace rs(size);
address addr = (address)rs.base();
address frame1 = (address)0x1234;
address frame2 = (address)0x1235;
@ -247,10 +266,7 @@ public:
NativeCallStack stack(&frame1, 1);
NativeCallStack stack2(&frame2, 1);
// Add the reserved memory
VirtualMemoryTracker::add_reserved_region(addr, size, stack, mtTest);
// Fetch the added RMR added above
// Fetch the added RMR for the space
ReservedMemoryRegion* rmr = VirtualMemoryTracker::_reserved_regions->find(ReservedMemoryRegion(addr, size));
ASSERT_EQ(rmr->size(), size);
@ -410,11 +426,10 @@ public:
}
static void test_remove_uncommitted_region() {
VirtualMemoryTracker::initialize(NMT_detail);
VirtualMemoryTracker::late_initialize(NMT_detail);
address addr = (address)0x10000000;
size_t size = 0x01000000;
ReservedSpace rs(size);
address addr = (address)rs.base();
address frame1 = (address)0x1234;
address frame2 = (address)0x1235;
@ -422,10 +437,7 @@ public:
NativeCallStack stack(&frame1, 1);
NativeCallStack stack2(&frame2, 1);
// Add the reserved memory
VirtualMemoryTracker::add_reserved_region(addr, size, stack, mtTest);
// Fetch the added RMR added above
// Fetch the added RMR for the space
ReservedMemoryRegion* rmr = VirtualMemoryTracker::_reserved_regions->find(ReservedMemoryRegion(addr, size));
ASSERT_EQ(rmr->size(), size);
@ -539,12 +551,20 @@ public:
}
};
TEST_VM(VirtualMemoryTracker, add_committed_region) {
VirtualMemoryTrackerTest::test_add_committed_region();
TEST_VM(NMT_VirtualMemoryTracker, add_committed_region) {
if (MemTracker::tracking_level() >= NMT_detail) {
VirtualMemoryTrackerTest::test_add_committed_region();
} else {
tty->print_cr("skipped.");
}
}
TEST_VM(VirtualMemoryTracker, remove_uncommitted_region) {
VirtualMemoryTrackerTest::test_remove_uncommitted_region();
TEST_VM(NMT_VirtualMemoryTracker, remove_uncommitted_region) {
if (MemTracker::tracking_level() >= NMT_detail) {
VirtualMemoryTrackerTest::test_remove_uncommitted_region();
} else {
tty->print_cr("skipped.");
}
}
#endif // INCLUDE_NMT

View File

@ -98,7 +98,8 @@ tier1_common = \
sanity/BasicVMTest.java \
gtest/GTestWrapper.java \
gtest/MetaspaceGtests.java \
gtest/LargePageGtests.java
gtest/LargePageGtests.java \
gtest/NMTGtests.java \
tier1_compiler = \
:tier1_compiler_1 \
@ -300,7 +301,6 @@ tier1_runtime = \
-runtime/modules/LoadUnloadModuleStress.java \
-runtime/modules/ModuleStress/ExportModuleStressTest.java \
-runtime/modules/ModuleStress/ModuleStressGC.java \
-runtime/NMT \
-runtime/ReservedStack \
-runtime/SelectionResolution/AbstractMethodErrorTest.java \
-runtime/SelectionResolution/IllegalAccessErrorTest.java \
@ -475,7 +475,8 @@ hotspot_runtime_minimalvm = \
runtime/logging
hotspot_nmt = \
runtime/NMT
runtime/NMT \
gtest/NMTGtests.java
hotspot_rest_runtime = \
:hotspot_runtime \

View File

@ -0,0 +1,49 @@
/*
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2021 SAP SE. 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.
*
*/
/*
* This tests NMT by running gtests with NMT enabled.
*
* To save time, we just run them for debug builds (where we would catch assertions) and only a selection of tests
* (namely, NMT tests themselves, and - for the detail statistics - os tests, since those reserve a lot and stress NMT)
*/
/* @test id=nmt-summary
* @summary Run NMT-related gtests with summary statistics
* @library /test/lib
* @modules java.base/jdk.internal.misc
* java.xml
* @requires vm.debug
* @run main/native GTestWrapper --gtest_filter=NMT* -XX:NativeMemoryTracking=summary
*/
/* @test id=nmt-detail
* @summary Run NMT-related gtests with detailed statistics
* @library /test/lib
* @modules java.base/jdk.internal.misc
* java.xml
* @requires vm.debug
* @run main/native GTestWrapper --gtest_filter=NMT*:os* -XX:NativeMemoryTracking=detail
*/

View File

@ -0,0 +1,73 @@
/*
* Copyright (c) 2021 SAP SE. All rights reserved.
* Copyright (c) 2021, 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.
*/
/*
* With JDK-8256844 "Make NMT late-initializable", NMT should work out of the box with jdk launchers other than
* java.exe.
*
* Test that assumption (we test with javac and jar and leave it at that, other tools should be fine as well)
*/
/**
* @test id=javac
* @bug 8256844
* @library /test/lib
* @modules java.base/jdk.internal.misc
* java.management
* @run driver NMTForOtherLaunchersTest javac
*/
/**
* @test id=jar
* @bug 8256844
* @library /test/lib
* @modules java.base/jdk.internal.misc
* java.management
* @run driver NMTForOtherLaunchersTest jar
*/
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.process.ProcessTools;
import jdk.test.lib.JDKToolFinder;
public class NMTForOtherLaunchersTest {
public static void main(String args[]) throws Exception {
String tool = args[0];
ProcessBuilder pb = new ProcessBuilder();
pb.command(new String[]{
JDKToolFinder.getJDKTool(tool),
"-J-XX:NativeMemoryTracking=summary",
"-J-XX:+UnlockDiagnosticVMOptions",
"-J-XX:+PrintNMTStatistics",
"--help"});
System.out.println(pb.command());
OutputAnalyzer output = new OutputAnalyzer(pb.start());
output.shouldHaveExitValue(0);
// We should not see the "wrong launcher?" message, which would indicate
// an older JDK, and we should see the NMT stat output when the VM shuts down.
output.shouldNotContain("wrong launcher");
output.shouldContain("Native Memory Tracking:");
output.shouldMatch("Total: reserved=\\d+, committed=\\d+.*");
}
}

View File

@ -0,0 +1,220 @@
/*
* Copyright (c) 2021 SAP SE. All rights reserved.
* Copyright (c) 2021, 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.
*/
/*
* This test tests the ability of NMT to work correctly when masses of allocations happen before NMT is initialized;
* That pre-NMT-init phase starts when the libjvm is loaded and the C++ dynamic initialization runs, and ends when
* NMT is initialized after the VM parsed its arguments in CreateJavaVM.
*
* During that phase, NMT is not yet initialized fully; C-heap allocations are kept in a special lookup table to
* be able to tell them apart from post-NMT-init initializations later. For details, see nmtPreInit.hpp.
*
* The size of this table is limited, and its load factor affects lookup time; that lookup time is paid throughout
* the VM life for all os::free() calls, regardless if NMT is on or not. Therefore we are interested in keeping the
* number of pre-NMT-init allocations low.
*
* Normally, the VM allocates about 500 surviving allocations (allocations which are not freed before NMT initialization
* finishes). The number is mainly influenced by the number of VM arguments, since those get strdup'ed around.
* Therefore, the only direct way to test pre-NMT-init allocations is by feeding the VM a lot of arguments, and this is
* what this test does.
*
*/
/**
* @test id=normal-off
* @bug 8256844
* @library /test/lib
* @modules java.base/jdk.internal.misc
* java.management
* @run driver NMTInitializationTest normal off
*/
/**
* @test id=normal-detail
* @bug 8256844
* @library /test/lib
* @modules java.base/jdk.internal.misc
* java.management
* @run driver NMTInitializationTest normal detail
*/
/**
* @test id=default_long-off
* @bug 8256844
* @library /test/lib
* @modules java.base/jdk.internal.misc
* java.management
* @run driver NMTInitializationTest long off
*/
/**
* @test id=default_long-detail
* @bug 8256844
* @library /test/lib
* @modules java.base/jdk.internal.misc
* java.management
* @run driver NMTInitializationTest long detail
*/
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.process.ProcessTools;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class NMTInitializationTest {
final static boolean debug = true;
static String randomString() {
Random r = new Random();
int len = r.nextInt(100) + 100;
StringBuilder bld = new StringBuilder();
for (int i = 0; i < len; i ++) {
bld.append(r.nextInt(26) + 'A');
}
return bld.toString();
}
static Path createCommandFile(int numlines) throws Exception {
String fileName = "commands_" + numlines + ".txt";
FileWriter fileWriter = new FileWriter(fileName);
PrintWriter printWriter = new PrintWriter(fileWriter);
String line = "-XX:ErrorFile=" + randomString();
for (long i = 0; i < numlines / 2; i++) {
printWriter.println(line);
}
printWriter.close();
return Paths.get(fileName);
}
enum TestMode {
// call the VM with a normal-ish command line (long but not oudlandishly so). We expect the lookup table after
// initialization to be sparsely populated and sport very short chain lengths.
mode_normal(30, 5),
// call the VM with an outlandishly long command line. We expect the lookup table after initialization
// to be densely populated but hopefully evenly distributed.
mode_long(20000, 20);
final int num_command_line_args;
final int expected_max_chain_len;
TestMode(int num_command_line_args, int expected_max_chain_len) {
this.num_command_line_args = num_command_line_args;
this.expected_max_chain_len = expected_max_chain_len;
}
};
enum NMTMode {
off, summary, detail
};
public static void main(String args[]) throws Exception {
TestMode testMode = TestMode.valueOf("mode_" + args[0]);
NMTMode nmtMode = NMTMode.valueOf(args[1]);
System.out.println("Test mode: " + testMode + ", NMT mode: " + nmtMode);
Path commandLineFile = createCommandFile(testMode.num_command_line_args);
ArrayList<String> vmArgs = new ArrayList<>();
vmArgs.add("-Xlog:nmt");
vmArgs.add("-XX:NativeMemoryTracking=" + nmtMode.name());
vmArgs.add("-XX:+UnlockDiagnosticVMOptions");
vmArgs.add("-XX:+PrintNMTStatistics");
if (commandLineFile != null) {
vmArgs.add("@" + commandLineFile.getFileName());
}
vmArgs.add("-version");
ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(vmArgs);
OutputAnalyzer output = new OutputAnalyzer(pb.start());
if (debug) {
output.reportDiagnosticSummary();
}
output.shouldHaveExitValue(0);
// Now evaluate the output of -Xlog:nmt
// We expect something like:
// [0.001s][info][nmt] NMT initialized: detail
// [0.001s][info][nmt] Preinit state:
// [0.001s][info][nmt] entries: 342 (primary: 342, empties: 7577), sum bytes: 12996, longest chain length: 1
// [0.001s][info][nmt] pre-init mallocs: 375, pre-init reallocs: 6, pre-init frees: 33, pre-to-post reallocs: 4, pre-to-post frees: 0
output.shouldContain("NMT initialized: " + nmtMode.name());
output.shouldContain("Preinit state:");
String regex = ".*entries: (\\d+).*sum bytes: (\\d+).*longest chain length: (\\d+).*";
output.shouldMatch(regex);
String line = output.firstMatch(regex, 0);
if (line == null) {
throw new RuntimeException("expected: " + regex);
}
System.out.println(line);
Pattern p = Pattern.compile(regex);
Matcher mat = p.matcher(line);
mat.matches();
int entries = Integer.parseInt(mat.group(1));
int sum_bytes = Integer.parseInt(mat.group(2));
int longest_chain = Integer.parseInt(mat.group(3));
System.out.println("found: " + entries + " - " + sum_bytes + longest_chain + ".");
// Now we test the state of the internal lookup table, and through our assumptions about
// early pre-NMT-init allocations:
// The normal allocation count of surviving pre-init allocations is around 300-500, with the sum of allocated
// bytes of a few dozen KB. We check these boundaries (with a very generous overhead) to see if the numbers are
// way off. If they are, we may either have a leak or just a lot more allocations than we thought before
// NMT initialization. Both cases should be investigated. Even if the allocations are valid, too many of them
// stretches the limits of the lookup map, and therefore may cause slower lookup. We should then either change
// the coding, reducing the number of allocations. Or enlarge the lookup table.
// Apply some sensible assumptions
if (entries > testMode.num_command_line_args + 2000) { // Note: normal baseline is 400-500
throw new RuntimeException("Suspiciously high number of pre-init allocations.");
}
if (sum_bytes > 128 * 1024 * 1024) { // Note: normal baseline is ~30-40KB
throw new RuntimeException("Suspiciously high pre-init memory usage.");
}
if (longest_chain > testMode.expected_max_chain_len) {
// Under normal circumstances, load factor of the map should be about 0.1. With a good hash distribution, we
// should rarely see even a chain > 1. Warn if we see exceedingly long bucket chains, since this indicates
// either that the hash algorithm is inefficient or we have a bug somewhere.
throw new RuntimeException("Suspiciously long bucket chains in lookup table.");
}
// Finally, check that we see our final NMT report:
if (nmtMode != NMTMode.off) {
output.shouldContain("Native Memory Tracking:");
output.shouldMatch("Total: reserved=\\d+, committed=\\d+.*");
}
}
}

View File

@ -24,8 +24,7 @@
/*
* @test
* @bug 7124089 7131021 8042469 8066185 8074373 8258917
* @summary Checks for Launcher special flags, such as MacOSX specific flags,
* and JVM NativeMemoryTracking flags.
* @summary Checks for Launcher special flags, such as MacOSX specific flags.
* @modules jdk.compiler
* jdk.zipfs
* @compile -XDignore.symbol.file TestSpecialArgs.java EnvironmentVariables.java
@ -111,167 +110,6 @@ public class TestSpecialArgs extends TestHelper {
}
}
@Test
void testNativeMemoryTracking() {
final Map<String, String> envMap = new HashMap<>();
envMap.put("_JAVA_LAUNCHER_DEBUG", "true");
TestResult tr;
/*
* test argument : -XX:NativeMemoryTracking=value
* A JVM flag, comsumed by the JVM, but requiring launcher
* to set an environmental variable if and only if value is supplied.
* Test and order:
* 1) execute with valid parameter: -XX:NativeMemoryTracking=MyValue
* a) check for correct env variable name: "NMT_LEVEL_" + pid
* b) check that "MyValue" was found in local env.
* 2) execute with invalid parameter: -XX:NativeMemoryTracking=
* !) Won't find "NativeMemoryTracking:"
* Code to create env variable not executed.
* 3) execute with invalid parameter: -XX:NativeMemoryTracking
* !) Won't find "NativeMemoryTracking:"
* Code to create env variable not executed.
* 4) give and invalid value and check to make sure JVM commented
*/
String envVarPidString = "TRACER_MARKER: NativeMemoryTracking: env var is NMT_LEVEL_";
String NMT_Option_Value = "off";
String myClassName = "helloworld";
// === Run the tests ===
// ---Test 1a
tr = doExec(envMap, javaCmd, "-XX:NativeMemoryTracking=" + NMT_Option_Value,
"-version");
// get the PID from the env var we set for the JVM
String envVarPid = null;
for (String line : tr.testOutput) {
if (line.contains(envVarPidString)) {
int sindex = envVarPidString.length();
envVarPid = line.substring(sindex);
break;
}
}
// did we find envVarPid?
if (envVarPid == null) {
System.out.println(tr);
throw new RuntimeException("Error: failed to find env Var Pid in tracking info");
}
// we think we found the pid string. min test, not "".
if (envVarPid.length() < 1) {
System.out.println(tr);
throw new RuntimeException("Error: env Var Pid in tracking info is empty string");
}
// --- Test 1b
if (!tr.contains("NativeMemoryTracking: got value " + NMT_Option_Value)) {
System.out.println(tr);
throw new RuntimeException("Error: Valid param failed to set env variable");
}
// --- Test 2
tr = doExec(envMap, javaCmd, "-XX:NativeMemoryTracking=",
"-version");
if (tr.contains("NativeMemoryTracking:")) {
System.out.println(tr);
throw new RuntimeException("Error: invalid param caused env variable to be erroneously created");
}
if (!tr.contains("Syntax error, expecting -XX:NativeMemoryTracking=")) {
System.out.println(tr);
throw new RuntimeException("Error: invalid param not checked by JVM");
}
// --- Test 3
tr = doExec(envMap, javaCmd, "-XX:NativeMemoryTracking",
"-version");
if (tr.contains("NativeMemoryTracking:")) {
System.out.println(tr);
throw new RuntimeException("Error: invalid param caused env variable to be erroneously created");
}
if (!tr.contains("Syntax error, expecting -XX:NativeMemoryTracking=")) {
System.out.println(tr);
throw new RuntimeException("Error: invalid param not checked by JVM");
}
// --- Test 4
tr = doExec(envMap, javaCmd, "-XX:NativeMemoryTracking=BADVALUE",
"-version");
if (!tr.contains("expecting -XX:NativeMemoryTracking")) {
System.out.println(tr);
throw new RuntimeException("Error: invalid param did not get JVM Syntax error message");
}
}
@Test
void testNMArgumentProcessing() throws FileNotFoundException {
TestResult tr;
// the direct invokers of the VM
String options[] = {
"-version", "-fullversion", "-help", "-?", "-X"
};
for (String option : options) {
tr = doExec(javaCmd, option, "-XX:NativeMemoryTracking=summary");
checkTestResult(tr);
}
// create a test jar
File jarFile = new File("test.jar");
createJar(jarFile, "public static void main(String... args){}");
// ones that involve main-class of some sort
tr = doExec(javaCmd, "-jar", jarFile.getName(),
"-XX:NativeMemoryTracking=summary");
checkTestResult(tr);
tr = doExec(javaCmd, "-cp", jarFile.getName(), "Foo",
"-XX:NativeMemoryTracking=summary");
checkTestResult(tr);
final Map<String, String> envMap = new HashMap<>();
// checkwith CLASSPATH set ie. no -cp or -classpath
envMap.put("CLASSPATH", ".");
tr = doExec(envMap, javaCmd, "Foo", "-XX:NativeMemoryTracking=summary");
checkTestResult(tr);
// should accept with no warnings
tr = doExec(javaCmd, "-cp", jarFile.getName(),
"-XX:NativeMemoryTracking=summary", "Foo");
ensureNoWarnings(tr);
// should accept with no warnings
tr = doExec(javaCmd, "-classpath", jarFile.getName(),
"-XX:NativeMemoryTracking=summary", "Foo");
ensureNoWarnings(tr);
// make sure a missing class is handled correctly, because the class
// resolution is performed by the JVM.
tr = doExec(javaCmd, "AbsentClass", "-XX:NativeMemoryTracking=summary");
if (!tr.contains("Error: Could not find or load main class AbsentClass")) {
throw new RuntimeException("Test Fails");
}
// Make sure we handle correctly the module long form (--module=)
tr = doExec(javaCmd, "-XX:NativeMemoryTracking=summary", "--module=jdk.compiler/com.sun.tools.javac.Main", "--help");
ensureNoWarnings(tr);
}
@Test
void testNMTTools() throws FileNotFoundException {
TestResult tr;
// Tools (non-java launchers) should handle NTM (no "wrong launcher" warning).
tr = doExec(jarCmd, "-J-XX:NativeMemoryTracking=summary", "--help");
ensureNoWarnings(tr);
// And java terminal args (like "--help") don't stop "-J" args parsing.
tr = doExec(jarCmd, "--help", "-J-XX:NativeMemoryTracking=summary");
ensureNoWarnings(tr);
}
void ensureNoWarnings(TestResult tr) {
checkTestResult(tr);
if (tr.contains("warning: Native Memory Tracking")) {
System.err.println(tr.toString());
throw new RuntimeException("Test Fails");
}
}
void checkTestResult(TestResult tr) {
if (!tr.isOK()) {
System.err.println(tr.toString());