8318016: Per-compilation memory ceiling

Reviewed-by: roland, thartmann
This commit is contained in:
Thomas Stuefe 2023-11-01 08:12:39 +00:00
parent 2a76ad975c
commit 0461d9a7d6
17 changed files with 426 additions and 34 deletions

View File

@ -397,6 +397,7 @@ int Compilation::compile_java_method() {
PhaseTraceTime timeit(_t_buildIR);
build_hir();
}
CHECK_BAILOUT_(no_frame_size);
if (BailoutAfterHIR) {
BAILOUT_("Bailing out because of -XX:+BailoutAfterHIR", no_frame_size);
}
@ -446,13 +447,13 @@ void Compilation::install_code(int frame_size) {
void Compilation::compile_method() {
CompilationMemoryStatisticMark cmsm(env()->task()->directive());
{
PhaseTraceTime timeit(_t_setup);
// setup compilation
initialize();
CHECK_BAILOUT();
}
if (!method()->can_be_compiled()) {
@ -605,6 +606,9 @@ Compilation::Compilation(AbstractCompiler* compiler, ciEnv* env, ciMethod* metho
_cfg_printer_output = new CFGPrinterOutput(this);
}
#endif
CompilationMemoryStatisticMark cmsm(directive);
compile_method();
if (bailed_out()) {
_env->record_method_not_compilable(bailout_msg());

View File

@ -85,6 +85,7 @@ class Compilation: public StackObj {
bool _has_monitors; // Fastpath monitors detection for Continuations
bool _install_code;
const char* _bailout_msg;
bool _oom;
ExceptionInfoList* _exception_info_list;
ExceptionHandlerTable _exception_handler_table;
ImplicitExceptionTable _implicit_exception_table;
@ -203,6 +204,10 @@ class Compilation: public StackObj {
}
#endif // PRODUCT
// MemLimit handling
bool oom() const { return _oom; }
void set_oom() { _oom = true; }
// error handling
void bailout(const char* msg);
bool bailed_out() const { return _bailout_msg != nullptr; }

View File

@ -319,10 +319,10 @@ public:
// This is true if the compilation is not going to produce code.
// (It is reasonable to retry failed compilations.)
bool failing() { return _failure_reason != nullptr; }
bool failing() const { return _failure_reason != nullptr; }
// Reason this compilation is failing, such as "too many basic blocks".
const char* failure_reason() { return _failure_reason; }
const char* failure_reason() const { return _failure_reason; }
// Return state of appropriate compatibility
int compilable() { return _compilable; }

View File

@ -26,6 +26,9 @@
#include "precompiled.hpp"
#include "logging/log.hpp"
#include "logging/logStream.hpp"
#ifdef COMPILER1
#include "c1/c1_Compilation.hpp"
#endif
#include "compiler/abstractCompiler.hpp"
#include "compiler/compilationMemoryStatistic.hpp"
#include "compiler/compilerDirectives.hpp"
@ -42,15 +45,16 @@
#endif
#include "runtime/mutexLocker.hpp"
#include "runtime/os.hpp"
#include "utilities/debug.hpp"
#include "utilities/globalDefinitions.hpp"
#include "utilities/ostream.hpp"
#include "utilities/quickSort.hpp"
#include "utilities/resourceHash.hpp"
ArenaStatCounter::ArenaStatCounter() :
_current(0), _start(0), _peak(0),
_na(0), _ra(0),
_limit(0), _hit_limit(false),
_na_at_peak(0), _ra_at_peak(0), _live_nodes_at_peak(0)
{}
@ -58,8 +62,15 @@ size_t ArenaStatCounter::peak_since_start() const {
return _peak > _start ? _peak - _start : 0;
}
void ArenaStatCounter::start() {
void ArenaStatCounter::start(size_t limit) {
_peak = _start = _current;
_limit = limit;
_hit_limit = false;
}
void ArenaStatCounter::end(){
_limit = 0;
_hit_limit = false;
}
void ArenaStatCounter::update_c2_node_count() {
@ -104,6 +115,10 @@ bool ArenaStatCounter::account(ssize_t delta, int tag) {
_ra_at_peak = _ra;
update_c2_node_count();
rc = true;
// Did we hit the memory limit?
if (!_hit_limit && _limit > 0 && peak_since_start() > _limit) {
_hit_limit = true;
}
}
return rc;
}
@ -125,7 +140,8 @@ class FullMethodName {
public:
FullMethodName(Symbol* k, Symbol* m, Symbol* s) : _k(k), _m(m), _s(s) {}
FullMethodName(const Method* m) :
_k(m->klass_name()), _m(m->name()), _s(m->signature()) {};
FullMethodName(const FullMethodName& o) : _k(o._k), _m(o._m), _s(o._s) {}
void make_permanent() {
@ -173,13 +189,15 @@ class MemStatEntry : public CHeapObj<mtInternal> {
size_t _na_at_peak;
size_t _ra_at_peak;
unsigned _live_nodes_at_peak;
const char* _result;
public:
MemStatEntry(FullMethodName method)
: _method(method), _comptype(compiler_c1),
_time(0), _num_recomp(0), _thread(nullptr),
_total(0), _na_at_peak(0), _ra_at_peak(0), _live_nodes_at_peak(0) {
_total(0), _na_at_peak(0), _ra_at_peak(0), _live_nodes_at_peak(0),
_result(nullptr) {
}
void set_comptype(CompilerType comptype) { _comptype = comptype; }
@ -192,6 +210,8 @@ public:
void set_ra_at_peak(size_t n) { _ra_at_peak = n; }
void set_live_nodes_at_peak(unsigned n) { _live_nodes_at_peak = n; }
void set_result(const char* s) { _result = s; }
size_t total() const { return _total; }
static void print_legend(outputStream* st) {
@ -199,7 +219,8 @@ public:
st->print_cr(" total : memory allocated via arenas while compiling");
st->print_cr(" NA : ...how much in node arenas (if c2)");
st->print_cr(" RA : ...how much in resource areas");
st->print_cr(" #nodes : ...how many nodes (if c2)");
st->print_cr(" result : Result: 'ok' finished successfully, 'oom' hit memory limit, 'err' compilation failed");
st->print_cr(" #nodes : ...how many nodes (c2 only)");
st->print_cr(" time : time of last compilation (sec)");
st->print_cr(" type : compiler type");
st->print_cr(" #rc : how often recompiled");
@ -207,7 +228,7 @@ public:
}
static void print_header(outputStream* st) {
st->print_cr("total NA RA #nodes time type #rc thread method");
st->print_cr("total NA RA result #nodes time type #rc thread method");
}
void print_on(outputStream* st, bool human_readable) const {
@ -237,6 +258,10 @@ public:
}
col += 10; st->fill_to(col);
// result?
st->print("%s ", _result ? _result : "");
col += 8; st->fill_to(col);
// Number of Nodes when memory peaked
st->print("%u ", _live_nodes_at_peak);
col += 8; st->fill_to(col);
@ -281,7 +306,7 @@ public:
void add(const FullMethodName& fmn, CompilerType comptype,
size_t total, size_t na_at_peak, size_t ra_at_peak,
unsigned live_nodes_at_peak) {
unsigned live_nodes_at_peak, const char* result) {
assert_lock_strong(NMTCompilationCostHistory_lock);
MemStatEntry** pe = get(fmn);
@ -302,6 +327,7 @@ public:
e->set_na_at_peak(na_at_peak);
e->set_ra_at_peak(ra_at_peak);
e->set_live_nodes_at_peak(live_nodes_at_peak);
e->set_result(result);
}
// Returns a C-heap-allocated SortMe array containing all entries from the table,
@ -341,20 +367,21 @@ void CompilationMemoryStatistic::initialize() {
log_info(compilation, alloc)("Compilation memory statistic enabled");
}
void CompilationMemoryStatistic::on_start_compilation() {
void CompilationMemoryStatistic::on_start_compilation(const DirectiveSet* directive) {
assert(enabled(), "Not enabled?");
Thread::current()->as_Compiler_thread()->arena_stat()->start();
const size_t limit = directive->mem_limit();
Thread::current()->as_Compiler_thread()->arena_stat()->start(limit);
}
void CompilationMemoryStatistic::on_end_compilation() {
assert(enabled(), "Not enabled?");
ResourceMark rm;
CompilerThread* const th = Thread::current()->as_Compiler_thread();
const ArenaStatCounter* const arena_stat = th->arena_stat();
ArenaStatCounter* const arena_stat = th->arena_stat();
const CompilerType ct = th->task()->compiler()->type();
const Method* const m = th->task()->method();
FullMethodName fmn(m->klass_name(), m->name(), m->signature());
FullMethodName fmn(m);
fmn.make_permanent();
const DirectiveSet* directive = th->task()->directive();
@ -368,6 +395,20 @@ void CompilationMemoryStatistic::on_end_compilation() {
arena_stat->print_on(tty);
tty->cr();
}
// Store result
// For this to work, we must call on_end_compilation() at a point where
// Compile|Compilation already handed over the failure string to ciEnv,
// but ciEnv must still be alive.
const char* result = "ok"; // ok
const ciEnv* const env = th->env();
if (env) {
const char* const failure_reason = env->failure_reason();
if (failure_reason != nullptr) {
result = (failure_reason == failure_reason_memlimit()) ? "oom" : "err";
}
}
{
MutexLocker ml(NMTCompilationCostHistory_lock, Mutex::_no_safepoint_check_flag);
assert(_the_table != nullptr, "not initialized");
@ -376,14 +417,105 @@ void CompilationMemoryStatistic::on_end_compilation() {
arena_stat->peak_since_start(), // total
arena_stat->na_at_peak(),
arena_stat->ra_at_peak(),
arena_stat->live_nodes_at_peak());
arena_stat->live_nodes_at_peak(),
result);
}
arena_stat->end(); // reset things
}
static void inform_compilation_about_oom(CompilerType ct) {
// Inform C1 or C2 that an OOM happened. They will take delayed action
// and abort the compilation in progress. Note that this is not instantaneous,
// since the compiler has to actively bailout, which may take a while, during
// which memory usage may rise further.
//
// The mechanism differs slightly between C1 and C2:
// - With C1, we directly set the bailout string, which will cause C1 to
// bailout at the typical BAILOUT places.
// - With C2, the corresponding mechanism would be the failure string; but
// bailout paths in C2 are not complete and therefore it is dangerous to
// set the failure string at - for C2 - seemingly random places. Instead,
// upon OOM C2 sets the failure string next time it checks the node limit.
if (ciEnv::current() != nullptr) {
void* compiler_data = ciEnv::current()->compiler_data();
#ifdef COMPILER1
if (ct == compiler_c1) {
Compilation* C = static_cast<Compilation*>(compiler_data);
if (C != nullptr) {
C->bailout(CompilationMemoryStatistic::failure_reason_memlimit());
C->set_oom();
}
}
#endif
#ifdef COMPILER2
if (ct == compiler_c2) {
Compile* C = static_cast<Compile*>(compiler_data);
if (C != nullptr) {
C->set_oom();
}
}
#endif // COMPILER2
}
}
void CompilationMemoryStatistic::on_arena_change(ssize_t diff, const Arena* arena) {
assert(enabled(), "Not enabled?");
CompilerThread* const th = Thread::current()->as_Compiler_thread();
th->arena_stat()->account(diff, (int)arena->get_tag());
ArenaStatCounter* const arena_stat = th->arena_stat();
bool hit_limit_before = arena_stat->hit_limit();
if (arena_stat->account(diff, (int)arena->get_tag())) { // new peak?
// Limit handling
if (arena_stat->hit_limit()) {
char name[1024] = "";
bool print = false;
bool crash = false;
CompilerType ct = compiler_none;
// get some more info
const CompileTask* task = th->task();
if (task != nullptr) {
ct = task->compiler()->type();
const DirectiveSet* directive = task->directive();
print = directive->should_print_memstat();
crash = directive->should_crash_at_mem_limit();
const Method* m = th->task()->method();
if (m != nullptr) {
FullMethodName(m).as_C_string(name, sizeof(name));
}
}
char message[1024] = "";
// build up message if we need it later
if (print || crash) {
stringStream ss(message, sizeof(message));
if (ct != compiler_none && name[0] != '\0') {
ss.print("%s %s: ", compilertype2name(ct), name);
}
ss.print("Hit MemLimit %s (limit: %zu now: %zu)",
(hit_limit_before ? "again" : ""),
arena_stat->limit(), arena_stat->peak_since_start());
}
// log if needed
if (print) {
tty->print_raw(message);
tty->cr();
}
// Crash out if needed
if (crash) {
report_fatal(OOM_HOTSPOT_ARENA, __FILE__, __LINE__, "%s", message);
} else {
inform_compilation_about_oom(ct);
}
}
}
}
static inline ssize_t diff_entries_by_size(const MemStatEntry* e1, const MemStatEntry* e2) {
@ -438,10 +570,15 @@ void CompilationMemoryStatistic::print_all_by_size(outputStream* st, bool human_
FREE_C_HEAP_ARRAY(Entry, filtered);
}
const char* CompilationMemoryStatistic::failure_reason_memlimit() {
static const char* const s = "hit memory limit while compiling";
return s;
}
CompilationMemoryStatisticMark::CompilationMemoryStatisticMark(const DirectiveSet* directive)
: _active(directive->should_collect_memstat()) {
if (_active) {
CompilationMemoryStatistic::on_start_compilation();
CompilationMemoryStatistic::on_start_compilation(directive);
}
}
CompilationMemoryStatisticMark::~CompilationMemoryStatisticMark() {

View File

@ -47,6 +47,9 @@ class ArenaStatCounter : public CHeapObj<mtCompiler> {
size_t _na;
// Current bytes used for resource areas
size_t _ra;
// MemLimit handling
size_t _limit;
bool _hit_limit;
// Peak composition:
// Size of node arena when total peaked (c2 only)
@ -69,15 +72,20 @@ public:
size_t ra_at_peak() const { return _ra_at_peak; }
unsigned live_nodes_at_peak() const { return _live_nodes_at_peak; }
// Mark the start of a compilation.
void start();
// Mark the start and end of a compilation.
void start(size_t limit);
void end();
// Account an arena allocation or de-allocation.
// Returns true if new peak reached
bool account(ssize_t delta, int tag);
void set_live_nodes_at_peak(unsigned i) { _live_nodes_at_peak = i; }
void print_on(outputStream* st) const;
size_t limit() const { return _limit; }
bool hit_limit() const { return _hit_limit; }
};
class CompilationMemoryStatistic : public AllStatic {
@ -86,10 +94,16 @@ public:
static void initialize();
// true if CollectMemStat or PrintMemStat has been enabled for any method
static bool enabled() { return _enabled; }
static void on_start_compilation();
static void on_start_compilation(const DirectiveSet* directive);
// Called at end of compilation. Records the arena usage peak. Also takes over
// status information from ciEnv (compilation failed, oom'ed or went okay). ciEnv::_failure_reason
// must be set at this point (so place CompilationMemoryStatisticMark correctly).
static void on_end_compilation();
static void on_arena_change(ssize_t diff, const Arena* arena);
static void print_all_by_size(outputStream* st, bool human_readable, size_t minsize);
// For compilers
static const char* failure_reason_memlimit();
};
// RAII object to wrap one compilation

View File

@ -142,7 +142,7 @@ void CompileTask::initialize(int compile_id,
/**
* Returns the compiler for this task.
*/
AbstractCompiler* CompileTask::compiler() {
AbstractCompiler* CompileTask::compiler() const {
return CompileBroker::compiler(_comp_level);
}

View File

@ -180,7 +180,7 @@ class CompileTask : public CHeapObj<mtCompiler> {
int comp_level() { return _comp_level;}
void set_comp_level(int comp_level) { _comp_level = comp_level;}
AbstractCompiler* compiler();
AbstractCompiler* compiler() const;
CompileTask* select_for_compilation();
int num_inlined_bytecodes() const { return _num_inlined_bytecodes; }

View File

@ -203,13 +203,24 @@ bool DirectiveSet::is_c2(CompilerDirectives* directive) const {
}
bool DirectiveSet::should_collect_memstat() const {
return MemStatOption > 0;
// MemLimit requires the memory statistic to be active
return MemStatOption > 0 || MemLimitOption != 0;
}
bool DirectiveSet::should_print_memstat() const {
return MemStatOption == (uintx)MemStatAction::print;
}
size_t DirectiveSet::mem_limit() const {
return MemLimitOption < 0 ? -MemLimitOption : MemLimitOption;
}
bool DirectiveSet::should_crash_at_mem_limit() const {
// The sign encodes the action to be taken when reaching
// the memory limit (+ stop - crash)
return MemLimitOption < 0;
}
// In the list of Control/disabled intrinsics, the ID of the control intrinsics can separated:
// - by ',' (if -XX:Control/DisableIntrinsic is used once when invoking the VM) or
// - by '\n' (if -XX:Control/DisableIntrinsic is used multiple times when invoking the VM) or

View File

@ -41,6 +41,7 @@
cflags(BreakAtExecute, bool, false, BreakAtExecute) \
cflags(BreakAtCompile, bool, false, BreakAtCompile) \
cflags(Log, bool, LogCompilation, Unknown) \
cflags(MemLimit, intx, 0, MemLimit) \
cflags(MemStat, uintx, 0, MemStat) \
cflags(PrintAssembly, bool, PrintAssembly, PrintAssembly) \
cflags(PrintCompilation, bool, PrintCompilation, PrintCompilation) \
@ -150,6 +151,8 @@ public:
bool is_c2(CompilerDirectives* directive) const;
bool should_collect_memstat() const;
bool should_print_memstat() const;
size_t mem_limit() const;
bool should_crash_at_mem_limit() const; // true: crash false: stop compilation
typedef enum {
#define enum_of_flags(name, type, dvalue, cc_flag) name##Index,

View File

@ -39,6 +39,7 @@
#include "runtime/handles.inline.hpp"
#include "runtime/jniHandles.hpp"
#include "runtime/os.hpp"
#include "utilities/parseInteger.hpp"
static const char* optiontype_names[] = {
#define enum_of_types(type, name) name,
@ -459,7 +460,7 @@ bool CompilerOracle::should_print_methods() {
// Tells whether there are any methods to collect memory statistics for
bool CompilerOracle::should_collect_memstat() {
return has_command(CompileCommand::MemStat);
return has_command(CompileCommand::MemStat) || has_command(CompileCommand::MemLimit);
}
bool CompilerOracle::should_print_final_memstat_report() {
@ -634,6 +635,44 @@ void skip_comma(char* &line) {
}
}
static bool parseMemLimit(const char* line, intx& value, int& bytes_read, char* errorbuf, const int buf_size) {
// Format:
// "<memory size>['~' <suboption>]"
// <memory size> can have units, e.g. M
// <suboption> one of "crash" "stop", if omitted, "stop" is implied.
//
// Examples:
// -XX:CompileCommand='memlimit,*.*,20m'
// -XX:CompileCommand='memlimit,*.*,20m~stop'
// -XX:CompileCommand='memlimit,Option::toString,1m~crash'
//
// The resulting intx carries the size and whether we are to stop or crash:
// - neg. value means crash
// - pos. value (default) means stop
size_t s = 0;
char* end;
if (!parse_integer<size_t>(line, &end, &s)) {
jio_snprintf(errorbuf, buf_size, "MemLimit: invalid value");
}
bytes_read = (int)(end - line);
intx v = (intx)s;
if ((*end) != '\0') {
if (strncasecmp(end, "~crash", 6) == 0) {
v = -v;
bytes_read += 6;
} else if (strncasecmp(end, "~stop", 5) == 0) {
// ok, this is the default
bytes_read += 5;
} else {
jio_snprintf(errorbuf, buf_size, "MemLimit: invalid option");
return true;
}
}
value = v;
return true;
}
static bool parseEnumValueAsUintx(enum CompileCommand option, const char* line, uintx& value, int& bytes_read, char* errorbuf, const int buf_size) {
if (option == CompileCommand::MemStat) {
if (strncasecmp(line, "collect", 7) == 0) {
@ -659,7 +698,13 @@ static void scan_value(enum OptionType type, char* line, int& total_bytes_read,
total_bytes_read += skipped;
if (type == OptionType::Intx) {
intx value;
if (sscanf(line, "" INTX_FORMAT "%n", &value, &bytes_read) == 1) {
// Special handling for memlimit
bool success = (option == CompileCommand::MemLimit) && parseMemLimit(line, value, bytes_read, errorbuf, buf_size);
if (!success) {
// Is it a raw number?
success = sscanf(line, "" INTX_FORMAT "%n", &value, &bytes_read) == 1;
}
if (success) {
total_bytes_read += bytes_read;
line += bytes_read;
register_command(matcher, option, value);

View File

@ -57,6 +57,7 @@ class methodHandle;
option(Break, "break", Bool) \
option(BreakAtExecute, "BreakAtExecute", Bool) \
option(BreakAtCompile, "BreakAtCompile", Bool) \
option(MemLimit, "MemLimit", Intx) \
option(MemStat, "MemStat", Uintx) \
option(PrintAssembly, "PrintAssembly", Bool) \
option(PrintCompilation, "PrintCompilation", Bool) \

View File

@ -29,6 +29,7 @@
#include "classfile/javaClasses.hpp"
#include "code/exceptionHandlerTable.hpp"
#include "code/nmethod.hpp"
#include "compiler/compilationMemoryStatistic.hpp"
#include "compiler/compileBroker.hpp"
#include "compiler/compileLog.hpp"
#include "compiler/disassembler.hpp"
@ -661,6 +662,7 @@ Compile::Compile( ciEnv* ci_env, ciMethod* target, int osr_bci,
_vector_reboxing_late_inlines(comp_arena(), 2, 0, nullptr),
_late_inlines_pos(0),
_number_of_mh_late_inlines(0),
_oom(false),
_print_inlining_stream(new (mtCompiler) stringStream()),
_print_inlining_list(nullptr),
_print_inlining_idx(0),
@ -938,6 +940,7 @@ Compile::Compile( ciEnv* ci_env,
_types(nullptr),
_node_hash(nullptr),
_number_of_mh_late_inlines(0),
_oom(false),
_print_inlining_stream(new (mtCompiler) stringStream()),
_print_inlining_list(nullptr),
_print_inlining_idx(0),
@ -5261,3 +5264,6 @@ Node* Compile::narrow_value(BasicType bt, Node* value, const Type* type, PhaseGV
return result;
}
void Compile::record_method_not_compilable_oom() {
record_method_not_compilable(CompilationMemoryStatistic::failure_reason_memlimit());
}

View File

@ -460,6 +460,9 @@ private:
int _late_inlines_pos; // Where in the queue should the next late inlining candidate go (emulate depth first inlining)
uint _number_of_mh_late_inlines; // number of method handle late inlining still pending
// "MemLimit" directive was specified and the memory limit was hit during compilation
bool _oom;
// Inlining may not happen in parse order which would make
// PrintInlining output confusing. Keep track of PrintInlining
// pieces in order.
@ -503,6 +506,8 @@ private:
void log_late_inline_failure(CallGenerator* cg, const char* msg);
DEBUG_ONLY(bool _exception_backedge;)
void record_method_not_compilable_oom();
public:
void* barrier_set_state() const { return _barrier_set_state; }
@ -814,6 +819,10 @@ private:
record_failure(reason);
}
bool check_node_count(uint margin, const char* reason) {
if (oom()) {
record_method_not_compilable_oom();
return true;
}
if (live_nodes() + margin > max_node_limit()) {
record_method_not_compilable(reason);
return true;
@ -821,6 +830,8 @@ private:
return false;
}
}
bool oom() const { return _oom; }
void set_oom() { _oom = true; }
// Node management
uint unique() const { return _unique; }

View File

@ -250,7 +250,8 @@ enum VMErrorType : unsigned int {
OOM_MALLOC_ERROR = 0xe0000001,
OOM_MMAP_ERROR = 0xe0000002,
OOM_MPROTECT_ERROR = 0xe0000003,
OOM_JAVA_HEAP_FATAL = 0xe0000004
OOM_JAVA_HEAP_FATAL = 0xe0000004,
OOM_HOTSPOT_ARENA = 0xe0000005
};
// error reporting helper functions

View File

@ -0,0 +1,154 @@
/*
* 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.
*/
/*
* @test id=c1_crash
* @requires vm.compiler1.enabled
* @summary Checks that -XX:CompileCommand=MemLimit,...,crash causes compilation to crash
* @library /test/lib
* @run driver compiler.print.CompileCommandMemLimit crash false
*/
/*
* @test id=c2_crash
* @requires vm.compiler2.enabled
* @summary Checks that -XX:CompileCommand=MemLimit,...,crash causes compilation to crash
* @library /test/lib
* @run driver compiler.print.CompileCommandMemLimit crash true
*/
/*
* @test id=c1_stop
* @requires vm.compiler1.enabled
* @summary Checks that -XX:CompileCommand=MemLimit,...,stop causes compilation to stop
* @library /test/lib
* @run driver compiler.print.CompileCommandMemLimit stop false
*/
/*
* @test id=c2_stop
* @requires vm.compiler2.enabled
* @summary Checks that -XX:CompileCommand=MemLimit,...,stop causes compilation to stop
* @library /test/lib
* @run driver compiler.print.CompileCommandMemLimit stop true
*/
package compiler.print;
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.process.ProcessTools;
import java.util.ArrayList;
import java.util.List;
public class CompileCommandMemLimit {
final static String METHOD1 = "method1";
final static String METHOD2 = "method2";
static boolean c2;
static boolean test_crash;
public static void main(String[] args) throws Exception {
switch (args[0]) {
case "crash" : test_crash = true; break;
case "stop" : test_crash = false; break;
default: throw new RuntimeException("invalid argument");
}
c2 = Boolean.parseBoolean(args[1]);
test(METHOD1, METHOD2);
test(METHOD2, METHOD1);
}
private static void test(String include, String exclude) throws Exception {
// A method that is known to cost compilers a bit of memory to compile
List<String> options = new ArrayList<String>();
options.add("-Xcomp");
options.add("-XX:-Inline");
options.add("-XX:CompileCommand=compileonly," + getTestClass() + "::*");
// We pass a very small size to guarantee the crash
options.add("-XX:CompileCommand=MemStat," + getTestMethod(include) + ",print");
options.add("-XX:CompileCommand=MemLimit," + getTestMethod(include) + ",4k" + (test_crash ? "~crash" : ""));
if (c2) {
options.add("-XX:-TieredCompilation");
} else {
options.add("-XX:TieredStopAtLevel=1");
}
options.add(getTestClass());
OutputAnalyzer oa = ProcessTools.executeTestJvm(options);
oa.reportDiagnosticSummary();
String expectedNameIncl = getTestMethod(include)
.replace('.', '/')
.replace("$", "\\$");
String expectedNameExcl = getTestMethod(exclude)
.replace('.', '/')
.replace("$", "\\$");
String ct = c2 ? "c2" : "c1";
if (test_crash) {
oa.shouldNotHaveExitValue(0);
oa.shouldMatch("# *Internal Error.*");
oa.shouldMatch("# *fatal error: " + ct + " *" + expectedNameIncl + ".*: Hit MemLimit .*limit: 4096.*");
oa.shouldNotMatch(".*" + expectedNameExcl + ".*");
} else {
// Should see trace output when methods are compiled
oa.shouldHaveExitValue(0)
.shouldMatch(".*" + expectedNameIncl + ".*")
.shouldNotMatch(".*" + expectedNameExcl + ".*");
// Expect this log line
oa.shouldMatch(".*" + expectedNameIncl + ".*Hit MemLimit.*");
// Expect final output to contain "oom"
oa.shouldMatch(".*oom.*" + expectedNameIncl + ".*");
}
}
// Test class that is invoked by the sub process
public static String getTestClass() {
return TestMain.class.getName();
}
public static String getTestMethod(String method) {
return getTestClass() + "::" + method;
}
public static class TestMain {
public static void main(String[] args) {
method1();
method2();
}
static long method1() {
return System.currentTimeMillis();
}
static void method2() {}
}
}

View File

@ -73,11 +73,11 @@ public class CompileCommandPrintMemStat {
// Should see final report
// Looks like this:
// total NA RA #nodes time type #rc thread method
// 621832 0 589104 0 0,025 c1 1 0x00007f5ccc1951a0 java/util/zip/ZipFile$Source::checkAndAddEntry((II)I)
// total NA RA result #nodes time type #rc thread method
// 211488 66440 77624 ok 13 0.057 c2 2 0x00007fb49428db70 compiler/print/CompileCommandPrintMemStat$TestMain::method1(()V)
oa.shouldMatch("total.*method");
oa.shouldMatch("\\d+ +\\d+ +\\d+ +\\d+.*" + expectedNameIncl + ".*");
oa.shouldNotMatch("\\d+ +\\d+ +\\d+ +\\d+.*" + expectedNameExcl + ".*");
oa.shouldMatch("\\d+ +\\d+ +\\d+ +\\S+ +\\d+.*" + expectedNameIncl + ".*");
oa.shouldNotMatch("\\d+ +\\d+ +\\d+ +\\S+ +\\d+.*" + expectedNameExcl + ".*");
}
// Test class that is invoked by the sub process

View File

@ -59,9 +59,9 @@ public class CompilerMemoryStatisticTest {
out.shouldHaveExitValue(0);
// Looks like this:
// total NA RA #nodes time type #rc thread method
// 621832 0 589104 0 0,025 c1 1 0x00007f5ccc1951a0 java/util/zip/ZipFile$Source.checkAndAddEntry((II)I)
// total NA RA result #nodes time type #rc thread method
// 211488 66440 77624 ok 13 0.057 c2 2 0x00007fb49428db70 compiler/print/CompileCommandPrintMemStat$TestMain::method1(()V)
out.shouldMatch("total.*method");
out.shouldMatch("\\d+ +\\d+ +\\d+ +\\d+.*java.*\\(.*\\)");
out.shouldMatch("\\d+ +\\d+ +\\d+ +\\S+ +\\d+.*java.*\\(.*\\)");
}
}