8256844: Make NMT late-initializable
Reviewed-by: coleenp, zgu
This commit is contained in:
parent
4df1bc4bc6
commit
eec64f5587
@ -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");
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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];
|
||||
};
|
||||
|
194
src/hotspot/share/services/nmtPreInit.cpp
Normal file
194
src/hotspot/share/services/nmtPreInit.cpp
Normal 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
|
359
src/hotspot/share/services/nmtPreInit.hpp
Normal file
359
src/hotspot/share/services/nmtPreInit.hpp
Normal 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
|
||||
|
@ -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>();
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
131
test/hotspot/gtest/nmt/test_nmtpreinit.cpp
Normal file
131
test/hotspot/gtest/nmt/test_nmtpreinit.cpp
Normal 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
|
136
test/hotspot/gtest/nmt/test_nmtpreinitmap.cpp
Normal file
136
test/hotspot/gtest/nmt/test_nmtpreinitmap.cpp
Normal 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
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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 \
|
||||
|
49
test/hotspot/jtreg/gtest/NMTGtests.java
Normal file
49
test/hotspot/jtreg/gtest/NMTGtests.java
Normal 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
|
||||
*/
|
73
test/hotspot/jtreg/runtime/NMT/NMTForOtherLaunchersTest.java
Normal file
73
test/hotspot/jtreg/runtime/NMT/NMTForOtherLaunchersTest.java
Normal 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+.*");
|
||||
}
|
||||
}
|
220
test/hotspot/jtreg/runtime/NMT/NMTInitializationTest.java
Normal file
220
test/hotspot/jtreg/runtime/NMT/NMTInitializationTest.java
Normal 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+.*");
|
||||
}
|
||||
}
|
||||
}
|
@ -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());
|
||||
|
Loading…
x
Reference in New Issue
Block a user