8293114: JVM should trim the native heap
Reviewed-by: shade, rehn, dholmes
This commit is contained in:
parent
59f66a3b83
commit
9e4fc568a6
@ -52,7 +52,7 @@ inline bool os::must_commit_stack_guard_pages() {
|
|||||||
inline void os::map_stack_shadow_pages(address sp) {
|
inline void os::map_stack_shadow_pages(address sp) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// stubbed-out trim-native support
|
// Trim-native support, stubbed out for now, may be enabled later
|
||||||
inline bool os::can_trim_native_heap() { return false; }
|
inline bool os::can_trim_native_heap() { return false; }
|
||||||
inline bool os::trim_native_heap(os::size_change_t* rss_change) { return false; }
|
inline bool os::trim_native_heap(os::size_change_t* rss_change) { return false; }
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ inline bool os::must_commit_stack_guard_pages() {
|
|||||||
inline void os::map_stack_shadow_pages(address sp) {
|
inline void os::map_stack_shadow_pages(address sp) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// stubbed-out trim-native support
|
// Trim-native support, stubbed out for now, may be enabled later
|
||||||
inline bool os::can_trim_native_heap() { return false; }
|
inline bool os::can_trim_native_heap() { return false; }
|
||||||
inline bool os::trim_native_heap(os::size_change_t* rss_change) { return false; }
|
inline bool os::trim_native_heap(os::size_change_t* rss_change) { return false; }
|
||||||
|
|
||||||
|
@ -93,7 +93,7 @@ inline void PlatformMonitor::notify_all() {
|
|||||||
WakeAllConditionVariable(&_cond);
|
WakeAllConditionVariable(&_cond);
|
||||||
}
|
}
|
||||||
|
|
||||||
// stubbed-out trim-native support
|
// Trim-native support, stubbed out for now, may be enabled later
|
||||||
inline bool os::can_trim_native_heap() { return false; }
|
inline bool os::can_trim_native_heap() { return false; }
|
||||||
inline bool os::trim_native_heap(os::size_change_t* rss_change) { return false; }
|
inline bool os::trim_native_heap(os::size_change_t* rss_change) { return false; }
|
||||||
|
|
||||||
|
@ -53,6 +53,7 @@
|
|||||||
#include "runtime/mutexLocker.hpp"
|
#include "runtime/mutexLocker.hpp"
|
||||||
#include "runtime/safepointVerifiers.hpp"
|
#include "runtime/safepointVerifiers.hpp"
|
||||||
#include "runtime/timerTrace.hpp"
|
#include "runtime/timerTrace.hpp"
|
||||||
|
#include "runtime/trimNativeHeap.hpp"
|
||||||
#include "services/diagnosticCommand.hpp"
|
#include "services/diagnosticCommand.hpp"
|
||||||
#include "utilities/concurrentHashTable.inline.hpp"
|
#include "utilities/concurrentHashTable.inline.hpp"
|
||||||
#include "utilities/concurrentHashTableTasks.inline.hpp"
|
#include "utilities/concurrentHashTableTasks.inline.hpp"
|
||||||
@ -456,6 +457,7 @@ void StringTable::clean_dead_entries(JavaThread* jt) {
|
|||||||
|
|
||||||
StringTableDeleteCheck stdc;
|
StringTableDeleteCheck stdc;
|
||||||
StringTableDoDelete stdd;
|
StringTableDoDelete stdd;
|
||||||
|
NativeHeapTrimmer::SuspendMark sm("stringtable");
|
||||||
{
|
{
|
||||||
TraceTime timer("Clean", TRACETIME_LOG(Debug, stringtable, perf));
|
TraceTime timer("Clean", TRACETIME_LOG(Debug, stringtable, perf));
|
||||||
while(bdt.do_task(jt, stdc, stdd)) {
|
while(bdt.do_task(jt, stdc, stdd)) {
|
||||||
|
@ -37,6 +37,7 @@
|
|||||||
#include "runtime/atomic.hpp"
|
#include "runtime/atomic.hpp"
|
||||||
#include "runtime/interfaceSupport.inline.hpp"
|
#include "runtime/interfaceSupport.inline.hpp"
|
||||||
#include "runtime/timerTrace.hpp"
|
#include "runtime/timerTrace.hpp"
|
||||||
|
#include "runtime/trimNativeHeap.hpp"
|
||||||
#include "services/diagnosticCommand.hpp"
|
#include "services/diagnosticCommand.hpp"
|
||||||
#include "utilities/concurrentHashTable.inline.hpp"
|
#include "utilities/concurrentHashTable.inline.hpp"
|
||||||
#include "utilities/concurrentHashTableTasks.inline.hpp"
|
#include "utilities/concurrentHashTableTasks.inline.hpp"
|
||||||
@ -737,6 +738,7 @@ void SymbolTable::clean_dead_entries(JavaThread* jt) {
|
|||||||
|
|
||||||
SymbolTableDeleteCheck stdc;
|
SymbolTableDeleteCheck stdc;
|
||||||
SymbolTableDoDelete stdd;
|
SymbolTableDoDelete stdd;
|
||||||
|
NativeHeapTrimmer::SuspendMark sm("symboltable");
|
||||||
{
|
{
|
||||||
TraceTime timer("Clean", TRACETIME_LOG(Debug, symboltable, perf));
|
TraceTime timer("Clean", TRACETIME_LOG(Debug, symboltable, perf));
|
||||||
while (bdt.do_task(jt, stdc, stdd)) {
|
while (bdt.do_task(jt, stdc, stdd)) {
|
||||||
|
@ -198,6 +198,7 @@ class outputStream;
|
|||||||
LOG_TAG(timer) \
|
LOG_TAG(timer) \
|
||||||
LOG_TAG(tlab) \
|
LOG_TAG(tlab) \
|
||||||
LOG_TAG(tracking) \
|
LOG_TAG(tracking) \
|
||||||
|
LOG_TAG(trimnative) /* trim native heap */ \
|
||||||
LOG_TAG(unload) /* Trace unloading of classes */ \
|
LOG_TAG(unload) /* Trace unloading of classes */ \
|
||||||
LOG_TAG(unmap) \
|
LOG_TAG(unmap) \
|
||||||
LOG_TAG(unshareable) \
|
LOG_TAG(unshareable) \
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
#include "runtime/os.hpp"
|
#include "runtime/os.hpp"
|
||||||
#include "runtime/task.hpp"
|
#include "runtime/task.hpp"
|
||||||
#include "runtime/threadCritical.hpp"
|
#include "runtime/threadCritical.hpp"
|
||||||
|
#include "runtime/trimNativeHeap.hpp"
|
||||||
#include "services/memTracker.inline.hpp"
|
#include "services/memTracker.inline.hpp"
|
||||||
#include "utilities/align.hpp"
|
#include "utilities/align.hpp"
|
||||||
#include "utilities/debug.hpp"
|
#include "utilities/debug.hpp"
|
||||||
@ -92,6 +93,7 @@ class ChunkPool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void clean() {
|
static void clean() {
|
||||||
|
NativeHeapTrimmer::SuspendMark sm("chunk pool cleaner");
|
||||||
for (int i = 0; i < _num_pools; i++) {
|
for (int i = 0; i < _num_pools; i++) {
|
||||||
_pools[i].prune();
|
_pools[i].prune();
|
||||||
}
|
}
|
||||||
|
@ -2587,6 +2587,14 @@ WB_ENTRY(jboolean, WB_SetVirtualThreadsNotifyJvmtiMode(JNIEnv* env, jobject wb,
|
|||||||
return result;
|
return result;
|
||||||
WB_END
|
WB_END
|
||||||
|
|
||||||
|
WB_ENTRY(void, WB_PreTouchMemory(JNIEnv* env, jobject wb, jlong addr, jlong size))
|
||||||
|
void* const from = (void*)addr;
|
||||||
|
void* const to = (void*)(addr + size);
|
||||||
|
if (from > to) {
|
||||||
|
os::pretouch_memory(from, to, os::vm_page_size());
|
||||||
|
}
|
||||||
|
WB_END
|
||||||
|
|
||||||
#define CC (char*)
|
#define CC (char*)
|
||||||
|
|
||||||
static JNINativeMethod methods[] = {
|
static JNINativeMethod methods[] = {
|
||||||
@ -2869,6 +2877,7 @@ static JNINativeMethod methods[] = {
|
|||||||
{CC"lockCritical", CC"()V", (void*)&WB_LockCritical},
|
{CC"lockCritical", CC"()V", (void*)&WB_LockCritical},
|
||||||
{CC"unlockCritical", CC"()V", (void*)&WB_UnlockCritical},
|
{CC"unlockCritical", CC"()V", (void*)&WB_UnlockCritical},
|
||||||
{CC"setVirtualThreadsNotifyJvmtiMode", CC"(Z)Z", (void*)&WB_SetVirtualThreadsNotifyJvmtiMode},
|
{CC"setVirtualThreadsNotifyJvmtiMode", CC"(Z)Z", (void*)&WB_SetVirtualThreadsNotifyJvmtiMode},
|
||||||
|
{CC"preTouchMemory", CC"(JJ)V", (void*)&WB_PreTouchMemory},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -1985,6 +1985,13 @@ const int ObjectAlignmentInBytes = 8;
|
|||||||
"1: monitors & legacy stack-locking (LM_LEGACY, default), " \
|
"1: monitors & legacy stack-locking (LM_LEGACY, default), " \
|
||||||
"2: monitors & new lightweight locking (LM_LIGHTWEIGHT)") \
|
"2: monitors & new lightweight locking (LM_LIGHTWEIGHT)") \
|
||||||
range(0, 2) \
|
range(0, 2) \
|
||||||
|
\
|
||||||
|
product(uint, TrimNativeHeapInterval, 0, EXPERIMENTAL, \
|
||||||
|
"Interval, in ms, at which the JVM will trim the native heap if " \
|
||||||
|
"the platform supports that. Lower values will reclaim memory " \
|
||||||
|
"more eagerly at the cost of higher overhead. A value of 0 " \
|
||||||
|
"(default) disables native heap trimming.") \
|
||||||
|
range(0, UINT_MAX) \
|
||||||
|
|
||||||
// end of RUNTIME_FLAGS
|
// end of RUNTIME_FLAGS
|
||||||
|
|
||||||
|
@ -70,6 +70,7 @@
|
|||||||
#include "runtime/task.hpp"
|
#include "runtime/task.hpp"
|
||||||
#include "runtime/threads.hpp"
|
#include "runtime/threads.hpp"
|
||||||
#include "runtime/timer.hpp"
|
#include "runtime/timer.hpp"
|
||||||
|
#include "runtime/trimNativeHeap.hpp"
|
||||||
#include "runtime/vmOperations.hpp"
|
#include "runtime/vmOperations.hpp"
|
||||||
#include "runtime/vmThread.hpp"
|
#include "runtime/vmThread.hpp"
|
||||||
#include "runtime/vm_version.hpp"
|
#include "runtime/vm_version.hpp"
|
||||||
@ -477,6 +478,8 @@ void before_exit(JavaThread* thread, bool halt) {
|
|||||||
StatSampler::disengage();
|
StatSampler::disengage();
|
||||||
StatSampler::destroy();
|
StatSampler::destroy();
|
||||||
|
|
||||||
|
NativeHeapTrimmer::cleanup();
|
||||||
|
|
||||||
// Stop concurrent GC threads
|
// Stop concurrent GC threads
|
||||||
Universe::heap()->stop();
|
Universe::heap()->stop();
|
||||||
|
|
||||||
|
@ -54,6 +54,7 @@
|
|||||||
#include "runtime/synchronizer.hpp"
|
#include "runtime/synchronizer.hpp"
|
||||||
#include "runtime/threads.hpp"
|
#include "runtime/threads.hpp"
|
||||||
#include "runtime/timer.hpp"
|
#include "runtime/timer.hpp"
|
||||||
|
#include "runtime/trimNativeHeap.hpp"
|
||||||
#include "runtime/vframe.hpp"
|
#include "runtime/vframe.hpp"
|
||||||
#include "runtime/vmThread.hpp"
|
#include "runtime/vmThread.hpp"
|
||||||
#include "utilities/align.hpp"
|
#include "utilities/align.hpp"
|
||||||
@ -1646,6 +1647,7 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
static size_t delete_monitors(GrowableArray<ObjectMonitor*>* delete_list) {
|
static size_t delete_monitors(GrowableArray<ObjectMonitor*>* delete_list) {
|
||||||
|
NativeHeapTrimmer::SuspendMark sm("monitor deletion");
|
||||||
size_t count = 0;
|
size_t count = 0;
|
||||||
for (ObjectMonitor* monitor: *delete_list) {
|
for (ObjectMonitor* monitor: *delete_list) {
|
||||||
delete monitor;
|
delete monitor;
|
||||||
|
@ -87,6 +87,7 @@
|
|||||||
#include "runtime/threadSMR.inline.hpp"
|
#include "runtime/threadSMR.inline.hpp"
|
||||||
#include "runtime/timer.hpp"
|
#include "runtime/timer.hpp"
|
||||||
#include "runtime/timerTrace.hpp"
|
#include "runtime/timerTrace.hpp"
|
||||||
|
#include "runtime/trimNativeHeap.hpp"
|
||||||
#include "runtime/vmOperations.hpp"
|
#include "runtime/vmOperations.hpp"
|
||||||
#include "runtime/vm_version.hpp"
|
#include "runtime/vm_version.hpp"
|
||||||
#include "services/attachListener.hpp"
|
#include "services/attachListener.hpp"
|
||||||
@ -759,6 +760,10 @@ jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if (NativeHeapTrimmer::enabled()) {
|
||||||
|
NativeHeapTrimmer::initialize();
|
||||||
|
}
|
||||||
|
|
||||||
// Always call even when there are not JVMTI environments yet, since environments
|
// Always call even when there are not JVMTI environments yet, since environments
|
||||||
// may be attached late and JVMTI must track phases of VM execution
|
// may be attached late and JVMTI must track phases of VM execution
|
||||||
JvmtiExport::enter_live_phase();
|
JvmtiExport::enter_live_phase();
|
||||||
|
275
src/hotspot/share/runtime/trimNativeHeap.cpp
Normal file
275
src/hotspot/share/runtime/trimNativeHeap.cpp
Normal file
@ -0,0 +1,275 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023 SAP SE. All rights reserved.
|
||||||
|
* Copyright (c) 2023 Red Hat Inc. 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 "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 is_stopped() const {
|
||||||
|
assert(_lock->is_locked(), "Must be");
|
||||||
|
return _stop;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 int64_t wait_ms = MAX2(1.0, to_ms(next_trim_time - tnow));
|
||||||
|
ml.wait(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 (std::nothrow) 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 {
|
||||||
|
// Don't pull lock during error reporting
|
||||||
|
Mutex* const lock = VMError::is_error_reported() ? nullptr : _lock;
|
||||||
|
int64_t num_trims = 0;
|
||||||
|
bool stopped = false;
|
||||||
|
uint16_t suspenders = 0;
|
||||||
|
{
|
||||||
|
MutexLocker ml(lock, 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");
|
||||||
|
}
|
||||||
|
}
|
69
src/hotspot/share/runtime/trimNativeHeap.hpp
Normal file
69
src/hotspot/share/runtime/trimNativeHeap.hpp
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023 SAP SE. All rights reserved.
|
||||||
|
* Copyright (c) 2023 Red Hat Inc. 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_RUNTIME_TRIMNATIVEHEAP_HPP
|
||||||
|
#define SHARE_RUNTIME_TRIMNATIVEHEAP_HPP
|
||||||
|
|
||||||
|
#include "memory/allStatic.hpp"
|
||||||
|
#include "runtime/globals.hpp"
|
||||||
|
|
||||||
|
class outputStream;
|
||||||
|
|
||||||
|
class NativeHeapTrimmer : public AllStatic {
|
||||||
|
|
||||||
|
// Pause periodic trim (if enabled).
|
||||||
|
static void suspend_periodic_trim(const char* reason);
|
||||||
|
|
||||||
|
// Unpause periodic trim (if enabled).
|
||||||
|
static void resume_periodic_trim(const char* reason);
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
static void initialize();
|
||||||
|
static void cleanup();
|
||||||
|
|
||||||
|
static inline bool enabled() { return TrimNativeHeapInterval > 0; }
|
||||||
|
|
||||||
|
static void print_state(outputStream* st);
|
||||||
|
|
||||||
|
// Pause periodic trimming while in scope; when leaving scope,
|
||||||
|
// resume periodic trimming.
|
||||||
|
struct SuspendMark {
|
||||||
|
const char* const _reason;
|
||||||
|
SuspendMark(const char* reason = "unknown") : _reason(reason) {
|
||||||
|
if (NativeHeapTrimmer::enabled()) {
|
||||||
|
suspend_periodic_trim(_reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
~SuspendMark() {
|
||||||
|
if (NativeHeapTrimmer::enabled()) {
|
||||||
|
resume_periodic_trim(_reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // SHARE_RUNTIME_TRIMNATIVEHEAP_HPP
|
@ -54,6 +54,7 @@
|
|||||||
#include "runtime/stackOverflow.hpp"
|
#include "runtime/stackOverflow.hpp"
|
||||||
#include "runtime/threads.hpp"
|
#include "runtime/threads.hpp"
|
||||||
#include "runtime/threadSMR.hpp"
|
#include "runtime/threadSMR.hpp"
|
||||||
|
#include "runtime/trimNativeHeap.hpp"
|
||||||
#include "runtime/vmThread.hpp"
|
#include "runtime/vmThread.hpp"
|
||||||
#include "runtime/vmOperations.hpp"
|
#include "runtime/vmOperations.hpp"
|
||||||
#include "runtime/vm_version.hpp"
|
#include "runtime/vm_version.hpp"
|
||||||
@ -1289,9 +1290,13 @@ void VMError::report(outputStream* st, bool _verbose) {
|
|||||||
|
|
||||||
STEP_IF("Native Memory Tracking", _verbose)
|
STEP_IF("Native Memory Tracking", _verbose)
|
||||||
MemTracker::error_report(st);
|
MemTracker::error_report(st);
|
||||||
|
st->cr();
|
||||||
|
|
||||||
|
STEP_IF("printing periodic trim state", _verbose)
|
||||||
|
NativeHeapTrimmer::print_state(st);
|
||||||
|
st->cr();
|
||||||
|
|
||||||
STEP_IF("printing system", _verbose)
|
STEP_IF("printing system", _verbose)
|
||||||
st->cr();
|
|
||||||
st->print_cr("--------------- S Y S T E M ---------------");
|
st->print_cr("--------------- S Y S T E M ---------------");
|
||||||
st->cr();
|
st->cr();
|
||||||
|
|
||||||
@ -1458,10 +1463,14 @@ void VMError::print_vm_info(outputStream* st) {
|
|||||||
// STEP("Native Memory Tracking")
|
// STEP("Native Memory Tracking")
|
||||||
|
|
||||||
MemTracker::error_report(st);
|
MemTracker::error_report(st);
|
||||||
|
st->cr();
|
||||||
|
|
||||||
|
// STEP("printing periodic trim state")
|
||||||
|
NativeHeapTrimmer::print_state(st);
|
||||||
|
st->cr();
|
||||||
|
|
||||||
|
|
||||||
// STEP("printing system")
|
// STEP("printing system")
|
||||||
|
|
||||||
st->cr();
|
|
||||||
st->print_cr("--------------- S Y S T E M ---------------");
|
st->print_cr("--------------- S Y S T E M ---------------");
|
||||||
st->cr();
|
st->cr();
|
||||||
|
|
||||||
|
101
test/hotspot/gtest/runtime/test_trim_native.cpp
Normal file
101
test/hotspot/gtest/runtime/test_trim_native.cpp
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023 Red Hat Inc. All rights reserved.
|
||||||
|
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* Copyright (c) 1997, 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "precompiled.hpp"
|
||||||
|
#include "runtime/os.hpp"
|
||||||
|
#include "runtime/trimNativeHeap.hpp"
|
||||||
|
#include "utilities/globalDefinitions.hpp"
|
||||||
|
#include "utilities/ostream.hpp"
|
||||||
|
#include "testutils.hpp"
|
||||||
|
#include "unittest.hpp"
|
||||||
|
|
||||||
|
using ::testing::HasSubstr;
|
||||||
|
|
||||||
|
// Check the state of the trimmer via print_state; returns the suspend count
|
||||||
|
static int check_trim_state() {
|
||||||
|
char buf [1024];
|
||||||
|
stringStream ss(buf, sizeof(buf));
|
||||||
|
NativeHeapTrimmer::print_state(&ss);
|
||||||
|
if (NativeHeapTrimmer::enabled()) {
|
||||||
|
assert(TrimNativeHeapInterval > 0, "Sanity");
|
||||||
|
EXPECT_THAT(buf, HasSubstr("Periodic native trim enabled"));
|
||||||
|
|
||||||
|
const char* s = ::strstr(buf, "Trims performed");
|
||||||
|
EXPECT_NOT_NULL(s);
|
||||||
|
|
||||||
|
uint64_t num_trims = 0;
|
||||||
|
int suspend_count = 0;
|
||||||
|
int stopped = 0;
|
||||||
|
EXPECT_EQ(::sscanf(s, "Trims performed: " UINT64_FORMAT ", current suspend count: %d, stopped: %d",
|
||||||
|
&num_trims, &suspend_count, &stopped), 3);
|
||||||
|
|
||||||
|
// Number of trims we can reasonably expect should be limited
|
||||||
|
const double fudge_factor = 1.5;
|
||||||
|
const uint64_t elapsed_ms = (uint64_t)(os::elapsedTime() * fudge_factor * 1000.0);
|
||||||
|
const uint64_t max_num_trims = (elapsed_ms / TrimNativeHeapInterval) + 1;
|
||||||
|
EXPECT_LE(num_trims, max_num_trims);
|
||||||
|
|
||||||
|
// We should not be stopped
|
||||||
|
EXPECT_EQ(stopped, 0);
|
||||||
|
|
||||||
|
// Suspend count must not underflow
|
||||||
|
EXPECT_GE(suspend_count, 0);
|
||||||
|
return suspend_count;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
EXPECT_THAT(buf, HasSubstr("Periodic native trim disabled"));
|
||||||
|
EXPECT_THAT(buf, Not(HasSubstr("Trims performed")));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_VM(os, TrimNative) {
|
||||||
|
|
||||||
|
if (!NativeHeapTrimmer::enabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try recursive pausing. This tests that we are able to pause, that pauses stack,
|
||||||
|
// and that stacking works within the same thread.
|
||||||
|
int c1 = 0, c2 = 0, c3 = 0;
|
||||||
|
{
|
||||||
|
NativeHeapTrimmer::SuspendMark sm1("Test1");
|
||||||
|
c1 = check_trim_state();
|
||||||
|
{
|
||||||
|
NativeHeapTrimmer::SuspendMark sm2("Test2");
|
||||||
|
c2 = check_trim_state();
|
||||||
|
{
|
||||||
|
NativeHeapTrimmer::SuspendMark sm3("Test3");
|
||||||
|
c3 = check_trim_state();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// We also check the state: the suspend count should go up. But since we don't know
|
||||||
|
// whether concurrent code will have increased the suspend count too, this is fuzzy and
|
||||||
|
// we must avoid intermittent false positives.
|
||||||
|
EXPECT_GT(c2, c1);
|
||||||
|
EXPECT_GT(c3, c2);
|
||||||
|
}
|
32
test/hotspot/jtreg/gtest/NativeHeapTrimmerGtest.java
Normal file
32
test/hotspot/jtreg/gtest/NativeHeapTrimmerGtest.java
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* Copyright (c) 2023 Red Hat, Inc. 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 Run a subset of gtests with the native trimmer activated.
|
||||||
|
* @library /test/lib
|
||||||
|
* @modules java.base/jdk.internal.misc
|
||||||
|
* java.xml
|
||||||
|
* @run main/native GTestWrapper --gtest_filter=os.trim* -Xlog:trimnative -XX:+UnlockExperimentalVMOptions -XX:TrimNativeHeapInterval=100
|
||||||
|
*/
|
309
test/hotspot/jtreg/runtime/os/TestTrimNative.java
Normal file
309
test/hotspot/jtreg/runtime/os/TestTrimNative.java
Normal file
@ -0,0 +1,309 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023 SAP SE. All rights reserved.
|
||||||
|
* Copyright (c) 2023 Red Hat, Inc. 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @test id=trimNative
|
||||||
|
* @requires (os.family=="linux") & !vm.musl
|
||||||
|
* @modules java.base/jdk.internal.misc
|
||||||
|
* @library /test/lib
|
||||||
|
* @build jdk.test.whitebox.WhiteBox
|
||||||
|
* @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
|
||||||
|
* @run driver TestTrimNative trimNative
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @test id=trimNativeHighInterval
|
||||||
|
* @summary High interval trimming should not even kick in for short program runtimes
|
||||||
|
* @requires (os.family=="linux") & !vm.musl
|
||||||
|
* @modules java.base/jdk.internal.misc
|
||||||
|
* @library /test/lib
|
||||||
|
* @build jdk.test.whitebox.WhiteBox
|
||||||
|
* @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
|
||||||
|
* @run driver TestTrimNative trimNativeHighInterval
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @test id=trimNativeLowInterval
|
||||||
|
* @summary Very low (sub-second) interval, nothing should explode
|
||||||
|
* @requires (os.family=="linux") & !vm.musl
|
||||||
|
* @modules java.base/jdk.internal.misc
|
||||||
|
* @library /test/lib
|
||||||
|
* @build jdk.test.whitebox.WhiteBox
|
||||||
|
* @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
|
||||||
|
* @run driver TestTrimNative trimNativeLowInterval
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @test id=testOffByDefault
|
||||||
|
* @summary Test that trimming is disabled by default
|
||||||
|
* @requires (os.family=="linux") & !vm.musl
|
||||||
|
* @modules java.base/jdk.internal.misc
|
||||||
|
* @library /test/lib
|
||||||
|
* @build jdk.test.whitebox.WhiteBox
|
||||||
|
* @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
|
||||||
|
* @run driver TestTrimNative testOffByDefault
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @test id=testOffExplicit
|
||||||
|
* @summary Test that trimming can be disabled explicitly
|
||||||
|
* @requires (os.family=="linux") & !vm.musl
|
||||||
|
* @modules java.base/jdk.internal.misc
|
||||||
|
* @library /test/lib
|
||||||
|
* @build jdk.test.whitebox.WhiteBox
|
||||||
|
* @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
|
||||||
|
* @run driver TestTrimNative testOffExplicit
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @test id=testOffOnNonCompliantPlatforms
|
||||||
|
* @summary Test that trimming is correctly reported as unavailable if unavailable
|
||||||
|
* @requires (os.family!="linux") | vm.musl
|
||||||
|
* @modules java.base/jdk.internal.misc
|
||||||
|
* @library /test/lib
|
||||||
|
* @build jdk.test.whitebox.WhiteBox
|
||||||
|
* @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
|
||||||
|
* @run driver TestTrimNative testOffOnNonCompliantPlatforms
|
||||||
|
*/
|
||||||
|
|
||||||
|
import jdk.test.lib.Platform;
|
||||||
|
import jdk.test.lib.process.OutputAnalyzer;
|
||||||
|
import jdk.test.lib.process.ProcessTools;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import jdk.test.whitebox.WhiteBox;
|
||||||
|
|
||||||
|
public class TestTrimNative {
|
||||||
|
|
||||||
|
// Actual RSS increase is a lot larger than 4 MB. Depends on glibc overhead, and NMT malloc headers in debug VMs.
|
||||||
|
// We need small-grained allocations to make sure they actually increase RSS (all touched) and to see the
|
||||||
|
// glibc-retaining-memory effect.
|
||||||
|
static final int szAllocations = 128;
|
||||||
|
static final int totalAllocationsSize = 128 * 1024 * 1024; // 128 MB total
|
||||||
|
static final int numAllocations = totalAllocationsSize / szAllocations;
|
||||||
|
|
||||||
|
static long[] ptrs = new long[numAllocations];
|
||||||
|
|
||||||
|
enum Unit {
|
||||||
|
B(1), K(1024), M(1024*1024), G(1024*1024*1024);
|
||||||
|
public final long size;
|
||||||
|
Unit(long size) { this.size = size; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String[] prepareOptions(String[] extraVMOptions, String[] programOptions) {
|
||||||
|
List<String> allOptions = new ArrayList<String>();
|
||||||
|
if (extraVMOptions != null) {
|
||||||
|
allOptions.addAll(Arrays.asList(extraVMOptions));
|
||||||
|
}
|
||||||
|
allOptions.add("-Xmx128m");
|
||||||
|
allOptions.add("-Xms128m"); // Stabilize RSS
|
||||||
|
allOptions.add("-XX:+AlwaysPreTouch"); // Stabilize RSS
|
||||||
|
allOptions.add("-XX:+UnlockDiagnosticVMOptions"); // For whitebox
|
||||||
|
allOptions.add("-XX:+WhiteBoxAPI");
|
||||||
|
allOptions.add("-Xbootclasspath/a:.");
|
||||||
|
allOptions.add("-XX:-ExplicitGCInvokesConcurrent"); // Invoke explicit GC on System.gc
|
||||||
|
allOptions.add("-Xlog:trimnative=debug");
|
||||||
|
allOptions.add("--add-exports=java.base/jdk.internal.misc=ALL-UNNAMED");
|
||||||
|
if (programOptions != null) {
|
||||||
|
allOptions.addAll(Arrays.asList(programOptions));
|
||||||
|
}
|
||||||
|
return allOptions.toArray(new String[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static OutputAnalyzer runTestWithOptions(String[] extraOptions, String[] programOptions) throws IOException {
|
||||||
|
ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(prepareOptions(extraOptions, programOptions));
|
||||||
|
OutputAnalyzer output = new OutputAnalyzer(pb.start());
|
||||||
|
output.shouldHaveExitValue(0);
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void checkExpectedLogMessages(OutputAnalyzer output, boolean expectEnabled,
|
||||||
|
int expectedInterval) {
|
||||||
|
if (expectEnabled) {
|
||||||
|
output.shouldContain("Periodic native trim enabled (interval: " + expectedInterval + " ms");
|
||||||
|
output.shouldContain("Native heap trimmer start");
|
||||||
|
output.shouldContain("Native heap trimmer stop");
|
||||||
|
} else {
|
||||||
|
output.shouldNotContain("Periodic native trim enabled");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given JVM output, look for one or more log lines that describes a successful negative trim. The total amount
|
||||||
|
* of trims should be matching about what the test program allocated.
|
||||||
|
* @param output
|
||||||
|
* @param minTrimsExpected min number of periodic trim lines expected in UL log
|
||||||
|
* @param maxTrimsExpected min number of periodic trim lines expected in UL log
|
||||||
|
*/
|
||||||
|
private static void parseOutputAndLookForNegativeTrim(OutputAnalyzer output, int minTrimsExpected,
|
||||||
|
int maxTrimsExpected) {
|
||||||
|
output.reportDiagnosticSummary();
|
||||||
|
List<String> lines = output.asLines();
|
||||||
|
Pattern pat = Pattern.compile(".*\\[trimnative\\] Periodic Trim \\(\\d+\\): (\\d+)([BKMG])->(\\d+)([BKMG]).*");
|
||||||
|
int numTrimsFound = 0;
|
||||||
|
long rssReductionTotal = 0;
|
||||||
|
for (String line : lines) {
|
||||||
|
Matcher mat = pat.matcher(line);
|
||||||
|
if (mat.matches()) {
|
||||||
|
long rss1 = Long.parseLong(mat.group(1)) * Unit.valueOf(mat.group(2)).size;
|
||||||
|
long rss2 = Long.parseLong(mat.group(3)) * Unit.valueOf(mat.group(4)).size;
|
||||||
|
if (rss1 > rss2) {
|
||||||
|
rssReductionTotal += (rss1 - rss2);
|
||||||
|
}
|
||||||
|
numTrimsFound ++;
|
||||||
|
}
|
||||||
|
if (numTrimsFound > maxTrimsExpected) {
|
||||||
|
throw new RuntimeException("Abnormal high number of periodic trim attempts found (more than " + maxTrimsExpected +
|
||||||
|
"). Does the interval setting not work?");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (numTrimsFound < minTrimsExpected) {
|
||||||
|
throw new RuntimeException("We found fewer (periodic) trim lines in UL log than expected (expected at least " + minTrimsExpected +
|
||||||
|
", found " + numTrimsFound + ").");
|
||||||
|
}
|
||||||
|
if (maxTrimsExpected > 0) {
|
||||||
|
// This is very fuzzy. Test program malloced X bytes, then freed them again and trimmed. But the log line prints change in RSS.
|
||||||
|
// Which, of course, is influenced by a lot of other factors. But we expect to see *some* reasonable reduction in RSS
|
||||||
|
// due to trimming.
|
||||||
|
float fudge = 0.5f;
|
||||||
|
// On ppc, we see a vastly diminished return (~3M reduction instead of ~200), I suspect because of the underlying
|
||||||
|
// 64k pages lead to a different geometry. Manual tests with larger reclaim sizes show that autotrim works. For
|
||||||
|
// this test, we just reduce the fudge factor.
|
||||||
|
if (Platform.isPPC()) { // le and be both
|
||||||
|
fudge = 0.01f;
|
||||||
|
}
|
||||||
|
long expectedMinimalReduction = (long) (totalAllocationsSize * fudge);
|
||||||
|
if (rssReductionTotal < expectedMinimalReduction) {
|
||||||
|
throw new RuntimeException("We did not see the expected RSS reduction in the UL log. Expected (with fudge)" +
|
||||||
|
" to see at least a combined reduction of " + expectedMinimalReduction + ".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Tester {
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
long sleeptime = Long.parseLong(args[0]);
|
||||||
|
|
||||||
|
System.out.println("Will spike now...");
|
||||||
|
WhiteBox wb = WhiteBox.getWhiteBox();
|
||||||
|
for (int i = 0; i < numAllocations; i++) {
|
||||||
|
ptrs[i] = wb.NMTMalloc(szAllocations);
|
||||||
|
wb.preTouchMemory(ptrs[i], szAllocations);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < numAllocations; i++) {
|
||||||
|
wb.NMTFree(ptrs[i]);
|
||||||
|
}
|
||||||
|
System.out.println("Done spiking.");
|
||||||
|
|
||||||
|
System.out.println("GC...");
|
||||||
|
System.gc();
|
||||||
|
|
||||||
|
// give GC time to react
|
||||||
|
System.out.println("Sleeping...");
|
||||||
|
Thread.sleep(sleeptime);
|
||||||
|
System.out.println("Done.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
|
||||||
|
if (args.length == 0) {
|
||||||
|
throw new RuntimeException("Argument error");
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (args[0]) {
|
||||||
|
case "trimNative": {
|
||||||
|
long trimInterval = 500; // twice per second
|
||||||
|
long ms1 = System.currentTimeMillis();
|
||||||
|
OutputAnalyzer output = runTestWithOptions(
|
||||||
|
new String[] { "-XX:+UnlockExperimentalVMOptions", "-XX:TrimNativeHeapInterval=" + trimInterval },
|
||||||
|
new String[] { TestTrimNative.Tester.class.getName(), "5000" }
|
||||||
|
);
|
||||||
|
long ms2 = System.currentTimeMillis();
|
||||||
|
long runtime_ms = ms2 - ms1;
|
||||||
|
|
||||||
|
checkExpectedLogMessages(output, true, 500);
|
||||||
|
|
||||||
|
long maxTrimsExpected = runtime_ms / trimInterval;
|
||||||
|
long minTrimsExpected = maxTrimsExpected / 2;
|
||||||
|
parseOutputAndLookForNegativeTrim(output, (int) minTrimsExpected, (int) maxTrimsExpected);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case "trimNativeHighInterval": {
|
||||||
|
OutputAnalyzer output = runTestWithOptions(
|
||||||
|
new String[] { "-XX:+UnlockExperimentalVMOptions", "-XX:TrimNativeHeapInterval=" + Integer.MAX_VALUE },
|
||||||
|
new String[] { TestTrimNative.Tester.class.getName(), "5000" }
|
||||||
|
);
|
||||||
|
checkExpectedLogMessages(output, true, Integer.MAX_VALUE);
|
||||||
|
// We should not see any trims since the interval would prevent them
|
||||||
|
parseOutputAndLookForNegativeTrim(output, 0, 0);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case "trimNativeLowInterval": {
|
||||||
|
OutputAnalyzer output = runTestWithOptions(
|
||||||
|
new String[] { "-XX:+UnlockExperimentalVMOptions", "-XX:TrimNativeHeapInterval=1" },
|
||||||
|
new String[] { TestTrimNative.Tester.class.getName(), "0" }
|
||||||
|
);
|
||||||
|
checkExpectedLogMessages(output, true, 1);
|
||||||
|
parseOutputAndLookForNegativeTrim(output, 1, 3000);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case "testOffOnNonCompliantPlatforms": {
|
||||||
|
OutputAnalyzer output = runTestWithOptions(
|
||||||
|
new String[] { "-XX:+UnlockExperimentalVMOptions", "-XX:TrimNativeHeapInterval=1" },
|
||||||
|
new String[] { "-version" }
|
||||||
|
);
|
||||||
|
checkExpectedLogMessages(output, false, 0);
|
||||||
|
parseOutputAndLookForNegativeTrim(output, 0, 0);
|
||||||
|
// The following output is expected to be printed with warning level, so it should not need -Xlog
|
||||||
|
output.shouldContain("[warning][trimnative] Native heap trim is not supported on this platform");
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case "testOffExplicit": {
|
||||||
|
OutputAnalyzer output = runTestWithOptions(
|
||||||
|
new String[] { "-XX:+UnlockExperimentalVMOptions", "-XX:TrimNativeHeapInterval=0" },
|
||||||
|
new String[] { "-version" }
|
||||||
|
);
|
||||||
|
checkExpectedLogMessages(output, false, 0);
|
||||||
|
parseOutputAndLookForNegativeTrim(output, 0, 0);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case "testOffByDefault": {
|
||||||
|
OutputAnalyzer output = runTestWithOptions(null, new String[] { "-version" } );
|
||||||
|
checkExpectedLogMessages(output, false, 0);
|
||||||
|
parseOutputAndLookForNegativeTrim(output, 0, 0);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new RuntimeException("Invalid test " + args[0]);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -22,6 +22,7 @@
|
|||||||
* questions.
|
* questions.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import jdk.test.lib.Platform;
|
||||||
import org.testng.annotations.Test;
|
import org.testng.annotations.Test;
|
||||||
import jdk.test.lib.dcmd.CommandExecutor;
|
import jdk.test.lib.dcmd.CommandExecutor;
|
||||||
import jdk.test.lib.dcmd.JMXExecutor;
|
import jdk.test.lib.dcmd.JMXExecutor;
|
||||||
@ -31,7 +32,7 @@ import jdk.test.lib.process.OutputAnalyzer;
|
|||||||
* @test
|
* @test
|
||||||
* @summary Test of diagnostic command VM.trim_libc_heap
|
* @summary Test of diagnostic command VM.trim_libc_heap
|
||||||
* @library /test/lib
|
* @library /test/lib
|
||||||
* @requires (os.family=="linux") & !vm.musl
|
* @requires os.family == "linux"
|
||||||
* @modules java.base/jdk.internal.misc
|
* @modules java.base/jdk.internal.misc
|
||||||
* java.compiler
|
* java.compiler
|
||||||
* java.management
|
* java.management
|
||||||
@ -42,7 +43,11 @@ public class TrimLibcHeapTest {
|
|||||||
public void run(CommandExecutor executor) {
|
public void run(CommandExecutor executor) {
|
||||||
OutputAnalyzer output = executor.execute("System.trim_native_heap");
|
OutputAnalyzer output = executor.execute("System.trim_native_heap");
|
||||||
output.reportDiagnosticSummary();
|
output.reportDiagnosticSummary();
|
||||||
output.shouldMatch(".*Trim native heap: RSS\\+Swap: \\d+[BKM]->\\d+[BKM].*");
|
if (Platform.isMusl()) {
|
||||||
|
output.shouldContain("Not available");
|
||||||
|
} else {
|
||||||
|
output.shouldMatch("Trim native heap: RSS\\+Swap: \\d+[BKMG]->\\d+[BKMG] \\(-\\d+[BKMG]\\)");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -767,4 +767,6 @@ public class WhiteBox {
|
|||||||
public native void unlockCritical();
|
public native void unlockCritical();
|
||||||
|
|
||||||
public native boolean setVirtualThreadsNotifyJvmtiMode(boolean enabled);
|
public native boolean setVirtualThreadsNotifyJvmtiMode(boolean enabled);
|
||||||
|
|
||||||
|
public native void preTouchMemory(long addr, long size);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user