8317683: Add JIT memory statistics

Reviewed-by: kvn, adinn
This commit is contained in:
Thomas Stuefe 2023-10-14 05:56:52 +00:00
parent f7d6d7a04f
commit 56aa1e8dc8
28 changed files with 903 additions and 27 deletions

View File

@ -33,7 +33,10 @@
#include "c1/c1_ValueMap.hpp"
#include "c1/c1_ValueStack.hpp"
#include "code/debugInfoRec.hpp"
#include "compiler/compilationMemoryStatistic.hpp"
#include "compiler/compilerDirectives.hpp"
#include "compiler/compileLog.hpp"
#include "compiler/compileTask.hpp"
#include "compiler/compilerDirectives.hpp"
#include "memory/resourceArea.hpp"
#include "runtime/sharedRuntime.hpp"
@ -442,6 +445,9 @@ void Compilation::install_code(int frame_size) {
void Compilation::compile_method() {
CompilationMemoryStatisticMark cmsm(env()->task()->directive());
{
PhaseTraceTime timeit(_t_setup);

View File

@ -219,7 +219,7 @@ void SymbolTable::create_table () {
if (symbol_alloc_arena_size == 0) {
_arena = new (mtSymbol) Arena(mtSymbol);
} else {
_arena = new (mtSymbol) Arena(mtSymbol, symbol_alloc_arena_size);
_arena = new (mtSymbol) Arena(mtSymbol, Arena::Tag::tag_other, symbol_alloc_arena_size);
}
}

View File

@ -0,0 +1,451 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, Red Hat, Inc. and/or its affiliates.
* 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 "logging/log.hpp"
#include "logging/logStream.hpp"
#include "compiler/abstractCompiler.hpp"
#include "compiler/compilationMemoryStatistic.hpp"
#include "compiler/compilerDirectives.hpp"
#include "compiler/compileTask.hpp"
#include "compiler/compilerDefinitions.hpp"
#include "compiler/compilerThread.hpp"
#include "memory/arena.hpp"
#include "memory/resourceArea.hpp"
#include "oops/symbol.hpp"
#ifdef COMPILER2
#include "opto/node.hpp" // compile.hpp is not self-contained
#include "opto/compile.hpp"
#endif
#include "services/nmtCommon.hpp"
#include "runtime/mutexLocker.hpp"
#include "runtime/os.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),
_na_at_peak(0), _ra_at_peak(0), _live_nodes_at_peak(0)
{}
size_t ArenaStatCounter::peak_since_start() const {
return _peak > _start ? _peak - _start : 0;
}
void ArenaStatCounter::start() {
_peak = _start = _current;
}
void ArenaStatCounter::update_c2_node_count() {
#ifdef COMPILER2
CompilerThread* const th = Thread::current()->as_Compiler_thread();
const CompileTask* const task = th->task();
if (task != nullptr &&
th->task()->compiler() != nullptr &&
th->task()->compiler()->type() == compiler_c2) {
const Compile* const comp = Compile::current();
if (comp != nullptr) {
_live_nodes_at_peak = comp->live_nodes();
}
}
#endif
}
// Account an arena allocation or de-allocation.
bool ArenaStatCounter::account(ssize_t delta, int tag) {
bool rc = false;
#ifdef ASSERT
// Note: if this fires, we free more arena memory under the scope of the
// CompilationMemoryHistoryMark than we allocate. This cannot be since we
// assume arena allocations in CompilerThread to be stack bound and symmetric.
assert(delta >= 0 || ((ssize_t)_current + delta) >= 0,
"Negative overflow (d=%zd %zu %zu %zu)", delta, _current, _start, _peak);
#endif
// Update totals
_current += delta;
// Update detail counter
switch ((Arena::Tag)tag) {
case Arena::Tag::tag_ra: _ra += delta; break;
case Arena::Tag::tag_node: _na += delta; break;
default: // ignore
break;
};
// Did we reach a peak?
if (_current > _peak) {
_peak = _current;
assert(delta > 0, "Sanity (%zu %zu %zu)", _current, _start, _peak);
_na_at_peak = _na;
_ra_at_peak = _ra;
update_c2_node_count();
rc = true;
}
return rc;
}
void ArenaStatCounter::print_on(outputStream* st) const {
st->print("%zu [na %zu ra %zu]", peak_since_start(), _na_at_peak, _ra_at_peak);
#ifdef ASSERT
st->print(" (%zu->%zu->%zu)", _start, _peak, _current);
#endif
}
//////////////////////////
// Backend
class FullMethodName {
Symbol* const _k;
Symbol* const _m;
Symbol* const _s;
public:
FullMethodName(Symbol* k, Symbol* m, Symbol* s) : _k(k), _m(m), _s(s) {}
FullMethodName(const FullMethodName& o) : _k(o._k), _m(o._m), _s(o._s) {}
void make_permanent() {
_k->make_permanent();
_m->make_permanent();
_s->make_permanent();
}
static unsigned compute_hash(const FullMethodName& n) {
return Symbol::compute_hash(n._k) ^
Symbol::compute_hash(n._m) ^
Symbol::compute_hash(n._s);
}
char* as_C_string(char* buf, size_t len) const {
stringStream ss(buf, len);
ResourceMark rm;
ss.print_raw(_k->as_C_string());
ss.print_raw("::");
ss.print_raw(_m->as_C_string());
ss.put('(');
ss.print_raw(_s->as_C_string());
ss.put(')');
return buf;
}
bool equals(const FullMethodName& b) const {
return _k == b._k && _m == b._m && _s == b._s;
}
bool operator== (const FullMethodName& other) const { return equals(other); }
};
// Note: not mtCompiler since we don't want to change what we measure
class MemStatEntry : public CHeapObj<mtInternal> {
const FullMethodName _method;
CompilerType _comptype;
double _time;
// How often this has been recompiled.
int _num_recomp;
// Compiling thread. Only for diagnostic purposes. Thread may not be alive anymore.
const Thread* _thread;
size_t _total;
size_t _na_at_peak;
size_t _ra_at_peak;
unsigned _live_nodes_at_peak;
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) {
}
void set_comptype(CompilerType comptype) { _comptype = comptype; }
void set_current_time() { _time = os::elapsedTime(); }
void set_current_thread() { _thread = Thread::current(); }
void inc_recompilation() { _num_recomp++; }
void set_total(size_t n) { _total = n; }
void set_na_at_peak(size_t n) { _na_at_peak = n; }
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; }
size_t total() const { return _total; }
static void print_legend(outputStream* st) {
st->print_cr("Legend:");
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(" time : time of last compilation (sec)");
st->print_cr(" type : compiler type");
st->print_cr(" #rc : how often recompiled");
st->print_cr(" thread : compiler thread");
}
static void print_header(outputStream* st) {
st->print_cr("total NA RA #nodes time type #rc thread method");
}
void print_on(outputStream* st, bool human_readable) const {
int col = 0;
// Total
if (human_readable) {
st->print(PROPERFMT " ", PROPERFMTARGS(_total));
} else {
st->print("%zu ", _total);
}
col += 10; st->fill_to(col);
// NA
if (human_readable) {
st->print(PROPERFMT " ", PROPERFMTARGS(_na_at_peak));
} else {
st->print("%zu ", _na_at_peak);
}
col += 10; st->fill_to(col);
// RA
if (human_readable) {
st->print(PROPERFMT " ", PROPERFMTARGS(_ra_at_peak));
} else {
st->print("%zu ", _ra_at_peak);
}
col += 10; st->fill_to(col);
// Number of Nodes when memory peaked
st->print("%u ", _live_nodes_at_peak);
col += 8; st->fill_to(col);
// TimeStamp
st->print("%.3f ", _time);
col += 8; st->fill_to(col);
// Type
st->print("%s ", compilertype2name(_comptype));
col += 6; st->fill_to(col);
// Recomp
st->print("%u ", _num_recomp);
col += 4; st->fill_to(col);
// Thread
st->print(PTR_FORMAT " ", p2i(_thread));
// MethodName
char buf[1024];
st->print("%s ", _method.as_C_string(buf, sizeof(buf)));
st->cr();
}
int compare_by_size(const MemStatEntry* b) const {
const size_t x1 = b->_total;
const size_t x2 = _total;
return x1 < x2 ? -1 : x1 == x2 ? 0 : 1;
}
bool equals(const FullMethodName& b) const {
return _method.equals(b);
}
};
class MemStatTable :
public ResourceHashtable<FullMethodName, MemStatEntry*, 7919, AnyObj::C_HEAP,
mtInternal, FullMethodName::compute_hash>
{
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) {
assert_lock_strong(NMTCompilationCostHistory_lock);
MemStatEntry** pe = get(fmn);
MemStatEntry* e = nullptr;
if (pe == nullptr) {
e = new MemStatEntry(fmn);
put(fmn, e);
} else {
// Update existing entry
e = *pe;
assert(e != nullptr, "Sanity");
}
e->set_current_time();
e->set_current_thread();
e->set_comptype(comptype);
e->inc_recompilation();
e->set_total(total);
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);
}
// Returns a C-heap-allocated SortMe array containing all entries from the table,
// optionally filtered by entry size
MemStatEntry** calc_flat_array(int& num, size_t min_size) {
assert_lock_strong(NMTCompilationCostHistory_lock);
const int num_all = number_of_entries();
MemStatEntry** flat = NEW_C_HEAP_ARRAY(MemStatEntry*, num_all, mtInternal);
int i = 0;
auto do_f = [&] (const FullMethodName& ignored, MemStatEntry* e) {
if (e->total() >= min_size) {
flat[i] = e;
assert(i < num_all, "Sanity");
i ++;
}
};
iterate_all(do_f);
if (min_size == 0) {
assert(i == num_all, "Sanity");
} else {
assert(i <= num_all, "Sanity");
}
num = i;
return flat;
}
};
bool CompilationMemoryStatistic::_enabled = false;
static MemStatTable* _the_table = nullptr;
void CompilationMemoryStatistic::initialize() {
assert(_enabled == false && _the_table == nullptr, "Only once");
_the_table = new (mtCompiler) MemStatTable;
_enabled = true;
log_info(compilation, alloc)("Compilation memory statistic enabled");
}
void CompilationMemoryStatistic::on_start_compilation() {
assert(enabled(), "Not enabled?");
Thread::current()->as_Compiler_thread()->arena_stat()->start();
}
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();
const CompilerType ct = th->task()->compiler()->type();
const Method* const m = th->task()->method();
FullMethodName fmn(m->klass_name(), m->name(), m->signature());
fmn.make_permanent();
const DirectiveSet* directive = th->task()->directive();
assert(directive->should_collect_memstat(), "Only call if memstat is enabled");
const bool print = directive->should_print_memstat();
if (print) {
char buf[1024];
fmn.as_C_string(buf, sizeof(buf));
tty->print("%s Arena usage %s: ", compilertype2name(ct), buf);
arena_stat->print_on(tty);
tty->cr();
}
{
MutexLocker ml(NMTCompilationCostHistory_lock, Mutex::_no_safepoint_check_flag);
assert(_the_table != nullptr, "not initialized");
_the_table->add(fmn, ct,
arena_stat->peak_since_start(), // total
arena_stat->na_at_peak(),
arena_stat->ra_at_peak(),
arena_stat->live_nodes_at_peak());
}
}
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());
}
static inline ssize_t diff_entries_by_size(const MemStatEntry* e1, const MemStatEntry* e2) {
return e1->compare_by_size(e2);
}
void CompilationMemoryStatistic::print_all_by_size(outputStream* st, bool human_readable, size_t min_size) {
st->print_cr("Compilation memory statistics");
if (!enabled()) {
st->print_cr("(unavailable)");
return;
}
st->cr();
MemStatEntry::print_legend(st);
st->cr();
if (min_size > 0) {
st->print_cr(" (cutoff: %zu bytes)", min_size);
}
st->cr();
MemStatEntry::print_header(st);
MemStatEntry** filtered = nullptr;
{
MutexLocker ml(NMTCompilationCostHistory_lock, Mutex::_no_safepoint_check_flag);
if (_the_table != nullptr) {
// We sort with quicksort
int num = 0;
filtered = _the_table->calc_flat_array(num, min_size);
if (min_size > 0) {
st->print_cr("(%d/%d)", num, _the_table->number_of_entries());
}
if (num > 0) {
QuickSort::sort(filtered, num, diff_entries_by_size, false);
// Now print. Has to happen under lock protection too, since entries may be changed.
for (int i = 0; i < num; i ++) {
filtered[i]->print_on(st, human_readable);
}
} else {
st->print_cr("No entries.");
}
} else {
st->print_cr("Not initialized.");
}
} // locked
FREE_C_HEAP_ARRAY(Entry, filtered);
}
CompilationMemoryStatisticMark::CompilationMemoryStatisticMark(const DirectiveSet* directive)
: _active(directive->should_collect_memstat()) {
if (_active) {
CompilationMemoryStatistic::on_start_compilation();
}
}
CompilationMemoryStatisticMark::~CompilationMemoryStatisticMark() {
if (_active) {
CompilationMemoryStatistic::on_end_compilation();
}
}

View File

@ -0,0 +1,103 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, Red Hat, Inc. and/or its affiliates.
* 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_COMPILER_COMPILATIONMEMORYSTATISTIC_HPP
#define SHARE_COMPILER_COMPILATIONMEMORYSTATISTIC_HPP
#include "compiler/compilerDefinitions.hpp"
#include "memory/allocation.hpp"
#include "memory/allStatic.hpp"
#include "utilities/globalDefinitions.hpp"
class outputStream;
class Symbol;
class DirectiveSet;
// Counters for allocations from one arena
class ArenaStatCounter : public CHeapObj<mtCompiler> {
// Current bytes, total
size_t _current;
// bytes when compilation started
size_t _start;
// bytes at last peak, total
size_t _peak;
// Current bytes used for node arenas, total
size_t _na;
// Current bytes used for resource areas
size_t _ra;
// Peak composition:
// Size of node arena when total peaked (c2 only)
size_t _na_at_peak;
// Size of resource area when total peaked
size_t _ra_at_peak;
// Number of live nodes when total peaked (c2 only)
unsigned _live_nodes_at_peak;
void update_c2_node_count();
public:
ArenaStatCounter();
// Size of peak since last compilation
size_t peak_since_start() const;
// Peak details
size_t na_at_peak() const { return _na_at_peak; }
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();
// 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;
};
class CompilationMemoryStatistic : public AllStatic {
static bool _enabled;
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_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);
};
// RAII object to wrap one compilation
class CompilationMemoryStatisticMark {
const bool _active;
public:
CompilationMemoryStatisticMark(const DirectiveSet* directive);
~CompilationMemoryStatisticMark();
};
#endif // SHARE_COMPILER_COMPILATIONMEMORYSTATISTIC_HPP

View File

@ -31,6 +31,7 @@
#include "code/codeHeapState.hpp"
#include "code/dependencyContext.hpp"
#include "compiler/compilationLog.hpp"
#include "compiler/compilationMemoryStatistic.hpp"
#include "compiler/compilationPolicy.hpp"
#include "compiler/compileBroker.hpp"
#include "compiler/compileLog.hpp"
@ -650,6 +651,10 @@ void CompileBroker::compilation_init(JavaThread* THREAD) {
}
#endif // INCLUDE_JVMCI
if (CompilerOracle::should_collect_memstat()) {
CompilationMemoryStatistic::initialize();
}
// Start the compiler thread(s)
init_compiler_threads();
// totalTime performance counter is always created as it is required

View File

@ -38,7 +38,7 @@ enum CompilerType : u1 {
};
extern const char* compilertype2name_tab[compiler_number_of_types]; // Map CompilerType to its name
inline const char* compilertype2name(CompilerType t) { return (uint)t < compiler_number_of_types ? compilertype2name_tab[t] : nullptr; }
inline const char* compilertype2name(CompilerType t) { return (uint)t < compiler_number_of_types ? compilertype2name_tab[t] : "invalid"; }
// Handy constants for deciding which compiler mode to use.
enum MethodCompilation {

View File

@ -202,6 +202,14 @@ bool DirectiveSet::is_c2(CompilerDirectives* directive) const {
return this == directive->_c2_store;
}
bool DirectiveSet::should_collect_memstat() const {
return MemStatOption > 0;
}
bool DirectiveSet::should_print_memstat() const {
return MemStatOption == (uintx)MemStatAction::print;
}
// 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(MemStat, uintx, 0, MemStat) \
cflags(PrintAssembly, bool, PrintAssembly, PrintAssembly) \
cflags(PrintCompilation, bool, PrintCompilation, PrintCompilation) \
cflags(PrintInlining, bool, PrintInlining, PrintInlining) \
@ -131,6 +132,8 @@ public:
void finalize(outputStream* st);
bool is_c1(CompilerDirectives* directive) const;
bool is_c2(CompilerDirectives* directive) const;
bool should_collect_memstat() const;
bool should_print_memstat() const;
typedef enum {
#define enum_of_flags(name, type, dvalue, cc_flag) name##Index,

View File

@ -101,6 +101,7 @@ class TypedMethodOptionMatcher;
static TypedMethodOptionMatcher* option_list = nullptr;
static bool any_set = false;
static bool print_final_memstat_report = false;
// A filter for quick lookup if an option is set
static bool option_filter[static_cast<int>(CompileCommand::Unknown) + 1] = { 0 };
@ -325,6 +326,7 @@ static void register_command(TypedMethodOptionMatcher* matcher,
tty->print("CompileCommand: %s ", option2name(option));
matcher->print();
}
return;
}
@ -455,6 +457,15 @@ bool CompilerOracle::should_print_methods() {
return has_command(CompileCommand::Print);
}
// Tells whether there are any methods to collect memory statistics for
bool CompilerOracle::should_collect_memstat() {
return has_command(CompileCommand::MemStat);
}
bool CompilerOracle::should_print_final_memstat_report() {
return print_final_memstat_report;
}
bool CompilerOracle::should_log(const methodHandle& method) {
if (!LogCompilation) return false;
if (!has_command(CompileCommand::Log)) {
@ -623,6 +634,22 @@ void skip_comma(char* &line) {
}
}
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) {
value = (uintx)MemStatAction::collect;
} else if (strncasecmp(line, "print", 5) == 0) {
value = (uintx)MemStatAction::print;
print_final_memstat_report = true;
} else {
jio_snprintf(errorbuf, buf_size, "MemStat: invalid value expected 'collect' or 'print' (omitting value means 'collect')");
}
return true; // handled
}
return false;
#undef HANDLE_VALUE
}
static void scan_value(enum OptionType type, char* line, int& total_bytes_read,
TypedMethodOptionMatcher* matcher, enum CompileCommand option, char* errorbuf, const int buf_size) {
int bytes_read = 0;
@ -642,7 +669,13 @@ static void scan_value(enum OptionType type, char* line, int& total_bytes_read,
}
} else if (type == OptionType::Uintx) {
uintx value;
if (sscanf(line, "" UINTX_FORMAT "%n", &value, &bytes_read) == 1) {
// Is it a named enum?
bool success = parseEnumValueAsUintx(option, line, value, bytes_read, errorbuf, buf_size);
if (!success) {
// Is it a raw number?
success = (sscanf(line, "" UINTX_FORMAT "%n", &value, &bytes_read) == 1);
}
if (success) {
total_bytes_read += bytes_read;
line += bytes_read;
register_command(matcher, option, value);
@ -914,10 +947,14 @@ bool CompilerOracle::parse_from_line(char* line) {
}
skip_whitespace(line);
if (*line == '\0') {
// if this is a bool option this implies true
if (option2type(option) == OptionType::Bool) {
// if this is a bool option this implies true
register_command(matcher, option, true);
return true;
} else if (option == CompileCommand::MemStat) {
// MemStat default action is to collect data but to not print
register_command(matcher, option, (uintx)MemStatAction::collect);
return true;
} else {
jio_snprintf(error_buf, sizeof(error_buf), " Option '%s' is not followed by a value", option2name(option));
print_parse_error(error_buf, original.get());

View File

@ -57,6 +57,7 @@ class methodHandle;
option(Break, "break", Bool) \
option(BreakAtExecute, "BreakAtExecute", Bool) \
option(BreakAtCompile, "BreakAtCompile", Bool) \
option(MemStat, "MemStat", Uintx) \
option(PrintAssembly, "PrintAssembly", Bool) \
option(PrintCompilation, "PrintCompilation", Bool) \
option(PrintInlining, "PrintInlining", Bool) \
@ -113,6 +114,10 @@ enum class OptionType {
Unknown
};
enum class MemStatAction {
collect = 1, print = 2
};
class CompilerOracle : AllStatic {
private:
static bool _quiet;
@ -151,6 +156,10 @@ class CompilerOracle : AllStatic {
// Tells whether there are any methods to print for print_method_statistics()
static bool should_print_methods();
// Tells whether there are any methods to (collect|collect+print) memory statistics for
static bool should_collect_memstat();
static bool should_print_final_memstat_report();
// Tags the method as blackhole candidate, if possible.
static void tag_blackhole_if_possible(const methodHandle& method);

View File

@ -23,6 +23,7 @@
*/
#include "precompiled.hpp"
#include "compiler/compilationMemoryStatistic.hpp"
#include "compiler/compileBroker.hpp"
#include "compiler/compileTask.hpp"
#include "compiler/compilerThread.hpp"
@ -39,6 +40,7 @@ CompilerThread::CompilerThread(CompileQueue* queue,
_counters = counters;
_buffer_blob = nullptr;
_compiler = nullptr;
_arena_stat = CompilationMemoryStatistic::enabled() ? new ArenaStatCounter : nullptr;
// Compiler uses resource area for compilation, let's bias it to mtCompiler
resource_area()->bias_to(mtCompiler);
@ -51,6 +53,7 @@ CompilerThread::CompilerThread(CompileQueue* queue,
CompilerThread::~CompilerThread() {
// Delete objects which were allocated on heap.
delete _counters;
delete _arena_stat;
}
void CompilerThread::thread_entry(JavaThread* thread, TRAPS) {

View File

@ -27,8 +27,9 @@
#include "runtime/javaThread.hpp"
class BufferBlob;
class AbstractCompiler;
class ArenaStatCounter;
class BufferBlob;
class ciEnv;
class CompileThread;
class CompileLog;
@ -54,6 +55,8 @@ class CompilerThread : public JavaThread {
AbstractCompiler* _compiler;
TimeStamp _idle_time;
ArenaStatCounter* _arena_stat;
public:
static CompilerThread* current() {
@ -81,6 +84,7 @@ class CompilerThread : public JavaThread {
CompileQueue* queue() const { return _queue; }
CompilerCounters* counters() const { return _counters; }
ArenaStatCounter* arena_stat() const { return _arena_stat; }
// Get/set the thread's compilation environment.
ciEnv* env() { return _env; }

View File

@ -24,6 +24,7 @@
*/
#include "precompiled.hpp"
#include "compiler/compilationMemoryStatistic.hpp"
#include "memory/allocation.hpp"
#include "memory/allocation.inline.hpp"
#include "memory/arena.hpp"
@ -209,7 +210,7 @@ void Chunk::next_chop(Chunk* k) {
k->_next = nullptr;
}
Arena::Arena(MEMFLAGS flag, size_t init_size) : _flags(flag), _size_in_bytes(0) {
Arena::Arena(MEMFLAGS flag, Tag tag, size_t init_size) : _flags(flag), _tag(tag), _size_in_bytes(0) {
init_size = ARENA_ALIGN(init_size);
_chunk = ChunkPool::allocate_chunk(init_size, AllocFailStrategy::EXIT_OOM);
_first = _chunk;
@ -219,7 +220,7 @@ Arena::Arena(MEMFLAGS flag, size_t init_size) : _flags(flag), _size_in_bytes(0)
set_size_in_bytes(init_size);
}
Arena::Arena(MEMFLAGS flag) : _flags(flag), _size_in_bytes(0) {
Arena::Arena(MEMFLAGS flag, Tag tag) : _flags(flag), _tag(tag), _size_in_bytes(0) {
_chunk = ChunkPool::allocate_chunk(Chunk::init_size, AllocFailStrategy::EXIT_OOM);
_first = _chunk;
_hwm = _chunk->bottom(); // Save the cached hwm, max
@ -251,6 +252,12 @@ void Arena::set_size_in_bytes(size_t size) {
ssize_t delta = size - size_in_bytes();
_size_in_bytes = size;
MemTracker::record_arena_size_change(delta, _flags);
if (CompilationMemoryStatistic::enabled() && _flags == mtCompiler) {
Thread* const t = Thread::current();
if (t != nullptr && t->is_Compiler_thread()) {
CompilationMemoryStatistic::on_arena_change(delta, this);
}
}
}
}

View File

@ -86,13 +86,22 @@ public:
// Fast allocation of memory
class Arena : public CHeapObjBase {
public:
enum class Tag {
tag_other = 0,
tag_ra, // resource area
tag_ha, // handle area
tag_node // C2 Node arena
};
protected:
friend class HandleMark;
friend class NoHandleMark;
friend class VMStructs;
MEMFLAGS _flags; // Memory tracking flags
const Tag _tag;
Chunk* _first; // First chunk
Chunk* _chunk; // current chunk
char* _hwm; // High water mark
@ -115,9 +124,8 @@ protected:
public:
// Start the chunk_pool cleaner task
static void start_chunk_pool_cleaner_task();
Arena(MEMFLAGS memflag);
Arena(MEMFLAGS memflag, size_t init_size);
Arena(MEMFLAGS memflag, Tag tag = Tag::tag_other);
Arena(MEMFLAGS memflag, Tag tag, size_t init_size);
~Arena();
void destruct_contents();
char* hwm() const { return _hwm; }
@ -171,6 +179,8 @@ protected:
size_t size_in_bytes() const { return _size_in_bytes; };
void set_size_in_bytes(size_t size);
Tag get_tag() const { return _tag; }
private:
// Reset this Arena to empty, access will trigger grow if necessary
void reset(void) {

View File

@ -26,6 +26,7 @@
#define SHARE_MEMORY_RESOURCEAREA_HPP
#include "memory/allocation.hpp"
#include "memory/arena.hpp"
#include "runtime/javaThread.hpp"
// The resource area holds temporary data structures in the VM.
@ -51,10 +52,11 @@ class ResourceArea: public Arena {
public:
ResourceArea(MEMFLAGS flags = mtThread) :
Arena(flags) DEBUG_ONLY(COMMA _nesting(0)) {}
Arena(flags, Arena::Tag::tag_ra) DEBUG_ONLY(COMMA _nesting(0)) {}
ResourceArea(size_t init_size, MEMFLAGS flags = mtThread) :
Arena(flags, init_size) DEBUG_ONLY(COMMA _nesting(0)) {}
Arena(flags, Arena::Tag::tag_ra, init_size) DEBUG_ONLY(COMMA _nesting(0)) {
}
char* allocate_bytes(size_t size, AllocFailType alloc_failmode = AllocFailStrategy::EXIT_OOM);

View File

@ -24,6 +24,7 @@
#include "precompiled.hpp"
#include "classfile/vmClasses.hpp"
#include "compiler/compilationMemoryStatistic.hpp"
#include "compiler/compilerDefinitions.inline.hpp"
#include "runtime/handles.inline.hpp"
#include "jfr/support/jfrIntrinsics.hpp"
@ -109,6 +110,8 @@ void C2Compiler::initialize() {
void C2Compiler::compile_method(ciEnv* env, ciMethod* target, int entry_bci, bool install_code, DirectiveSet* directive) {
assert(is_initialized(), "Compiler thread must be initialized");
CompilationMemoryStatisticMark cmsm(directive);
bool subsume_loads = SubsumeLoads;
bool do_escape_analysis = DoEscapeAnalysis;
bool do_iterative_escape_analysis = DoEscapeAnalysis;

View File

@ -646,8 +646,8 @@ Compile::Compile( ciEnv* ci_env, ciMethod* target, int osr_bci,
_unique(0),
_dead_node_count(0),
_dead_node_list(comp_arena()),
_node_arena_one(mtCompiler),
_node_arena_two(mtCompiler),
_node_arena_one(mtCompiler, Arena::Tag::tag_node),
_node_arena_two(mtCompiler, Arena::Tag::tag_node),
_node_arena(&_node_arena_one),
_mach_constant_base_node(nullptr),
_Compile_types(mtCompiler),

View File

@ -728,7 +728,7 @@ WB_ENTRY(jint, WB_NMTGetHashSize(JNIEnv* env, jobject o))
WB_END
WB_ENTRY(jlong, WB_NMTNewArena(JNIEnv* env, jobject o, jlong init_size))
Arena* arena = new (mtTest) Arena(mtTest, size_t(init_size));
Arena* arena = new (mtTest) Arena(mtTest, Arena::Tag::tag_other, size_t(init_size));
return (jlong)arena;
WB_END

View File

@ -187,7 +187,7 @@ class HandleArea: public Arena {
HandleArea* _prev; // link to outer (older) area
public:
// Constructor
HandleArea(HandleArea* prev) : Arena(mtThread, Chunk::tiny_size) {
HandleArea(HandleArea* prev) : Arena(mtThread, Tag::tag_ha, Chunk::tiny_size) {
debug_only(_handle_mark_nesting = 0);
debug_only(_no_handle_mark_nesting = 0);
_prev = prev;

View File

@ -31,6 +31,7 @@
#include "classfile/symbolTable.hpp"
#include "classfile/systemDictionary.hpp"
#include "code/codeCache.hpp"
#include "compiler/compilationMemoryStatistic.hpp"
#include "compiler/compileBroker.hpp"
#include "compiler/compilerOracle.hpp"
#include "gc/shared/collectedHeap.hpp"
@ -341,6 +342,10 @@ void print_statistics() {
MetaspaceUtils::print_basic_report(tty, 0);
}
if (CompilerOracle::should_print_final_memstat_report()) {
CompilationMemoryStatistic::print_all_by_size(tty, false, 0);
}
ThreadsSMRSupport::log_statistics();
}

View File

@ -139,6 +139,7 @@ Mutex* ThreadIdTableCreate_lock = nullptr;
Mutex* SharedDecoder_lock = nullptr;
Mutex* DCmdFactory_lock = nullptr;
Mutex* NMTQuery_lock = nullptr;
Mutex* NMTCompilationCostHistory_lock = nullptr;
#if INCLUDE_CDS
#if INCLUDE_JVMTI
@ -309,6 +310,7 @@ void mutex_init() {
MUTEX_DEFN(SharedDecoder_lock , PaddedMutex , tty-1);
MUTEX_DEFN(DCmdFactory_lock , PaddedMutex , nosafepoint);
MUTEX_DEFN(NMTQuery_lock , PaddedMutex , safepoint);
MUTEX_DEFN(NMTCompilationCostHistory_lock , PaddedMutex , nosafepoint);
#if INCLUDE_CDS
#if INCLUDE_JVMTI
MUTEX_DEFN(CDSClassFileStream_lock , PaddedMutex , safepoint);

View File

@ -117,6 +117,7 @@ extern Mutex* ThreadIdTableCreate_lock; // Used by ThreadIdTable to laz
extern Mutex* SharedDecoder_lock; // serializes access to the decoder during normal (not error reporting) use
extern Mutex* DCmdFactory_lock; // serialize access to DCmdFactory information
extern Mutex* NMTQuery_lock; // serialize NMT Dcmd queries
extern Mutex* NMTCompilationCostHistory_lock; // guards NMT compilation cost history
#if INCLUDE_CDS
#if INCLUDE_JVMTI
extern Mutex* CDSClassFileStream_lock; // FileMapInfo::open_stream_for_jvmti

View File

@ -43,6 +43,7 @@
#include "jfr/support/jfrThreadExtension.hpp"
#endif
class CompilerThread;
class HandleArea;
class HandleMark;
class ICRefillVerifier;
@ -324,6 +325,12 @@ class Thread: public ThreadShadow {
virtual bool is_AttachListener_thread() const { return false; }
virtual bool is_monitor_deflation_thread() const { return false; }
// Convenience cast functions
CompilerThread* as_Compiler_thread() const {
assert(is_Compiler_thread(), "Must be compiler thread");
return (CompilerThread*)this;
}
// Can this thread make Java upcalls
virtual bool can_call_java() const { return false; }

View File

@ -31,6 +31,8 @@
#include "classfile/systemDictionary.hpp"
#include "classfile/vmClasses.hpp"
#include "code/codeCache.hpp"
#include "compiler/compilationMemoryStatistic.hpp"
#include "compiler/compiler_globals.hpp"
#include "compiler/compileBroker.hpp"
#include "compiler/directivesParser.hpp"
#include "gc/shared/gcVMOperations.hpp"
@ -138,6 +140,7 @@ void DCmd::register_dcmds(){
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<CompilerDirectivesAddDCmd>(full_export, true, false));
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<CompilerDirectivesRemoveDCmd>(full_export, true, false));
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<CompilerDirectivesClearDCmd>(full_export, true, false));
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<CompilationMemoryStatisticDCmd>(full_export, true, false));
// Enhanced JMX Agent Support
// These commands won't be exported via the DiagnosticCommandMBean until an
@ -1134,3 +1137,17 @@ void ThreadDumpToFileDCmd::dumpToFile(Symbol* name, Symbol* signature, const cha
jbyte* addr = typeArrayOop(res)->byte_at_addr(0);
output()->print_raw((const char*)addr, ba->length());
}
CompilationMemoryStatisticDCmd::CompilationMemoryStatisticDCmd(outputStream* output, bool heap) :
DCmdWithParser(output, heap),
_human_readable("-H", "Human readable format", "BOOLEAN", false, "false"),
_minsize("-s", "Minimum memory size", "MEMORY SIZE", false, "0") {
_dcmdparser.add_dcmd_option(&_human_readable);
_dcmdparser.add_dcmd_option(&_minsize);
}
void CompilationMemoryStatisticDCmd::execute(DCmdSource source, TRAPS) {
const bool human_readable = _human_readable.value();
const size_t minsize = _minsize.has_value() ? _minsize.value()._size : 0;
CompilationMemoryStatistic::print_all_by_size(output(), human_readable, minsize);
}

View File

@ -954,4 +954,28 @@ public:
virtual void execute(DCmdSource source, TRAPS);
};
class CompilationMemoryStatisticDCmd: public DCmdWithParser {
protected:
DCmdArgument<bool> _human_readable;
DCmdArgument<MemorySizeArgument> _minsize;
public:
static int num_arguments() { return 2; }
CompilationMemoryStatisticDCmd(outputStream* output, bool heap);
static const char* name() {
return "Compiler.memory";
}
static const char* description() {
return "Print compilation footprint";
}
static const char* impact() {
return "Medium: Pause time depends on number of compiled methods";
}
static const JavaPermission permission() {
JavaPermission p = {"java.lang.management.ManagementPermission",
"monitor", nullptr};
return p;
}
virtual void execute(DCmdSource source, TRAPS);
};
#endif // SHARE_SERVICES_DIAGNOSTICCOMMAND_HPP

View File

@ -329,7 +329,7 @@ TEST_VM(Arena, mixed_alignment_allocation) {
TEST_VM(Arena, Arena_with_crooked_initial_size) {
// Test that an arena with a crooked, not 64-bit aligned initial size
// works
Arena ar(mtTest, 4097);
Arena ar(mtTest, Arena::Tag::tag_other, 4097);
void* p1 = ar.AmallocWords(BytesPerWord);
void* p2 = ar.Amalloc(BytesPerLong);
ASSERT_TRUE(is_aligned(p1, BytesPerWord));
@ -342,7 +342,7 @@ TEST_VM(Arena, Arena_grows_large_unaligned) {
// (only possible on 32-bit when allocating with word alignment).
// Then we alloc some more. If Arena::grow() does not correctly align, on 32-bit
// something should assert at some point.
Arena ar(mtTest, 100); // first chunk is small
Arena ar(mtTest, Arena::Tag::tag_other, 100); // first chunk is small
void* p = ar.AmallocWords(Chunk::size + BytesPerWord); // if Arena::grow() misaligns, this asserts
// some more allocations for good measure
for (int i = 0; i < 100; i ++) {
@ -372,13 +372,13 @@ TEST_VM(Arena, different_chunk_sizes) {
for (int i = 0; i < 1000; i ++) {
// Unfortunately, Arenas cannot be newed,
// so we are left with awkwardly placing a few on the stack.
Arena ar0(mtTest, random_arena_chunk_size());
Arena ar1(mtTest, random_arena_chunk_size());
Arena ar2(mtTest, random_arena_chunk_size());
Arena ar3(mtTest, random_arena_chunk_size());
Arena ar4(mtTest, random_arena_chunk_size());
Arena ar5(mtTest, random_arena_chunk_size());
Arena ar6(mtTest, random_arena_chunk_size());
Arena ar7(mtTest, random_arena_chunk_size());
Arena ar0(mtTest, Arena::Tag::tag_other, random_arena_chunk_size());
Arena ar1(mtTest, Arena::Tag::tag_other, random_arena_chunk_size());
Arena ar2(mtTest, Arena::Tag::tag_other, random_arena_chunk_size());
Arena ar3(mtTest, Arena::Tag::tag_other, random_arena_chunk_size());
Arena ar4(mtTest, Arena::Tag::tag_other, random_arena_chunk_size());
Arena ar5(mtTest, Arena::Tag::tag_other, random_arena_chunk_size());
Arena ar6(mtTest, Arena::Tag::tag_other, random_arena_chunk_size());
Arena ar7(mtTest, Arena::Tag::tag_other, random_arena_chunk_size());
}
}

View File

@ -0,0 +1,102 @@
/*
* Copyright (c) 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
* 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
* @summary Checks that -XX:CompileCommand=PrintMemStat,... works
* @library /test/lib
* @run driver compiler.print.CompileCommandPrintMemStat
*/
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 CompileCommandPrintMemStat {
final static String METHOD1 = "method1";
final static String METHOD2 = "method2";
public static void main(String[] args) throws Exception {
test(METHOD1, METHOD2);
test(METHOD2, METHOD1);
}
private static void test(String include, String exclude) throws Exception {
List<String> options = new ArrayList<String>();
options.add("-Xcomp");
options.add("-XX:-Inline");
options.add("-XX:CompileCommand=compileonly," + getTestClass() + "::*");
options.add("-XX:CompileCommand=MemStat," + getTestMethod(include) + ",print");
options.add(getTestClass());
OutputAnalyzer oa = ProcessTools.executeTestJvm(options);
// We expect two printouts for "PrintMemStat". A line at compilation time, and a line in a summary report
// that is printed when we exit. Both use the typical <class>::name format but use / as separator and also
// print the signature.
String expectedNameIncl = getTestMethod(include)
.replace('.', '/')
.replace("$", "\\$");
String expectedNameExcl = getTestMethod(exclude)
.replace('.', '/')
.replace("$", "\\$");
// Should see trace output when methods are compiled
oa.shouldHaveExitValue(0)
.shouldMatch(".*" + expectedNameIncl + ".*")
.shouldNotMatch(".*" + expectedNameExcl + ".*");
// 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)
oa.shouldMatch("total.*method");
oa.shouldMatch("\\d+ +\\d+ +\\d+ +\\d+.*" + expectedNameIncl + ".*");
oa.shouldNotMatch("\\d+ +\\d+ +\\d+ +\\d+.*" + expectedNameExcl + ".*");
}
// 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 void method1() {}
static void method2() {}
}
}

View File

@ -0,0 +1,67 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, Red Hat, Inc. and/or its affiliates.
* 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.
*/
import jdk.test.lib.dcmd.PidJcmdExecutor;
import jdk.test.lib.process.OutputAnalyzer;
import java.util.Iterator;
/*
* @test CompilerMemoryStatisticTest
* @summary Test Compiler.memory
* @requires vm.compiler1.enabled
* @requires vm.compiler2.enabled
*
* @library /test/lib
* @modules java.base/jdk.internal.misc
* java.management
* @run main/othervm -XX:CompileCommand=memstat,*.* CompilerMemoryStatisticTest
*/
/*
* @test CompilerMemoryStatisticTest
* @summary Test Compiler.memory
* @requires vm.compiler1.enabled
* @requires vm.compiler2.enabled
*
* @library /test/lib
* @modules java.base/jdk.internal.misc
* java.management
* @run main/othervm -XX:CompileCommand=memstat,*.*,collect CompilerMemoryStatisticTest
*/
public class CompilerMemoryStatisticTest {
public static void main(String args[]) throws Exception {
PidJcmdExecutor executor = new PidJcmdExecutor();
OutputAnalyzer out = executor.execute("Compiler.memory");
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)
out.shouldMatch("total.*method");
out.shouldMatch("\\d+ +\\d+ +\\d+ +\\d+.*java.*\\(.*\\)");
}
}