diff --git a/src/hotspot/share/memory/arena.cpp b/src/hotspot/share/memory/arena.cpp index bac7cc989e2..53bfec47c81 100644 --- a/src/hotspot/share/memory/arena.cpp +++ b/src/hotspot/share/memory/arena.cpp @@ -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); diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp index e93a36c630d..71637c0dbc9 100644 --- a/src/hotspot/share/runtime/arguments.cpp +++ b/src/hotspot/share/runtime/arguments.cpp @@ -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]) { - // : - 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=:[,: ..] - 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); - -} diff --git a/src/hotspot/share/runtime/arguments.hpp b/src/hotspot/share/runtime/arguments.hpp index 46ac86c2614..d4dc4b9ab84 100644 --- a/src/hotspot/share/runtime/arguments.hpp +++ b/src/hotspot/share/runtime/arguments.hpp @@ -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=), it - // will return the size in *total_limit. - // 3) If option is given in its per-NMT-category form (-XX:MallocLimit=:[,:]), - // 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);) }; diff --git a/src/hotspot/share/runtime/globals.hpp b/src/hotspot/share/runtime/globals.hpp index 92699b2ae0d..23592fde1a1 100644 --- a/src/hotspot/share/runtime/globals.hpp +++ b/src/hotspot/share/runtime/globals.hpp @@ -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= to set a total limit. " \ - "- MallocLimit=:[,:...] " \ - " to set one or more category-specific limits." \ - "Example: -XX:MallocLimit=compiler:500m") \ + "\"-XX:MallocLimit=[:]\" sets a total limit." \ + "\"-XX:MallocLimit=:[:][,:[:] ...]\"" \ + "sets one or more category-specific limits." \ + " 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 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") \ diff --git a/src/hotspot/share/runtime/os.cpp b/src/hotspot/share/runtime/os.cpp index eb43fde5d71..9eb808cf814 100644 --- a/src/hotspot/share/runtime/os.cpp +++ b/src/hotspot/share/runtime/os.cpp @@ -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); diff --git a/src/hotspot/share/services/mallocLimit.cpp b/src/hotspot/share/services/mallocLimit.cpp new file mode 100644 index 00000000000..4f96bb582f2 --- /dev/null +++ b/src/hotspot/share/services/mallocLimit.cpp @@ -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(_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=[:flag] + + // Category-specific form: + // MallocLimit=:[:flag][,:[: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"); + } +} + diff --git a/src/hotspot/share/services/mallocLimit.hpp b/src/hotspot/share/services/mallocLimit.hpp new file mode 100644 index 00000000000..3e5e9347629 --- /dev/null +++ b/src/hotspot/share/services/mallocLimit.hpp @@ -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 diff --git a/src/hotspot/share/services/mallocTracker.cpp b/src/hotspot/share/services/mallocTracker.cpp index 63a0d63d23c..7277fba133c 100644 --- a/src/hotspot/share/services/mallocTracker.cpp +++ b/src/hotspot/share/services/mallocTracker.cpp @@ -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) { diff --git a/src/hotspot/share/services/mallocTracker.hpp b/src/hotspot/share/services/mallocTracker.hpp index fa32e5b5294..461b73b35ee 100644 --- a/src/hotspot/share/services/mallocTracker.hpp +++ b/src/hotspot/share/services/mallocTracker.hpp @@ -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 diff --git a/src/hotspot/share/services/mallocTracker.inline.hpp b/src/hotspot/share/services/mallocTracker.inline.hpp new file mode 100644 index 00000000000..75eaf7c1054 --- /dev/null +++ b/src/hotspot/share/services/mallocTracker.inline.hpp @@ -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 diff --git a/src/hotspot/share/services/memTracker.cpp b/src/hotspot/share/services/memTracker.cpp index f6f86387cba..8c8f7be5747 100644 --- a/src/hotspot/share/services/memTracker.cpp +++ b/src/hotspot/share/services/memTracker.cpp @@ -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(); } diff --git a/src/hotspot/share/services/memTracker.hpp b/src/hotspot/share/services/memTracker.hpp index 1cd57325f13..e9f2ea0bfbd 100644 --- a/src/hotspot/share/services/memTracker.hpp +++ b/src/hotspot/share/services/memTracker.hpp @@ -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); diff --git a/src/hotspot/share/services/memTracker.inline.hpp b/src/hotspot/share/services/memTracker.inline.hpp new file mode 100644 index 00000000000..6c08f1154d0 --- /dev/null +++ b/src/hotspot/share/services/memTracker.inline.hpp @@ -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 diff --git a/src/hotspot/share/services/nmtCommon.hpp b/src/hotspot/share/services/nmtCommon.hpp index 3180b82f083..71a48f656d0 100644 --- a/src/hotspot/share/services/nmtCommon.hpp +++ b/src/hotspot/share/services/nmtCommon.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); diff --git a/test/hotspot/gtest/nmt/test_nmt_malloclimit.cpp b/test/hotspot/gtest/nmt/test_nmt_malloclimit.cpp new file mode 100644 index 00000000000..ab235258756 --- /dev/null +++ b/test/hotspot/gtest/nmt/test_nmt_malloclimit.cpp @@ -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); + } +} diff --git a/test/hotspot/jtreg/runtime/ClassFile/JsrRewriting.java b/test/hotspot/jtreg/runtime/ClassFile/JsrRewriting.java index 92a9c74c276..4d2c74360c7 100644 --- a/test/hotspot/jtreg/runtime/ClassFile/JsrRewriting.java +++ b/test/hotspot/jtreg/runtime/ClassFile/JsrRewriting.java @@ -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()); diff --git a/test/hotspot/jtreg/runtime/ClassFile/OomWhileParsingRepeatedJsr.java b/test/hotspot/jtreg/runtime/ClassFile/OomWhileParsingRepeatedJsr.java index 3f1d04c3d6d..b82f03786ec 100644 --- a/test/hotspot/jtreg/runtime/ClassFile/OomWhileParsingRepeatedJsr.java +++ b/test/hotspot/jtreg/runtime/ClassFile/OomWhileParsingRepeatedJsr.java @@ -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()); diff --git a/test/hotspot/jtreg/runtime/CommandLine/OptionsValidation/TestOptionsWithRanges.java b/test/hotspot/jtreg/runtime/CommandLine/OptionsValidation/TestOptionsWithRanges.java index 72948c35641..ffa88a9d009 100644 --- a/test/hotspot/jtreg/runtime/CommandLine/OptionsValidation/TestOptionsWithRanges.java +++ b/test/hotspot/jtreg/runtime/CommandLine/OptionsValidation/TestOptionsWithRanges.java @@ -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. diff --git a/test/hotspot/jtreg/runtime/NMT/MallocLimitTest.java b/test/hotspot/jtreg/runtime/NMT/MallocLimitTest.java index 27f59e9d291..c321f91b719 100644 --- a/test/hotspot/jtreg/runtime/NMT/MallocLimitTest.java +++ b/test/hotspot/jtreg/runtime/NMT/MallocLimitTest.java @@ -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 { diff --git a/test/hotspot/jtreg/runtime/Unsafe/AllocateMemory.java b/test/hotspot/jtreg/runtime/Unsafe/AllocateMemory.java index 4d548d61cc4..7d8d33b1225 100644 --- a/test/hotspot/jtreg/runtime/Unsafe/AllocateMemory.java +++ b/test/hotspot/jtreg/runtime/Unsafe/AllocateMemory.java @@ -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 diff --git a/test/hotspot/jtreg/runtime/Unsafe/Reallocate.java b/test/hotspot/jtreg/runtime/Unsafe/Reallocate.java index 4381fa124bc..c15b931449d 100644 --- a/test/hotspot/jtreg/runtime/Unsafe/Reallocate.java +++ b/test/hotspot/jtreg/runtime/Unsafe/Reallocate.java @@ -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;