/* * 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(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"); } }