8293313: NMT: Rework MallocLimit

8293292: Remove MallocMaxTestWords

Reviewed-by: jsjolen, gziemski, lucy, mbaesken
This commit is contained in:
Thomas Stuefe 2023-02-16 16:14:05 +00:00
parent f558a6c599
commit 90e092280f
21 changed files with 764 additions and 368 deletions

View File

@ -1,5 +1,6 @@
/*
* Copyright (c) 2017, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2023 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
@ -29,7 +30,7 @@
#include "runtime/os.hpp"
#include "runtime/task.hpp"
#include "runtime/threadCritical.hpp"
#include "services/memTracker.hpp"
#include "services/memTracker.inline.hpp"
#include "utilities/align.hpp"
#include "utilities/debug.hpp"
#include "utilities/ostream.hpp"
@ -309,6 +310,10 @@ void* Arena::grow(size_t x, AllocFailType alloc_failmode) {
// (Note: all chunk sizes have to be 64-bit aligned)
size_t len = MAX2(ARENA_ALIGN(x), (size_t) Chunk::size);
if (MemTracker::check_exceeds_limit(x, _flags)) {
return nullptr;
}
Chunk *k = _chunk; // Get filled-up chunk address
_chunk = new (alloc_failmode, len) Chunk(len);

View File

@ -4263,78 +4263,3 @@ bool Arguments::copy_expand_pid(const char* src, size_t srclen,
*b = '\0';
return (p == src_end); // return false if not all of the source was copied
}
bool Arguments::parse_malloc_limit_size(const char* s, size_t* out) {
julong limit = 0;
Arguments::ArgsRange range = parse_memory_size(s, &limit, 1, SIZE_MAX);
switch (range) {
case ArgsRange::arg_in_range:
*out = (size_t)limit;
return true;
case ArgsRange::arg_too_big: // only possible on 32-bit
vm_exit_during_initialization("MallocLimit: too large", s);
break;
case ArgsRange::arg_too_small:
vm_exit_during_initialization("MallocLimit: limit must be > 0");
break;
default:
break;
}
return false;
}
// Helper for parse_malloc_limits
void Arguments::parse_single_category_limit(char* expression, size_t limits[mt_number_of_types]) {
// <category>:<limit>
char* colon = ::strchr(expression, ':');
if (colon == nullptr) {
vm_exit_during_initialization("MallocLimit: colon missing", expression);
}
*colon = '\0';
MEMFLAGS f = NMTUtil::string_to_flag(expression);
if (f == mtNone) {
vm_exit_during_initialization("MallocLimit: invalid nmt category", expression);
}
if (parse_malloc_limit_size(colon + 1, limits + (int)f) == false) {
vm_exit_during_initialization("Invalid MallocLimit size", colon + 1);
}
}
void Arguments::parse_malloc_limits(size_t* total_limit, size_t limits[mt_number_of_types]) {
// Reset output to 0
*total_limit = 0;
for (int i = 0; i < mt_number_of_types; i ++) {
limits[i] = 0;
}
// We are done if the option is not given.
if (MallocLimit == nullptr) {
return;
}
// Global form?
if (parse_malloc_limit_size(MallocLimit, total_limit)) {
return;
}
// No. So it must be in category-specific form: MallocLimit=<nmt category>:<size>[,<nmt category>:<size> ..]
char* copy = os::strdup(MallocLimit);
if (copy == nullptr) {
vm_exit_out_of_memory(strlen(MallocLimit), OOM_MALLOC_ERROR, "MallocLimit");
}
char* p = copy, *q;
do {
q = p;
p = ::strchr(q, ',');
if (p != nullptr) {
*p = '\0';
p ++;
}
parse_single_category_limit(q, limits);
} while (p != nullptr);
os::free(copy);
}

View File

@ -477,10 +477,6 @@ class Arguments : AllStatic {
char** base_archive_path,
char** top_archive_path) NOT_CDS_RETURN;
// Helpers for parse_malloc_limits
static bool parse_malloc_limit_size(const char* s, size_t* out);
static void parse_single_category_limit(char* expression, size_t limits[mt_number_of_types]);
public:
static int num_archives(const char* archive_path) NOT_CDS_RETURN_(0);
// Parses the arguments, first phase
@ -651,16 +647,6 @@ class Arguments : AllStatic {
assert(Arguments::is_dumping_archive(), "dump time only");
}
// Parse diagnostic NMT switch "MallocLimit" and return the found limits.
// 1) If option is not given, it will set all limits to 0 (aka "no limit").
// 2) If option is given in the global form (-XX:MallocLimit=<size>), it
// will return the size in *total_limit.
// 3) If option is given in its per-NMT-category form (-XX:MallocLimit=<category>:<size>[,<category>:<size>]),
// it will return all found limits in the limits array.
// 4) If option is malformed, it will exit the VM.
// For (2) and (3), limits not affected by the switch will be set to 0.
static void parse_malloc_limits(size_t* total_limit, size_t limits[mt_number_of_types]);
DEBUG_ONLY(static bool verify_special_jvm_flags(bool check_globals);)
};

View File

@ -1341,20 +1341,22 @@ const int ObjectAlignmentInBytes = 8;
notproduct(intx, ZombieALotInterval, 5, \
"Number of exits until ZombieALot kicks in") \
\
product(uintx, MallocMaxTestWords, 0, DIAGNOSTIC, \
"If non-zero, maximum number of words that malloc/realloc can " \
"allocate (for testing only)") \
range(0, max_uintx) \
\
product(ccstr, MallocLimit, nullptr, DIAGNOSTIC, \
"Limit malloc allocation size from VM. Reaching the limit will " \
"trigger a fatal error. This feature requires " \
"Limit malloc allocation size from VM. Reaching a limit will " \
"trigger an action (see flag). This feature requires " \
"NativeMemoryTracking=summary or NativeMemoryTracking=detail." \
"Usage:" \
"- MallocLimit=<size> to set a total limit. " \
"- MallocLimit=<NMT category>:<size>[,<NMT category>:<size>...] " \
" to set one or more category-specific limits." \
"Example: -XX:MallocLimit=compiler:500m") \
"\"-XX:MallocLimit=<size>[:<flag>]\" sets a total limit." \
"\"-XX:MallocLimit=<category>:<size>[:<flag>][,<category>:<size>[:<flag>] ...]\"" \
"sets one or more category-specific limits." \
"<flag> defines the action upon reaching the limit:" \
"\"fatal\": end VM with a fatal error at the allocation site" \
"\"oom\" : will mimic a native OOM" \
"If <flag> is omitted, \"fatal\" is the default." \
"Examples:\n" \
"-XX:MallocLimit=2g" \
"-XX:MallocLimit=2g:oom" \
"-XX:MallocLimit=compiler:200m:oom,code:100m") \
\
product(intx, TypeProfileWidth, 2, \
"Number of receiver types to record in call/cast profile") \

View File

@ -64,7 +64,7 @@
#include "services/attachListener.hpp"
#include "services/mallocTracker.hpp"
#include "services/mallocHeader.inline.hpp"
#include "services/memTracker.hpp"
#include "services/memTracker.inline.hpp"
#include "services/nmtPreInit.hpp"
#include "services/nmtCommon.hpp"
#include "services/threadService.hpp"
@ -87,8 +87,6 @@ int os::_processor_count = 0;
int os::_initial_active_processor_count = 0;
os::PageSizes os::_page_sizes;
static size_t cur_malloc_words = 0; // current size for MallocMaxTestWords
DEBUG_ONLY(bool os::_mutex_init_done = false;)
int os::snprintf(char* buf, size_t len, const char* fmt, ...) {
@ -607,23 +605,6 @@ char* os::strdup_check_oom(const char* str, MEMFLAGS flags) {
return p;
}
//
// This function supports testing of the malloc out of memory
// condition without really running the system out of memory.
//
static bool has_reached_max_malloc_test_peak(size_t alloc_size) {
if (MallocMaxTestWords > 0) {
size_t words = (alloc_size / BytesPerWord);
if ((cur_malloc_words + words) > MallocMaxTestWords) {
return true;
}
Atomic::add(&cur_malloc_words, words);
}
return false;
}
#ifdef ASSERT
static void check_crash_protection() {
assert(!ThreadCrashProtection::is_crash_protected(Thread::current_or_null()),
@ -658,8 +639,8 @@ void* os::malloc(size_t size, MEMFLAGS memflags, const NativeCallStack& stack) {
// we chose the latter.
size = MAX2((size_t)1, size);
// For the test flag -XX:MallocMaxTestWords
if (has_reached_max_malloc_test_peak(size)) {
// Observe MallocLimit
if (MemTracker::check_exceeds_limit(size, memflags)) {
return nullptr;
}
@ -710,11 +691,6 @@ void* os::realloc(void *memblock, size_t size, MEMFLAGS memflags, const NativeCa
// we chose the latter.
size = MAX2((size_t)1, size);
// For the test flag -XX:MallocMaxTestWords
if (has_reached_max_malloc_test_peak(size)) {
return nullptr;
}
if (MemTracker::enabled()) {
// NMT realloc handling
@ -725,12 +701,20 @@ void* os::realloc(void *memblock, size_t size, MEMFLAGS memflags, const NativeCa
return nullptr;
}
const size_t old_size = MallocTracker::malloc_header(memblock)->size();
// Observe MallocLimit
if ((size > old_size) && MemTracker::check_exceeds_limit(size - old_size, memflags)) {
return nullptr;
}
// Perform integrity checks on and mark the old block as dead *before* calling the real realloc(3) since it
// may invalidate the old block, including its header.
MallocHeader* header = MallocHeader::resolve_checked(memblock);
assert(memflags == header->flags(), "weird NMT flags mismatch (new:\"%s\" != old:\"%s\")\n",
NMTUtil::flag_to_name(memflags), NMTUtil::flag_to_name(header->flags()));
const MallocHeader::FreeInfo free_info = header->free_info();
header->mark_block_as_dead();
// the real realloc
@ -750,7 +734,7 @@ void* os::realloc(void *memblock, size_t size, MEMFLAGS memflags, const NativeCa
void* const new_inner_ptr = MemTracker::record_malloc(new_outer_ptr, size, memflags, stack);
#ifdef ASSERT
size_t old_size = free_info.size;
assert(old_size == free_info.size, "Sanity");
if (old_size < size) {
// We also zap the newly extended region.
::memset((char*)new_inner_ptr + old_size, uninitBlockPad, size - old_size);

View File

@ -0,0 +1,234 @@
/*
* Copyright (c) 2023 SAP SE. All rights reserved.
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*
*/
#include "precompiled.hpp"
#include "memory/allocation.hpp"
#include "runtime/java.hpp"
#include "runtime/globals.hpp"
#include "services/mallocLimit.hpp"
#include "services/nmtCommon.hpp"
#include "utilities/globalDefinitions.hpp"
#include "utilities/parseInteger.hpp"
#include "utilities/ostream.hpp"
MallocLimitSet MallocLimitHandler::_limits;
bool MallocLimitHandler::_have_limit = false;
static const char* const MODE_OOM = "oom";
static const char* const MODE_FATAL = "fatal";
static const char* mode_to_name(MallocLimitMode m) {
switch (m) {
case MallocLimitMode::trigger_fatal: return MODE_FATAL;
case MallocLimitMode::trigger_oom: return MODE_OOM;
default: ShouldNotReachHere();
};
return nullptr;
}
class ParserHelper {
// Start, end of parsed string.
const char* const _s;
const char* const _end;
// Current parse position.
const char* _p;
public:
ParserHelper(const char* s) : _s(s), _end(s + strlen(s)), _p(s) {}
bool eof() const { return _p >= _end; }
// Check if string at position matches a malloclimit_mode_t.
// Advance position on match.
bool match_mode_flag(MallocLimitMode* out) {
if (eof()) {
return false;
}
if (strncasecmp(_p, MODE_OOM, strlen(MODE_OOM)) == 0) {
*out = MallocLimitMode::trigger_oom;
_p += 3;
return true;
} else if (strncasecmp(_p, MODE_FATAL, strlen(MODE_FATAL)) == 0) {
*out = MallocLimitMode::trigger_fatal;
_p += 5;
return true;
}
return false;
}
// Check if string at position matches a category name.
// Advances position on match.
bool match_category(MEMFLAGS* out) {
if (eof()) {
return false;
}
const char* end = strchr(_p, ':');
if (end == nullptr) {
end = _end;
}
stringStream ss;
ss.print("%.*s", (int)(end - _p), _p);
MEMFLAGS f = NMTUtil::string_to_flag(ss.base());
if (f != mtNone) {
*out = f;
_p = end;
return true;
}
return false;
}
// Check if string at position matches a memory size (e.g. "100", "100g" etc).
// Advances position on match.
bool match_size(size_t* out) {
if (!eof()) {
char* remainder = nullptr;
if (parse_integer<size_t>(_p, &remainder, out)) {
assert(remainder > _p && remainder <= _end, "sanity");
_p = remainder;
return true;
}
}
return false;
}
// Check if char at pos matches c; return true and advance pos if so.
bool match_char(char c) {
if (!eof() && (*_p) == c) {
_p ++;
return true;
}
return false;
}
};
MallocLimitSet::MallocLimitSet() {
reset();
}
void MallocLimitSet::set_global_limit(size_t s, MallocLimitMode flag) {
_glob.sz = s; _glob.mode = flag;
}
void MallocLimitSet::set_category_limit(MEMFLAGS f, size_t s, MallocLimitMode flag) {
const int i = NMTUtil::flag_to_index(f);
_cat[i].sz = s; _cat[i].mode = flag;
}
void MallocLimitSet::reset() {
set_global_limit(0, MallocLimitMode::trigger_fatal);
_glob.sz = 0; _glob.mode = MallocLimitMode::trigger_fatal;
for (int i = 0; i < mt_number_of_types; i++) {
set_category_limit(NMTUtil::index_to_flag(i), 0, MallocLimitMode::trigger_fatal);
}
}
void MallocLimitSet::print_on(outputStream* st) const {
static const char* flagnames[] = { MODE_FATAL, MODE_OOM };
if (_glob.sz > 0) {
st->print_cr("MallocLimit: total limit: " PROPERFMT " (%s)", PROPERFMTARGS(_glob.sz),
mode_to_name(_glob.mode));
} else {
for (int i = 0; i < mt_number_of_types; i++) {
if (_cat[i].sz > 0) {
st->print_cr("MallocLimit: category \"%s\" limit: " PROPERFMT " (%s)",
NMTUtil::flag_to_enum_name(NMTUtil::index_to_flag(i)),
PROPERFMTARGS(_cat[i].sz), mode_to_name(_cat[i].mode));
}
}
}
}
bool MallocLimitSet::parse_malloclimit_option(const char* v, const char** err) {
#define BAIL_UNLESS(condition, errormessage) if (!(condition)) { *err = errormessage; return false; }
// Global form:
// MallocLimit=<size>[:flag]
// Category-specific form:
// MallocLimit=<category>:<size>[:flag][,<category>:<size>[:flag]...]
reset();
ParserHelper sst(v);
BAIL_UNLESS(!sst.eof(), "Empty string");
// Global form?
if (sst.match_size(&_glob.sz)) {
// Match optional mode flag (e.g. 1g:oom)
if (!sst.eof()) {
BAIL_UNLESS(sst.match_char(':'), "Expected colon");
BAIL_UNLESS(sst.match_mode_flag(&_glob.mode), "Expected flag");
}
}
// Category-specific form?
else {
while (!sst.eof()) {
MEMFLAGS f;
// Match category, followed by :
BAIL_UNLESS(sst.match_category(&f), "Expected category name");
BAIL_UNLESS(sst.match_char(':'), "Expected colon following category");
malloclimit* const modified_limit = &_cat[NMTUtil::flag_to_index(f)];
// Match size
BAIL_UNLESS(sst.match_size(&modified_limit->sz), "Expected size");
// Match optional flag
if (!sst.eof() && sst.match_char(':')) {
BAIL_UNLESS(sst.match_mode_flag(&modified_limit->mode), "Expected flag");
}
// More to come?
if (!sst.eof()) {
BAIL_UNLESS(sst.match_char(','), "Expected comma");
}
}
}
return true;
}
void MallocLimitHandler::initialize(const char* options) {
_have_limit = false;
if (options != nullptr && options[0] != '\0') {
const char* err = nullptr;
if (!_limits.parse_malloclimit_option(options, &err)) {
vm_exit_during_initialization("Failed to parse MallocLimit", err);
}
_have_limit = true;
}
}
void MallocLimitHandler::print_on(outputStream* st) {
if (have_limit()) {
_limits.print_on(st);
} else {
st->print_cr("MallocLimit: unset");
}
}

View File

@ -0,0 +1,77 @@
/*
* Copyright (c) 2023 SAP SE. All rights reserved.
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*
*/
#ifndef SHARE_SERVICES_MALLOCLIMIT_HPP
#define SHARE_SERVICES_MALLOCLIMIT_HPP
#include "memory/allocation.hpp"
#include "utilities/debug.hpp"
#include "utilities/globalDefinitions.hpp"
enum class MallocLimitMode {
trigger_fatal = 0,
trigger_oom = 1
};
struct malloclimit {
size_t sz; // Limit size
MallocLimitMode mode; // Behavior flags
};
class MallocLimitSet {
malloclimit _glob; // global limit
malloclimit _cat[mt_number_of_types]; // per-category limit
public:
MallocLimitSet();
void reset();
bool parse_malloclimit_option(const char* optionstring, const char** err);
void set_global_limit(size_t s, MallocLimitMode flag);
void set_category_limit(MEMFLAGS f, size_t s, MallocLimitMode flag);
const malloclimit* global_limit() const { return &_glob; }
const malloclimit* category_limit(MEMFLAGS f) const { return &_cat[(int)f]; }
void print_on(outputStream* st) const;
};
class MallocLimitHandler : public AllStatic {
static MallocLimitSet _limits;
static bool _have_limit; // shortcut
public:
static const malloclimit* global_limit() { return _limits.global_limit(); }
static const malloclimit* category_limit(MEMFLAGS f) { return _limits.category_limit(f); }
static void initialize(const char* options);
static void print_on(outputStream* st);
// True if there is any limit established
static bool have_limit() { return _have_limit; }
};
#endif // SHARE_SERVICES_MALLOCLIMIT_HPP

View File

@ -1,6 +1,6 @@
/*
* Copyright (c) 2014, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2021, 2022 SAP SE. All rights reserved.
* Copyright (c) 2021, 2023 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
@ -26,11 +26,14 @@
#include "precompiled.hpp"
#include "jvm_io.h"
#include "logging/log.hpp"
#include "logging/logStream.hpp"
#include "runtime/arguments.hpp"
#include "runtime/atomic.hpp"
#include "runtime/globals.hpp"
#include "runtime/os.hpp"
#include "runtime/safefetch.hpp"
#include "services/mallocHeader.inline.hpp"
#include "services/mallocLimit.hpp"
#include "services/mallocSiteTable.hpp"
#include "services/mallocTracker.hpp"
#include "services/memTracker.hpp"
@ -39,8 +42,6 @@
#include "utilities/vmError.hpp"
size_t MallocMemorySummary::_snapshot[CALC_OBJ_SIZE_IN_TYPE(MallocMemorySnapshot, size_t)];
size_t MallocMemorySummary::_limits_per_category[mt_number_of_types] = { 0 };
size_t MallocMemorySummary::_total_limit = 0;
#ifdef ASSERT
void MemoryCounter::update_peak(size_t size, size_t cnt) {
@ -80,59 +81,49 @@ void MallocMemorySummary::initialize() {
assert(sizeof(_snapshot) >= sizeof(MallocMemorySnapshot), "Sanity Check");
// Uses placement new operator to initialize static area.
::new ((void*)_snapshot)MallocMemorySnapshot();
initialize_limit_handling();
MallocLimitHandler::initialize(MallocLimit);
}
void MallocMemorySummary::initialize_limit_handling() {
// Initialize limit handling.
Arguments::parse_malloc_limits(&_total_limit, _limits_per_category);
bool MallocMemorySummary::total_limit_reached(size_t s, size_t so_far, const malloclimit* limit) {
if (_total_limit > 0) {
log_info(nmt)("MallocLimit: total limit: " SIZE_FORMAT "%s",
byte_size_in_proper_unit(_total_limit),
proper_unit_for_byte_size(_total_limit));
// Ignore the limit break during error reporting to prevent secondary errors.
if (VMError::is_error_reported()) {
return false;
}
#define FORMATTED \
"MallocLimit: reached global limit (triggering allocation size: " PROPERFMT ", allocated so far: " PROPERFMT ", limit: " PROPERFMT ") ", \
PROPERFMTARGS(s), PROPERFMTARGS(so_far), PROPERFMTARGS(limit->sz)
if (limit->mode == MallocLimitMode::trigger_fatal) {
fatal(FORMATTED);
} else {
for (int i = 0; i < mt_number_of_types; i ++) {
size_t catlim = _limits_per_category[i];
if (catlim > 0) {
log_info(nmt)("MallocLimit: category \"%s\" limit: " SIZE_FORMAT "%s",
NMTUtil::flag_to_name((MEMFLAGS)i),
byte_size_in_proper_unit(catlim),
proper_unit_for_byte_size(catlim));
}
}
log_warning(nmt)(FORMATTED);
}
#undef FORMATTED
return true;
}
void MallocMemorySummary::total_limit_reached(size_t size, size_t limit) {
// Assert in both debug and release, but allow error reporting to malloc beyond limits.
if (!VMError::is_error_reported()) {
fatal("MallocLimit: reached limit (size: " SIZE_FORMAT ", limit: " SIZE_FORMAT ") ",
size, limit);
}
}
bool MallocMemorySummary::category_limit_reached(MEMFLAGS f, size_t s, size_t so_far, const malloclimit* limit) {
void MallocMemorySummary::category_limit_reached(size_t size, size_t limit, MEMFLAGS flag) {
// Assert in both debug and release, but allow error reporting to malloc beyond limits.
if (!VMError::is_error_reported()) {
fatal("MallocLimit: category \"%s\" reached limit (size: " SIZE_FORMAT ", limit: " SIZE_FORMAT ") ",
NMTUtil::flag_to_name(flag), size, limit);
// Ignore the limit break during error reporting to prevent secondary errors.
if (VMError::is_error_reported()) {
return false;
}
}
void MallocMemorySummary::print_limits(outputStream* st) {
if (_total_limit != 0) {
st->print("MallocLimit: " SIZE_FORMAT, _total_limit);
#define FORMATTED \
"MallocLimit: reached category \"%s\" limit (triggering allocation size: " PROPERFMT ", allocated so far: " PROPERFMT ", limit: " PROPERFMT ") ", \
NMTUtil::flag_to_enum_name(f), PROPERFMTARGS(s), PROPERFMTARGS(so_far), PROPERFMTARGS(limit->sz)
if (limit->mode == MallocLimitMode::trigger_fatal) {
fatal(FORMATTED);
} else {
bool first = true;
for (int i = 0; i < mt_number_of_types; i ++) {
if (_limits_per_category[i] > 0) {
st->print("%s%s:" SIZE_FORMAT, (first ? "MallocLimit: " : ", "),
NMTUtil::flag_to_name((MEMFLAGS)i), _limits_per_category[i]);
first = false;
}
}
log_warning(nmt)(FORMATTED);
}
#undef FORMATTED
return true;
}
bool MallocTracker::initialize(NMT_TrackingLevel level) {

View File

@ -1,7 +1,6 @@
/*
* Copyright (c) 2014, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2021, 2022 SAP SE. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* Copyright (c) 2021, 2023 SAP SE. All rights reserved.
*
* 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
@ -34,6 +33,7 @@
#include "utilities/nativeCallStack.hpp"
class outputStream;
struct malloclimit;
/*
* This counter class counts memory allocation and deallocation,
@ -208,34 +208,15 @@ class MallocMemorySummary : AllStatic {
private:
// Reserve memory for placement of MallocMemorySnapshot object
static size_t _snapshot[CALC_OBJ_SIZE_IN_TYPE(MallocMemorySnapshot, size_t)];
static bool _have_limits;
// Malloc Limit handling (-XX:MallocLimit)
static size_t _limits_per_category[mt_number_of_types];
static size_t _total_limit;
// Called when a total limit break was detected.
// Will return true if the limit was handled, false if it was ignored.
static bool total_limit_reached(size_t s, size_t so_far, const malloclimit* limit);
static void initialize_limit_handling();
static void total_limit_reached(size_t size, size_t limit);
static void category_limit_reached(size_t size, size_t limit, MEMFLAGS flag);
static void check_limits_after_allocation(MEMFLAGS flag) {
// We can only either have a total limit or category specific limits,
// not both.
if (_total_limit != 0) {
size_t s = as_snapshot()->total();
if (s > _total_limit) {
total_limit_reached(s, _total_limit);
}
} else {
size_t per_cat_limit = _limits_per_category[(int)flag];
if (per_cat_limit > 0) {
const MallocMemory* mm = as_snapshot()->by_type(flag);
size_t s = mm->malloc_size() + mm->arena_size();
if (s > per_cat_limit) {
category_limit_reached(s, per_cat_limit, flag);
}
}
}
}
// Called when a total limit break was detected.
// Will return true if the limit was handled, false if it was ignored.
static bool category_limit_reached(MEMFLAGS f, size_t s, size_t so_far, const malloclimit* limit);
public:
static void initialize();
@ -243,7 +224,6 @@ class MallocMemorySummary : AllStatic {
static inline void record_malloc(size_t size, MEMFLAGS flag) {
as_snapshot()->by_type(flag)->record_malloc(size);
as_snapshot()->_all_mallocs.allocate(size);
check_limits_after_allocation(flag);
}
static inline void record_free(size_t size, MEMFLAGS flag) {
@ -261,7 +241,6 @@ class MallocMemorySummary : AllStatic {
static inline void record_arena_size_change(ssize_t size, MEMFLAGS flag) {
as_snapshot()->by_type(flag)->record_arena_size_change(size);
check_limits_after_allocation(flag);
}
static void snapshot(MallocMemorySnapshot* s) {
@ -278,7 +257,10 @@ class MallocMemorySummary : AllStatic {
return (MallocMemorySnapshot*)_snapshot;
}
static void print_limits(outputStream* st);
// MallocLimit: returns true if allocating s bytes on f would trigger
// either global or the category limit
static inline bool check_exceeds_limit(size_t s, MEMFLAGS f);
};
// Main class called from MemTracker to track malloc activities
@ -321,6 +303,10 @@ class MallocTracker : AllStatic {
MallocMemorySummary::record_arena_size_change(size, flags);
}
// MallocLimt: Given an allocation size s, check if mallocing this much
// under category f would hit either the global limit or the limit for category f.
static inline bool check_exceeds_limit(size_t s, MEMFLAGS f);
// Given a pointer, if it seems to point to the start of a valid malloced block,
// print the block. Note that since there is very low risk of memory looking
// accidentally like a valid malloc block header (canaries and all) this is not

View File

@ -0,0 +1,72 @@
/*
* Copyright (c) 2023 SAP SE. All rights reserved.
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*
*/
#ifndef SHARE_SERVICES_MALLOCTRACKER_INLINE_HPP
#define SHARE_SERVICES_MALLOCTRACKER_INLINE_HPP
#include "services/mallocLimit.hpp"
#include "services/mallocTracker.hpp"
#include "utilities/debug.hpp"
#include "utilities/globalDefinitions.hpp"
// Returns true if allocating s bytes on f would trigger either global or the category limit
inline bool MallocMemorySummary::check_exceeds_limit(size_t s, MEMFLAGS f) {
// Note: checks are ordered to have as little impact as possible on the standard code path,
// when MallocLimit is unset, resp. it is set but we have reached no limit yet.
// Somewhat expensive are:
// - as_snapshot()->total(), total malloc load (requires iteration over arena types)
// - VMError::is_error_reported() is a load from a volatile.
if (MallocLimitHandler::have_limit()) {
// Global Limit ?
const malloclimit* l = MallocLimitHandler::global_limit();
if (l->sz > 0) {
size_t so_far = as_snapshot()->total();
if ((so_far + s) > l->sz) { // hit the limit
return total_limit_reached(s, so_far, l);
}
} else {
// Category Limit?
l = MallocLimitHandler::category_limit(f);
if (l->sz > 0) {
const MallocMemory* mm = as_snapshot()->by_type(f);
size_t so_far = mm->malloc_size() + mm->arena_size();
if ((so_far + s) > l->sz) {
return category_limit_reached(f, s, so_far, l);
}
}
}
}
return false;
}
inline bool MallocTracker::check_exceeds_limit(size_t s, MEMFLAGS f) {
return MallocMemorySummary::check_exceeds_limit(s, f);
}
#endif // SHARE_SERVICES_MALLOCTRACKER_INLINE_HPP

View File

@ -1,6 +1,6 @@
/*
* Copyright (c) 2012, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2028, 2022 SAP SE. All rights reserved.
* Copyright (c) 2020, 2023 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
@ -34,6 +34,7 @@
#include "runtime/vmOperations.hpp"
#include "services/memBaseline.hpp"
#include "services/memReporter.hpp"
#include "services/mallocLimit.hpp"
#include "services/mallocTracker.hpp"
#include "services/memTracker.hpp"
#include "services/nmtCommon.hpp"
@ -90,7 +91,7 @@ void MemTracker::initialize() {
ls.print_cr("NMT initialized: %s", NMTUtil::tracking_level_to_string(_tracking_level));
ls.print_cr("Preinit state: ");
NMTPreInit::print_state(&ls);
ls.cr();
MallocLimitHandler::print_on(&ls);
}
}
@ -114,7 +115,7 @@ void MemTracker::error_report(outputStream* output) {
report(true, output, MemReporterBase::default_scale); // just print summary for error case.
output->print("Preinit state:");
NMTPreInit::print_state(output);
MallocMemorySummary::print_limits(output);
MallocLimitHandler::print_on(output);
}
}
@ -159,6 +160,6 @@ void MemTracker::tuning_statistics(outputStream* out) {
out->cr();
out->print_cr("Preinit state:");
NMTPreInit::print_state(out);
MallocMemorySummary::print_limits(out);
MallocLimitHandler::print_on(out);
out->cr();
}

View File

@ -230,6 +230,10 @@ class MemTracker : AllStatic {
static void tuning_statistics(outputStream* out);
// MallocLimt: Given an allocation size s, check if mallocing this much
// under category f would hit either the global limit or the limit for category f.
static inline bool check_exceeds_limit(size_t s, MEMFLAGS f);
private:
static void report(bool summary_only, outputStream* output, size_t scale);

View File

@ -0,0 +1,39 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023 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.
*
*/
#ifndef SHARE_SERVICES_MEMTRACKER_INLINE_HPP
#define SHARE_SERVICES_MEMTRACKER_INLINE_HPP
#include "services/mallocTracker.inline.hpp"
#include "services/memTracker.hpp"
inline bool MemTracker::check_exceeds_limit(size_t s, MEMFLAGS f) {
if (!enabled()) {
return false;
}
return MallocTracker::check_exceeds_limit(s, f);
}
#endif // SHARE_SERVICES_MEMTRACKER_INLINE_HPP

View File

@ -1,5 +1,7 @@
/*
* Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2014, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2021, 2023 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
@ -96,6 +98,11 @@ class NMTUtil : AllStatic {
return _strings[flag_to_index(flag)].human_readable;
}
// Map memory type to literalized enum name (e.g. "mtTest")
static const char* flag_to_enum_name(MEMFLAGS flag) {
return _strings[flag_to_index(flag)].enum_s;
}
// Map an index to memory type
static MEMFLAGS index_to_flag(int index) {
assert(flag_index_is_valid(index), "Invalid flag index (%d)", index);

View File

@ -0,0 +1,159 @@
/*
* Copyright (c) 2023 SAP SE. All rights reserved.
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include "precompiled.hpp"
#include "memory/allocation.hpp"
#include "runtime/os.hpp"
#include "services/mallocLimit.hpp"
#include "services/memTracker.hpp"
#include "services/nmtCommon.hpp"
#include "utilities/debug.hpp"
#include "utilities/globalDefinitions.hpp"
#include "utilities/ostream.hpp"
#include "testutils.hpp"
#include "unittest.hpp"
// Tests here just test the MallocLimit option parser. They are complemented
// by more extensive jtreg tests (runtime/NMT/TestMallocLimit.java)
static bool compare_limits(const malloclimit* a, const malloclimit* b) {
return a->sz == b->sz && a->mode == b->mode;
}
static bool compare_sets(const MallocLimitSet* a, const MallocLimitSet* b) {
if (compare_limits(a->global_limit(), b->global_limit())) {
for (int i = 0; i < mt_number_of_types; i++) {
if (!compare_limits(a->category_limit(NMTUtil::index_to_flag(i)),
b->category_limit(NMTUtil::index_to_flag(i)))) {
return false;
}
}
}
return true;
}
static void test(const char* s, const MallocLimitSet& expected) {
MallocLimitSet set;
const char* err;
EXPECT_TRUE(set.parse_malloclimit_option(s, &err)) << err;
EXPECT_TRUE(compare_sets(&set, &expected));
}
TEST(NMT, MallocLimitBasics) {
MallocLimitSet expected;
expected.set_global_limit(1 * G, MallocLimitMode::trigger_fatal);
test("1g", expected);
test("1024m", expected);
test("1048576k", expected);
test("1073741824", expected);
// Fatal is default, but can be specified explicitely
test("1g:fatal", expected);
expected.set_global_limit(2 * M, MallocLimitMode::trigger_oom);
test("2m:oom", expected);
test("2m:OOM", expected);
test("2048k:oom", expected);
}
TEST(NMT, MallocLimitPerCategory) {
MallocLimitSet expected;
expected.set_category_limit(mtMetaspace, 1 * M, MallocLimitMode::trigger_fatal);
test("metaspace:1m", expected);
test("metaspace:1m:fatal", expected);
test("METASPACE:1m", expected);
expected.set_category_limit(mtCompiler, 2 * M, MallocLimitMode::trigger_oom);
expected.set_category_limit(mtThread, 3 * M, MallocLimitMode::trigger_oom);
expected.set_category_limit(mtThreadStack, 4 * M, MallocLimitMode::trigger_oom);
expected.set_category_limit(mtClass, 5 * M, MallocLimitMode::trigger_fatal);
expected.set_category_limit(mtClassShared, 6 * M, MallocLimitMode::trigger_fatal);
test("metaspace:1m,compiler:2m:oom,thread:3m:oom,threadstack:4m:oom,class:5m,classshared:6m", expected);
}
TEST(NMT, MallocLimitCategoryEnumNames) {
MallocLimitSet expected;
stringStream option;
for (int i = 0; i < mt_number_of_types; i++) {
MEMFLAGS f = NMTUtil::index_to_flag(i);
if (f != MEMFLAGS::mtNone) {
expected.set_category_limit(f, (i + 1) * M, MallocLimitMode::trigger_fatal);
option.print("%s%s:%dM", (i > 0 ? "," : ""), NMTUtil::flag_to_enum_name(f), i + 1);
}
}
test(option.base(), expected);
}
TEST(NMT, MallocLimitAllCategoriesHaveHumanReadableNames) {
MallocLimitSet expected;
stringStream option;
for (int i = 0; i < mt_number_of_types; i++) {
MEMFLAGS f = NMTUtil::index_to_flag(i);
if (f != MEMFLAGS::mtNone) {
expected.set_category_limit(f, (i + 1) * M, MallocLimitMode::trigger_fatal);
option.print("%s%s:%dM", (i > 0 ? "," : ""), NMTUtil::flag_to_name(f), i + 1);
}
}
test(option.base(), expected);
}
static void test_failing(const char* s) {
MallocLimitSet set;
const char* err;
ASSERT_FALSE(set.parse_malloclimit_option(s, &err));
}
TEST(NMT, MallocLimitBadOptions) {
test_failing("abcd");
test_failing("compiler:1g:");
test_failing("compiler:1g:oom:mtTest:asas:1m");
}
// Death tests.
// Majority of MallocLimit functional tests are done via jtreg test runtime/NMT/MallocLimitTest. Here, we just
// test that limits are triggered for specific APIs.
TEST_VM_FATAL_ERROR_MSG(NMT, MallocLimitDeathTestOnRealloc, ".*MallocLimit: reached category .mtTest. limit.*") {
// We fake the correct assert if NMT is off to make the test pass (there is no way to execute a death test conditionally)
if (!MemTracker::enabled()) {
fatal("Fake message please ignore: MallocLimit: reached category \"mtTest\" limit");
}
// the real test
MallocLimitHandler::initialize("test:100m:fatal");
char* p = (char*)os::malloc(2, mtTest);
p = (char*)os::realloc(p, 120 * M, mtTest);
}
TEST_VM_FATAL_ERROR_MSG(NMT, MallocLimitDeathTestOnStrDup, ".*MallocLimit: reached category .mtTest. limit.*") {
// We fake the correct assert if NMT is off to make the test pass (there is no way to execute a death test conditionally)
if (!MemTracker::enabled()) {
fatal("Fake message please ignore: MallocLimit: reached category \"mtTest\" limit");
}
// the real test
MallocLimitHandler::initialize("test:10m:fatal");
for (int i = 0; i < 100000; i++) {
char* p = os::strdup("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", mtTest);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2011, 2022, 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
@ -68,10 +68,13 @@ public class JsrRewriting {
output.shouldHaveExitValue(0);
// ======= execute the test
// We run the test with MallocLimit set to 768m in oom mode,
// in order to trigger and observe a fake os::malloc oom. This needs NMT.
pb = ProcessTools.createJavaProcessBuilder(
"-cp", ".",
"-XX:+UnlockDiagnosticVMOptions",
"-XX:MallocMaxTestWords=" + mallocMaxTestWords,
"-XX:NativeMemoryTracking=summary",
"-XX:MallocLimit=768m:oom",
className);
output = new OutputAnalyzer(pb.start());

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2011, 2022, 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
@ -54,11 +54,6 @@ public class OomWhileParsingRepeatedJsr {
String jarFile = System.getProperty("test.src") + "/testcase.jar";
String className = "OOMCrashClass1960_2";
// limit is 768MB in native words
int mallocMaxTestWords = (1024 * 1024 * 768 / 4);
if (Platform.is64bit())
mallocMaxTestWords = (mallocMaxTestWords / 2);
// ======= extract the test class
ProcessBuilder pb = new ProcessBuilder(new String[] {
JDKToolFinder.getJDKTool("jar"),
@ -67,10 +62,13 @@ public class OomWhileParsingRepeatedJsr {
output.shouldHaveExitValue(0);
// ======= execute the test
// We run the test with MallocLimit set to 768m in oom mode,
// in order to trigger and observe a fake os::malloc oom. This needs NMT.
pb = ProcessTools.createJavaProcessBuilder(
"-cp", ".",
"-XX:+UnlockDiagnosticVMOptions",
"-XX:MallocMaxTestWords=" + mallocMaxTestWords,
"-XX:NativeMemoryTracking=summary",
"-XX:MallocLimit=768m:oom",
className );
output = new OutputAnalyzer(pb.start());

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2022, 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
@ -219,11 +219,6 @@ public class TestOptionsWithRanges {
excludeTestMaxRange("JVMCIThreads");
excludeTestMaxRange("JVMCIHostThreads");
/*
* Exclude MallocMaxTestWords as it is expected to exit VM at small values (>=0)
*/
excludeTestMinRange("MallocMaxTestWords");
/*
* Exclude below options as their maximum value would consume too much memory
* and would affect other tests that run in parallel.

View File

@ -24,19 +24,35 @@
*/
/*
* @test id=global-limit
* @test id=global-limit-fatal
* @summary Verify -XX:MallocLimit with a global limit
* @modules java.base/jdk.internal.misc
* @library /test/lib
* @run driver MallocLimitTest global-limit
* @run driver MallocLimitTest global-limit-fatal
*/
/*
* @test id=compiler-limit
* @test id=global-limit-oom
* @summary Verify -XX:MallocLimit with a global limit
* @modules java.base/jdk.internal.misc
* @library /test/lib
* @run driver MallocLimitTest global-limit-oom
*/
/*
* @test id=compiler-limit-fatal
* @summary Verify -XX:MallocLimit with a compiler-specific limit (for "mtCompiler" category)
* @modules java.base/jdk.internal.misc
* @library /test/lib
* @run driver MallocLimitTest compiler-limit
* @run driver MallocLimitTest compiler-limit-fatal
*/
/*
* @test id=compiler-limit-oom
* @summary Verify -XX:MallocLimit with a compiler-specific limit (for "mtCompiler" category)
* @modules java.base/jdk.internal.misc
* @library /test/lib
* @run driver MallocLimitTest compiler-limit-oom
*/
/*
@ -47,22 +63,6 @@
* @run driver MallocLimitTest multi-limit
*/
/*
* @test id=valid-settings
* @summary Verify -XX:MallocLimit rejects invalid settings
* @modules java.base/jdk.internal.misc
* @library /test/lib
* @run driver MallocLimitTest valid-settings
*/
/*
* @test id=invalid-settings
* @summary Verify -XX:MallocLimit rejects invalid settings
* @modules java.base/jdk.internal.misc
* @library /test/lib
* @run driver MallocLimitTest invalid-settings
*/
/*
* @test id=limit-without-nmt
* @summary Verify that the VM warns if -XX:MallocLimit is given but NMT is disabled
@ -96,126 +96,48 @@ public class MallocLimitTest {
return pb;
}
private static void testGlobalLimit() throws IOException {
long smallMemorySize = 1024*1024; // 1m
ProcessBuilder pb = processBuilderWithSetting("-XX:MallocLimit=" + smallMemorySize);
private static void testGlobalLimitFatal() throws IOException {
ProcessBuilder pb = processBuilderWithSetting("-XX:MallocLimit=1m");
OutputAnalyzer output = new OutputAnalyzer(pb.start());
output.shouldNotHaveExitValue(0);
output.shouldContain("[nmt] MallocLimit: total limit: 1024K"); // printed by byte_size_in_proper_unit()
String s = output.firstMatch(".*MallocLimit: reached limit \\(size: (\\d+), limit: " + smallMemorySize + "\\).*", 1);
Asserts.assertNotNull(s);
long size = Long.parseLong(s);
Asserts.assertGreaterThan(size, smallMemorySize);
output.shouldContain("[nmt] MallocLimit: total limit: 1024K (fatal)");
output.shouldMatch("# fatal error: MallocLimit: reached global limit \\(triggering allocation size: \\d+[BKM], allocated so far: \\d+[BKM], limit: 1024K\\)");
}
private static void testCompilerLimit() throws IOException {
// Here, we count on the VM, running with -Xcomp and with 1m of arena space allowed, will start a compilation
// and then trip over the limit.
// If limit is too small, Compiler stops too early and we won't get a Retry file (see below, we check that).
// If limit is too large, we may not trigger it for java -version.
// 1m seems to work out fine.
long smallMemorySize = 1024*1024; // 1m
ProcessBuilder pb = processBuilderWithSetting("-XX:MallocLimit=compiler:" + smallMemorySize,
"-Xcomp" // make sure we hit the compiler category limit
);
private static void testGlobalLimitOOM() throws IOException {
ProcessBuilder pb = processBuilderWithSetting("-XX:MallocLimit=1m:oom");
OutputAnalyzer output = new OutputAnalyzer(pb.start());
output.shouldNotHaveExitValue(0);
output.shouldContain("[nmt] MallocLimit: category \"Compiler\" limit: 1024K"); // printed by byte_size_in_proper_unit
String s = output.firstMatch(".*MallocLimit: category \"Compiler\" reached limit \\(size: (\\d+), limit: " + smallMemorySize + "\\).*", 1);
Asserts.assertNotNull(s);
long size = Long.parseLong(s);
output.shouldContain("Compiler replay data is saved as");
Asserts.assertGreaterThan(size, smallMemorySize);
output.shouldContain("[nmt] MallocLimit: total limit: 1024K (oom)");
output.shouldMatch(".*\\[warning\\]\\[nmt\\] MallocLimit: reached global limit \\(triggering allocation size: \\d+[BKM], allocated so far: \\d+[BKM], limit: 1024K\\)");
// The rest is fuzzy. We may get SIGSEGV or a native OOM message, depending on how the failing allocation was handled.
}
private static void testCompilerLimitFatal() throws IOException {
ProcessBuilder pb = processBuilderWithSetting("-XX:MallocLimit=compiler:1234k", "-Xcomp");
OutputAnalyzer output = new OutputAnalyzer(pb.start());
output.shouldNotHaveExitValue(0);
output.shouldContain("[nmt] MallocLimit: category \"mtCompiler\" limit: 1234K (fatal)");
output.shouldMatch("# fatal error: MallocLimit: reached category \"mtCompiler\" limit \\(triggering allocation size: \\d+[BKM], allocated so far: \\d+[BKM], limit: 1234K\\)");
}
private static void testCompilerLimitOOM() throws IOException {
ProcessBuilder pb = processBuilderWithSetting("-XX:MallocLimit=compiler:1234k:oom", "-Xcomp");
OutputAnalyzer output = new OutputAnalyzer(pb.start());
output.shouldNotHaveExitValue(0);
output.shouldContain("[nmt] MallocLimit: category \"mtCompiler\" limit: 1234K (oom)");
output.shouldMatch(".*\\[warning\\]\\[nmt\\] MallocLimit: reached category \"mtCompiler\" limit \\(triggering allocation size: \\d+[BKM], allocated so far: \\d+[BKM], limit: 1234K\\)");
// The rest is fuzzy. We may get SIGSEGV or a native OOM message, depending on how the failing allocation was handled.
}
private static void testMultiLimit() throws IOException {
long smallMemorySize = 1024; // 1k
ProcessBuilder pb = processBuilderWithSetting("-XX:MallocLimit=mtOther:2g,compiler:1g,internal:" + smallMemorySize);
ProcessBuilder pb = processBuilderWithSetting("-XX:MallocLimit=other:2g,compiler:1g:oom,internal:1k");
OutputAnalyzer output = new OutputAnalyzer(pb.start());
output.shouldNotHaveExitValue(0);
output.shouldContain("[nmt] MallocLimit: category \"Compiler\" limit: 1024M");
output.shouldContain("[nmt] MallocLimit: category \"Internal\" limit: 1024B");
output.shouldContain("[nmt] MallocLimit: category \"Other\" limit: 2048M");
String s = output.firstMatch(".*MallocLimit: category \"Internal\" reached limit \\(size: (\\d+), limit: " + smallMemorySize + "\\).*", 1);
long size = Long.parseLong(s);
Asserts.assertGreaterThan(size, smallMemorySize);
}
private static void testValidSetting(String setting, String... expected_output) throws IOException {
ProcessBuilder pb = processBuilderWithSetting("-XX:MallocLimit=" + setting);
OutputAnalyzer output = new OutputAnalyzer(pb.start());
output.shouldHaveExitValue(0);
for (String expected : expected_output) {
output.shouldContain(expected);
}
}
private static void testValidSettings() throws IOException {
// Test a number of valid settings.
testValidSetting(
"2097152k",
"[nmt] MallocLimit: total limit: 2048M",
"[nmt] NMT initialized: summary"
);
testValidSetting(
"gc:1234567891,mtInternal:987654321,Object Monitors:1g",
"[nmt] MallocLimit: category \"GC\" limit: 1177M",
"[nmt] MallocLimit: category \"Internal\" limit: 941M",
"[nmt] MallocLimit: category \"Object Monitors\" limit: 1024M",
"[nmt] NMT initialized: summary"
);
// Set all categories individually:
testValidSetting(
"JavaHeap:1024m,Class:1025m,Thread:1026m,ThreadStack:1027m,Code:1028m,GC:1029m,GCCardSet:1030m,Compiler:1031m,JVMCI:1032m," +
"Internal:1033m,Other:1034m,Symbol:1035m,NMT:1036m,ClassShared:1037m,Chunk:1038m,Test:1039m,Tracing:1040m,Logging:1041m," +
"Statistics:1042m,Arguments:1043m,Module:1044m,Safepoint:1045m,Synchronizer:1046m,Serviceability:1047m,Metaspace:1048m,StringDedup:1049m,ObjectMonitor:1050m",
"[nmt] MallocLimit: category \"Java Heap\" limit: 1024M",
"[nmt] MallocLimit: category \"Class\" limit: 1025M",
"[nmt] MallocLimit: category \"Thread\" limit: 1026M",
"[nmt] MallocLimit: category \"Thread Stack\" limit: 1027M",
"[nmt] MallocLimit: category \"Code\" limit: 1028M",
"[nmt] MallocLimit: category \"GC\" limit: 1029M",
"[nmt] MallocLimit: category \"GCCardSet\" limit: 1030M",
"[nmt] MallocLimit: category \"Compiler\" limit: 1031M",
"[nmt] MallocLimit: category \"JVMCI\" limit: 1032M",
"[nmt] MallocLimit: category \"Internal\" limit: 1033M",
"[nmt] MallocLimit: category \"Other\" limit: 1034M",
"[nmt] MallocLimit: category \"Symbol\" limit: 1035M",
"[nmt] MallocLimit: category \"Native Memory Tracking\" limit: 1036M",
"[nmt] MallocLimit: category \"Shared class space\" limit: 1037M",
"[nmt] MallocLimit: category \"Arena Chunk\" limit: 1038M",
"[nmt] MallocLimit: category \"Test\" limit: 1039M",
"[nmt] MallocLimit: category \"Tracing\" limit: 1040M",
"[nmt] MallocLimit: category \"Logging\" limit: 1041M",
"[nmt] MallocLimit: category \"Statistics\" limit: 1042M",
"[nmt] MallocLimit: category \"Arguments\" limit: 1043M",
"[nmt] MallocLimit: category \"Module\" limit: 1044M",
"[nmt] MallocLimit: category \"Safepoint\" limit: 1045M",
"[nmt] MallocLimit: category \"Synchronization\" limit: 1046M",
"[nmt] MallocLimit: category \"Serviceability\" limit: 1047M",
"[nmt] MallocLimit: category \"Metaspace\" limit: 1048M",
"[nmt] MallocLimit: category \"String Deduplication\" limit: 1049M",
"[nmt] MallocLimit: category \"Object Monitors\" limit: 1050M",
"[nmt] NMT initialized: summary"
);
}
private static void testInvalidSetting(String setting, String expected_error) throws IOException {
ProcessBuilder pb = processBuilderWithSetting("-XX:MallocLimit=" + setting);
OutputAnalyzer output = new OutputAnalyzer(pb.start());
output.reportDiagnosticSummary();
output.shouldNotHaveExitValue(0);
output.shouldContain(expected_error);
}
private static void testInvalidSettings() throws IOException {
// Test a number of invalid settings the parser should catch. VM should abort in initialization.
testInvalidSetting("gc", "MallocLimit: colon missing: gc");
testInvalidSetting("gc:abc", "Invalid MallocLimit size: abc");
testInvalidSetting("abcd:10m", "MallocLimit: invalid nmt category: abcd");
testInvalidSetting("nmt:100m,abcd:10m", "MallocLimit: invalid nmt category: abcd");
testInvalidSetting("0", "MallocLimit: limit must be > 0");
testInvalidSetting("GC:0", "MallocLimit: limit must be > 0");
output.shouldContain("[nmt] MallocLimit: category \"mtCompiler\" limit: 1024M (oom)");
output.shouldContain("[nmt] MallocLimit: category \"mtInternal\" limit: 1024B (fatal)");
output.shouldContain("[nmt] MallocLimit: category \"mtOther\" limit: 2048M (fatal)");
output.shouldMatch("# fatal error: MallocLimit: reached category \"mtInternal\" limit \\(triggering allocation size: \\d+[BKM], allocated so far: \\d+[BKM], limit: 1024B\\)");
}
private static void testLimitWithoutNmt() throws IOException {
@ -229,16 +151,16 @@ public class MallocLimitTest {
public static void main(String args[]) throws Exception {
if (args[0].equals("global-limit")) {
testGlobalLimit();
} else if (args[0].equals("compiler-limit")) {
testCompilerLimit();
if (args[0].equals("global-limit-fatal")) {
testGlobalLimitFatal();
} else if (args[0].equals("global-limit-oom")) {
testGlobalLimitOOM();
} else if (args[0].equals("compiler-limit-fatal")) {
testCompilerLimitFatal();
} else if (args[0].equals("compiler-limit-oom")) {
testCompilerLimitOOM();
} else if (args[0].equals("multi-limit")) {
testMultiLimit();
} else if (args[0].equals("valid-settings")) {
testValidSettings();
} else if (args[0].equals("invalid-settings")) {
testInvalidSettings();
} else if (args[0].equals("limit-without-nmt")) {
testLimitWithoutNmt();
} else {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2022, 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
@ -21,6 +21,9 @@
* questions.
*/
// Note: we run the test with MallocLimit for the "other" category set to 100m (oom mode),
// in order to trigger and observe a fake os::malloc oom. This needs NMT.
/*
* @test
* @requires vm.compMode != "Xcomp"
@ -28,7 +31,7 @@
* @library /test/lib
* @modules java.base/jdk.internal.misc
* java.management
* @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:MallocMaxTestWords=100m AllocateMemory
* @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:NativeMemoryTracking=summary -XX:MallocLimit=other:100m:oom AllocateMemory
*/
import jdk.internal.misc.Unsafe;
@ -56,9 +59,9 @@ public class AllocateMemory {
}
// allocateMemory() should throw an OutOfMemoryError when the underlying malloc fails,
// we test this by limiting the malloc using -XX:MallocMaxTestWords
// since we start with -XX:MallocLimit
try {
address = unsafe.allocateMemory(100 * 1024 * 1024 * 8);
address = unsafe.allocateMemory(100 * 1024 * 1024);
throw new RuntimeException("Did not get expected OutOfMemoryError");
} catch (OutOfMemoryError e) {
// Expected

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2022, 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
@ -21,6 +21,9 @@
* questions.
*/
// Note: we run the test with MallocLimit for the "other" category set to 100m (oom mode),
// in order to trigger and observe a fake os::malloc oom. This needs NMT.
/*
* @test
* @requires vm.compMode != "Xcomp"
@ -28,7 +31,7 @@
* @library /test/lib
* @modules java.base/jdk.internal.misc
* java.management
* @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:MallocMaxTestWords=100m Reallocate
* @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:NativeMemoryTracking=summary -XX:MallocLimit=other:100m:oom Reallocate
*/
import jdk.internal.misc.Unsafe;
@ -59,7 +62,7 @@ public class Reallocate {
// Make sure we can throw an OOME when we fail to reallocate due to OOM
try {
unsafe.reallocateMemory(address, 100 * 1024 * 1024 * 8);
unsafe.reallocateMemory(address, 100 * 1024 * 1024);
} catch (OutOfMemoryError e) {
// Expected
return;