8293313: NMT: Rework MallocLimit
8293292: Remove MallocMaxTestWords Reviewed-by: jsjolen, gziemski, lucy, mbaesken
This commit is contained in:
parent
f558a6c599
commit
90e092280f
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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);)
|
||||
};
|
||||
|
||||
|
@ -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") \
|
||||
|
@ -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);
|
||||
|
234
src/hotspot/share/services/mallocLimit.cpp
Normal file
234
src/hotspot/share/services/mallocLimit.cpp
Normal 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");
|
||||
}
|
||||
}
|
||||
|
77
src/hotspot/share/services/mallocLimit.hpp
Normal file
77
src/hotspot/share/services/mallocLimit.hpp
Normal 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
|
@ -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) {
|
||||
|
@ -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
|
||||
|
72
src/hotspot/share/services/mallocTracker.inline.hpp
Normal file
72
src/hotspot/share/services/mallocTracker.inline.hpp
Normal 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
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
39
src/hotspot/share/services/memTracker.inline.hpp
Normal file
39
src/hotspot/share/services/memTracker.inline.hpp
Normal 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
|
@ -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);
|
||||
|
159
test/hotspot/gtest/nmt/test_nmt_malloclimit.cpp
Normal file
159
test/hotspot/gtest/nmt/test_nmt_malloclimit.cpp
Normal 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);
|
||||
}
|
||||
}
|
@ -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());
|
||||
|
@ -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());
|
||||
|
@ -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.
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user