mirror of
https://github.com/openjdk/jdk.git
synced 2026-01-28 12:09:14 +00:00
269 lines
8.6 KiB
C++
269 lines
8.6 KiB
C++
/*
|
|
* Copyright (c) 2023 SAP SE. All rights reserved.
|
|
* Copyright (c) 2023 Red Hat Inc. All rights reserved.
|
|
* Copyright (c) 2023, 2025, 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 "logging/log.hpp"
|
|
#include "runtime/globals.hpp"
|
|
#include "runtime/globals_extension.hpp"
|
|
#include "runtime/mutex.hpp"
|
|
#include "runtime/mutexLocker.hpp"
|
|
#include "runtime/nonJavaThread.hpp"
|
|
#include "runtime/os.inline.hpp"
|
|
#include "runtime/safepoint.hpp"
|
|
#include "runtime/trimNativeHeap.hpp"
|
|
#include "utilities/debug.hpp"
|
|
#include "utilities/globalDefinitions.hpp"
|
|
#include "utilities/ostream.hpp"
|
|
#include "utilities/vmError.hpp"
|
|
|
|
class NativeHeapTrimmerThread : public NamedThread {
|
|
|
|
// Upper limit for the backoff during pending/in-progress safepoint.
|
|
// Chosen as reasonable value to balance the overheads of waking up
|
|
// during the safepoint, which might have undesired effects on latencies,
|
|
// and the accuracy in tracking the trimming interval.
|
|
static constexpr int64_t safepoint_poll_ms = 250;
|
|
|
|
Monitor* const _lock;
|
|
bool _stop;
|
|
uint16_t _suspend_count;
|
|
|
|
// Statistics
|
|
uint64_t _num_trims_performed;
|
|
|
|
bool is_suspended() const {
|
|
assert(_lock->is_locked(), "Must be");
|
|
return _suspend_count > 0;
|
|
}
|
|
|
|
uint16_t inc_suspend_count() {
|
|
assert(_lock->is_locked(), "Must be");
|
|
assert(_suspend_count < UINT16_MAX, "Sanity");
|
|
return ++_suspend_count;
|
|
}
|
|
|
|
uint16_t dec_suspend_count() {
|
|
assert(_lock->is_locked(), "Must be");
|
|
assert(_suspend_count != 0, "Sanity");
|
|
return --_suspend_count;
|
|
}
|
|
|
|
bool at_or_nearing_safepoint() const {
|
|
return SafepointSynchronize::is_at_safepoint() ||
|
|
SafepointSynchronize::is_synchronizing();
|
|
}
|
|
|
|
// in seconds
|
|
static double now() { return os::elapsedTime(); }
|
|
static double to_ms(double seconds) { return seconds * 1000.0; }
|
|
|
|
struct LogStartStopMark {
|
|
void log(const char* s) { log_info(trimnative)("Native heap trimmer %s", s); }
|
|
LogStartStopMark() { log("start"); }
|
|
~LogStartStopMark() { log("stop"); }
|
|
};
|
|
|
|
void run() override {
|
|
assert(NativeHeapTrimmer::enabled(), "Only call if enabled");
|
|
|
|
LogStartStopMark lssm;
|
|
|
|
const double interval_secs = (double)TrimNativeHeapInterval / 1000;
|
|
|
|
while (true) {
|
|
double tnow = now();
|
|
double next_trim_time = tnow + interval_secs;
|
|
|
|
unsigned times_suspended = 0;
|
|
unsigned times_waited = 0;
|
|
unsigned times_safepoint = 0;
|
|
|
|
{
|
|
MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag);
|
|
if (_stop) return;
|
|
|
|
while (at_or_nearing_safepoint() || is_suspended() || next_trim_time > tnow) {
|
|
if (is_suspended()) {
|
|
times_suspended ++;
|
|
ml.wait(0); // infinite
|
|
} else if (next_trim_time > tnow) {
|
|
times_waited ++;
|
|
const double wait_ms = MAX2(1.0, to_ms(next_trim_time - tnow));
|
|
ml.wait((int64_t)wait_ms);
|
|
} else if (at_or_nearing_safepoint()) {
|
|
times_safepoint ++;
|
|
const int64_t wait_ms = MIN2<int64_t>(TrimNativeHeapInterval, safepoint_poll_ms);
|
|
ml.wait(wait_ms);
|
|
}
|
|
|
|
if (_stop) return;
|
|
|
|
tnow = now();
|
|
}
|
|
}
|
|
|
|
log_trace(trimnative)("Times: %u suspended, %u timed, %u safepoint",
|
|
times_suspended, times_waited, times_safepoint);
|
|
|
|
execute_trim_and_log(tnow);
|
|
}
|
|
}
|
|
|
|
// Execute the native trim, log results.
|
|
void execute_trim_and_log(double t1) {
|
|
assert(os::can_trim_native_heap(), "Unexpected");
|
|
|
|
os::size_change_t sc = { 0, 0 };
|
|
LogTarget(Info, trimnative) lt;
|
|
const bool logging_enabled = lt.is_enabled();
|
|
|
|
// We only collect size change information if we are logging; save the access to procfs otherwise.
|
|
if (os::trim_native_heap(logging_enabled ? &sc : nullptr)) {
|
|
_num_trims_performed++;
|
|
if (logging_enabled) {
|
|
double t2 = now();
|
|
if (sc.after != SIZE_MAX) {
|
|
const size_t delta = sc.after < sc.before ? (sc.before - sc.after) : (sc.after - sc.before);
|
|
const char sign = sc.after < sc.before ? '-' : '+';
|
|
log_info(trimnative)("Periodic Trim (" UINT64_FORMAT "): " PROPERFMT "->" PROPERFMT " (%c" PROPERFMT ") %.3fms",
|
|
_num_trims_performed,
|
|
PROPERFMTARGS(sc.before), PROPERFMTARGS(sc.after), sign, PROPERFMTARGS(delta),
|
|
to_ms(t2 - t1));
|
|
} else {
|
|
log_info(trimnative)("Periodic Trim (" UINT64_FORMAT "): complete (no details) %.3fms",
|
|
_num_trims_performed,
|
|
to_ms(t2 - t1));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public:
|
|
|
|
NativeHeapTrimmerThread() :
|
|
_lock(new PaddedMonitor(Mutex::nosafepoint, "NativeHeapTrimmer_lock")),
|
|
_stop(false),
|
|
_suspend_count(0),
|
|
_num_trims_performed(0)
|
|
{
|
|
set_name("Native Heap Trimmer");
|
|
if (os::create_thread(this, os::vm_thread)) {
|
|
os::start_thread(this);
|
|
}
|
|
}
|
|
|
|
void suspend(const char* reason) {
|
|
assert(NativeHeapTrimmer::enabled(), "Only call if enabled");
|
|
uint16_t n = 0;
|
|
{
|
|
MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag);
|
|
n = inc_suspend_count();
|
|
// No need to wakeup trimmer
|
|
}
|
|
log_debug(trimnative)("Trim suspended for %s (%u suspend requests)", reason, n);
|
|
}
|
|
|
|
void resume(const char* reason) {
|
|
assert(NativeHeapTrimmer::enabled(), "Only call if enabled");
|
|
uint16_t n = 0;
|
|
{
|
|
MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag);
|
|
n = dec_suspend_count();
|
|
if (n == 0) {
|
|
ml.notify_all(); // pause end
|
|
}
|
|
}
|
|
if (n == 0) {
|
|
log_debug(trimnative)("Trim resumed after %s", reason);
|
|
} else {
|
|
log_debug(trimnative)("Trim still suspended after %s (%u suspend requests)", reason, n);
|
|
}
|
|
}
|
|
|
|
void stop() {
|
|
MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag);
|
|
_stop = true;
|
|
ml.notify_all();
|
|
}
|
|
|
|
void print_state(outputStream* st) const {
|
|
int64_t num_trims = 0;
|
|
bool stopped = false;
|
|
uint16_t suspenders = 0;
|
|
{
|
|
// Don't pull lock during error reporting
|
|
ConditionalMutexLocker ml(_lock, !VMError::is_error_reported(), Mutex::_no_safepoint_check_flag);
|
|
num_trims = _num_trims_performed;
|
|
stopped = _stop;
|
|
suspenders = _suspend_count;
|
|
}
|
|
st->print_cr("Trims performed: " UINT64_FORMAT ", current suspend count: %d, stopped: %d",
|
|
num_trims, suspenders, stopped);
|
|
}
|
|
|
|
}; // NativeHeapTrimmer
|
|
|
|
static NativeHeapTrimmerThread* g_trimmer_thread = nullptr;
|
|
|
|
void NativeHeapTrimmer::initialize() {
|
|
assert(g_trimmer_thread == nullptr, "Only once");
|
|
if (TrimNativeHeapInterval > 0) {
|
|
if (!os::can_trim_native_heap()) {
|
|
FLAG_SET_ERGO(TrimNativeHeapInterval, 0);
|
|
log_warning(trimnative)("Native heap trim is not supported on this platform");
|
|
return;
|
|
}
|
|
g_trimmer_thread = new NativeHeapTrimmerThread();
|
|
log_info(trimnative)("Periodic native trim enabled (interval: %u ms)", TrimNativeHeapInterval);
|
|
}
|
|
}
|
|
|
|
void NativeHeapTrimmer::cleanup() {
|
|
if (g_trimmer_thread != nullptr) {
|
|
g_trimmer_thread->stop();
|
|
}
|
|
}
|
|
|
|
void NativeHeapTrimmer::suspend_periodic_trim(const char* reason) {
|
|
if (g_trimmer_thread != nullptr) {
|
|
g_trimmer_thread->suspend(reason);
|
|
}
|
|
}
|
|
|
|
void NativeHeapTrimmer::resume_periodic_trim(const char* reason) {
|
|
if (g_trimmer_thread != nullptr) {
|
|
g_trimmer_thread->resume(reason);
|
|
}
|
|
}
|
|
|
|
void NativeHeapTrimmer::print_state(outputStream* st) {
|
|
if (g_trimmer_thread != nullptr) {
|
|
st->print_cr("Periodic native trim enabled (interval: %u ms)", TrimNativeHeapInterval);
|
|
g_trimmer_thread->print_state(st);
|
|
} else {
|
|
st->print_cr("Periodic native trim disabled");
|
|
}
|
|
}
|