diff --git a/src/hotspot/share/classfile/stringTable.cpp b/src/hotspot/share/classfile/stringTable.cpp index 89381b1a785f1..942506b270eb8 100644 --- a/src/hotspot/share/classfile/stringTable.cpp +++ b/src/hotspot/share/classfile/stringTable.cpp @@ -37,6 +37,7 @@ #include "gc/shared/oopStorage.inline.hpp" #include "gc/shared/oopStorageSet.hpp" #include "gc/shared/stringdedup/stringDedup.hpp" +#include "gc/shared/trimNative.hpp" #include "logging/log.hpp" #include "logging/logStream.hpp" #include "memory/allocation.inline.hpp" @@ -456,6 +457,7 @@ void StringTable::clean_dead_entries(JavaThread* jt) { StringTableDeleteCheck stdc; StringTableDoDelete stdd; + TrimNative::PauseMark trim_native_pause("stringtable"); { TraceTime timer("Clean", TRACETIME_LOG(Debug, stringtable, perf)); while(bdt.do_task(jt, stdc, stdd)) { diff --git a/src/hotspot/share/classfile/symbolTable.cpp b/src/hotspot/share/classfile/symbolTable.cpp index 61d5ba576b54a..3842e67680a4e 100644 --- a/src/hotspot/share/classfile/symbolTable.cpp +++ b/src/hotspot/share/classfile/symbolTable.cpp @@ -30,6 +30,7 @@ #include "classfile/compactHashtable.hpp" #include "classfile/javaClasses.hpp" #include "classfile/symbolTable.hpp" +#include "gc/shared/trimNative.hpp" #include "memory/allocation.inline.hpp" #include "memory/metaspaceClosure.hpp" #include "memory/resourceArea.hpp" @@ -737,6 +738,7 @@ void SymbolTable::clean_dead_entries(JavaThread* jt) { SymbolTableDeleteCheck stdc; SymbolTableDoDelete stdd; + TrimNative::PauseMark trim_native_pause("symboltable"); { TraceTime timer("Clean", TRACETIME_LOG(Debug, symboltable, perf)); while (bdt.do_task(jt, stdc, stdd)) { diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp index d432a7a7ae74c..9e27c77327a21 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp @@ -91,6 +91,7 @@ #include "gc/shared/taskqueue.inline.hpp" #include "gc/shared/taskTerminator.hpp" #include "gc/shared/tlab_globals.hpp" +#include "gc/shared/trimNative.hpp" #include "gc/shared/workerPolicy.hpp" #include "gc/shared/weakProcessor.inline.hpp" #include "logging/log.hpp" @@ -914,6 +915,8 @@ bool G1CollectedHeap::do_full_collection(bool clear_all_soft_refs, return false; } + TrimNative::PauseMark trim_native_pause("gc"); + const bool do_clear_all_soft_refs = clear_all_soft_refs || soft_ref_policy()->should_clear_all_soft_refs(); @@ -2549,6 +2552,7 @@ void G1CollectedHeap::retire_tlabs() { void G1CollectedHeap::do_collection_pause_at_safepoint_helper() { ResourceMark rm; + TrimNative::PauseMark trim_native_pause("gc"); IsGCActiveMark active_gc_mark; GCIdMark gc_id_mark; diff --git a/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp b/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp index 96bc773646746..fd8feff57f8e8 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp @@ -55,6 +55,7 @@ #include "gc/shared/suspendibleThreadSet.hpp" #include "gc/shared/taskTerminator.hpp" #include "gc/shared/taskqueue.inline.hpp" +#include "gc/shared/trimNative.hpp" #include "gc/shared/weakProcessor.inline.hpp" #include "gc/shared/workerPolicy.hpp" #include "jvm.h" @@ -1234,6 +1235,8 @@ void G1ConcurrentMark::remark() { return; } + TrimNative::PauseMark trim_native_pause("gc"); + G1Policy* policy = _g1h->policy(); policy->record_concurrent_mark_remark_start(); @@ -1448,6 +1451,8 @@ void G1ConcurrentMark::cleanup() { return; } + TrimNative::PauseMark trim_native_pause("gc"); + G1Policy* policy = _g1h->policy(); policy->record_concurrent_mark_cleanup_start(); diff --git a/src/hotspot/share/gc/g1/g1FullCollector.cpp b/src/hotspot/share/gc/g1/g1FullCollector.cpp index 4381d515c6a56..c7ca331a09285 100644 --- a/src/hotspot/share/gc/g1/g1FullCollector.cpp +++ b/src/hotspot/share/gc/g1/g1FullCollector.cpp @@ -178,6 +178,7 @@ class PrepareRegionsClosure : public HeapRegionClosure { }; void G1FullCollector::prepare_collection() { + _heap->policy()->record_full_collection_start(); // Verification needs the bitmap, so we should clear the bitmap only later. diff --git a/src/hotspot/share/gc/parallel/psParallelCompact.cpp b/src/hotspot/share/gc/parallel/psParallelCompact.cpp index d0839cf576baf..740a5de221796 100644 --- a/src/hotspot/share/gc/parallel/psParallelCompact.cpp +++ b/src/hotspot/share/gc/parallel/psParallelCompact.cpp @@ -49,6 +49,7 @@ #include "gc/shared/gcTimer.hpp" #include "gc/shared/gcTrace.hpp" #include "gc/shared/gcTraceTime.inline.hpp" +#include "gc/shared/trimNative.hpp" #include "gc/shared/isGCActiveMark.hpp" #include "gc/shared/oopStorage.inline.hpp" #include "gc/shared/oopStorageSet.inline.hpp" @@ -1709,6 +1710,9 @@ bool PSParallelCompact::invoke_no_policy(bool maximum_heap_compaction) { return false; } + // Pause native trimming for the duration of the GC + TrimNative::PauseMark trim_native_pause("gc"); + ParallelScavengeHeap* heap = ParallelScavengeHeap::heap(); GCIdMark gc_id_mark; diff --git a/src/hotspot/share/gc/shared/gc_globals.hpp b/src/hotspot/share/gc/shared/gc_globals.hpp index 4cfb77d02c19b..e698a75b86fbc 100644 --- a/src/hotspot/share/gc/shared/gc_globals.hpp +++ b/src/hotspot/share/gc/shared/gc_globals.hpp @@ -690,8 +690,19 @@ product(uint, GCCardSizeInBytes, 512, \ "Card table entry size (in bytes) for card based collectors") \ range(128, NOT_LP64(512) LP64_ONLY(1024)) \ - constraint(GCCardSizeInBytesConstraintFunc,AtParse) - // end of GC_FLAGS + constraint(GCCardSizeInBytesConstraintFunc,AtParse) \ + \ + product(bool, TrimNativeHeap, false, EXPERIMENTAL, \ + "GC will attempt to trim the native heap periodically. By " \ + "default, the interval time is 60 seconds, but can be " \ + "changed using TrimNativeHeapInterval.") \ + \ + product(uint, TrimNativeHeapInterval, 60, EXPERIMENTAL, \ + "If TrimNativeHeap is enabled: interval, in seconds, in which " \ + "the GC will attempt to trim the native heap.") \ + range(1, UINT_MAX) \ + \ + // end of GC_FLAGS DECLARE_FLAGS(GC_FLAGS) diff --git a/src/hotspot/share/gc/shared/genCollectedHeap.cpp b/src/hotspot/share/gc/shared/genCollectedHeap.cpp index f7e93d7d98433..4b858c77f537b 100644 --- a/src/hotspot/share/gc/shared/genCollectedHeap.cpp +++ b/src/hotspot/share/gc/shared/genCollectedHeap.cpp @@ -56,6 +56,7 @@ #include "gc/shared/scavengableNMethods.hpp" #include "gc/shared/space.hpp" #include "gc/shared/strongRootsScope.hpp" +#include "gc/shared/trimNative.hpp" #include "gc/shared/weakProcessor.hpp" #include "gc/shared/workerThread.hpp" #include "memory/iterator.hpp" @@ -442,6 +443,8 @@ void GenCollectedHeap::do_collection(bool full, "the requesting thread should have the Heap_lock"); guarantee(!is_gc_active(), "collection is not reentrant"); + TrimNative::PauseMark trim_native_pause("gc"); + if (GCLocker::check_active_before_gc()) { return; // GC is disabled (e.g. JNI GetXXXCritical operation) } diff --git a/src/hotspot/share/gc/shared/trimNative.cpp b/src/hotspot/share/gc/shared/trimNative.cpp new file mode 100644 index 0000000000000..1b67eb4fd83ce --- /dev/null +++ b/src/hotspot/share/gc/shared/trimNative.cpp @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2023 SAP SE. All rights reserved. + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * + * 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 + * questioSns. + * + */ + +#include "precompiled.hpp" +#include "gc/shared/concurrentGCThread.hpp" +#include "gc/shared/gc_globals.hpp" +#include "gc/shared/trimNative.hpp" +#include "logging/log.hpp" +#include "runtime/globals_extension.hpp" +#include "runtime/mutex.hpp" +#include "runtime/mutexLocker.hpp" +#include "runtime/os.inline.hpp" +#include "utilities/debug.hpp" +#include "utilities/globalDefinitions.hpp" +#include "utilities/ticks.hpp" + +class NativeTrimmerThread : public ConcurrentGCThread { + + Monitor* _lock; + + int64_t _next_trim_time; + + // Pausing + int _pausers; + int64_t _next_trim_time_saved; + + static const int64_t never = INT64_MAX; + + static int64_t now() { return os::javaTimeMillis(); } + + void run_service() override { + + log_info(gc, trim)("NativeTrimmer start."); + + int64_t ntt = 0; + int64_t tnow = 0; + + for (;;) { + // 1 - Wait for _next_trim_time. Handle spurious wakeups and shutdown. + { + MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag); + do { + tnow = now(); + ntt = _next_trim_time; + if (ntt == never) { + ml.wait(0); // infinite sleep + } else if (ntt > tnow) { + ml.wait(ntt - tnow); // sleep till next point + } + if (should_terminate()) { + log_info(gc, trim)("NativeTrimmer stop."); + return; + } + tnow = now(); + ntt = _next_trim_time; + } while (ntt > tnow); + } + + // 2 - Trim outside of lock protection. + execute_trim_and_log(); + + // 3 - Update _next_trim_time; but give concurrent setters preference. + { + MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag); + tnow = now(); + int64_t ntt2 = _next_trim_time; + + if (ntt2 == ntt) { // not changed concurrently? + _next_trim_time = tnow + (TrimNativeHeapInterval * 1000); + } + } // Mutex scope + } + } + + void stop_service() override { + MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag); + ml.notify_all(); + } + + // Execute the native trim, log results. + void execute_trim_and_log() const { + assert(os::can_trim_native_heap(), "Unexpected"); + const int64_t tnow = now(); + os::size_change_t sc; + Ticks start = Ticks::now(); + log_debug(gc, trim)("Trim native heap started..."); + if (os::trim_native_heap(&sc)) { + Tickspan trim_time = (Ticks::now() - start); + 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(gc, trim)("Trim native heap: RSS+Swap: " PROPERFMT "->" PROPERFMT " (%c" PROPERFMT "), %1.3fms", + PROPERFMTARGS(sc.before), PROPERFMTARGS(sc.after), sign, PROPERFMTARGS(delta), + trim_time.seconds() * 1000); + } else { + log_info(gc, trim)("Trim native heap (no details)"); + } + } + } + +public: + + NativeTrimmerThread() : + _lock(new (std::nothrow) PaddedMonitor(Mutex::nosafepoint, "NativeTrimmer_lock")), + _next_trim_time(0), + _pausers(0), + _next_trim_time_saved(0) + { + set_name("Native Heap Trimmer"); + _next_trim_time = now() + TrimNativeHeapInterval * 1000; + create_and_start(); + } + + void pause(const char* reason) { + assert(TrimNativeHeap, "Only call if enabled"); + assert(TrimNativeHeapInterval > 0, "Only call if periodic trimming is enabled"); + int lvl = 0; + { + MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag); + if (_pausers == 0) { + _next_trim_time_saved = _next_trim_time; + _next_trim_time = never; + } + lvl = ++_pausers; + // No need to wakeup trimmer + } + log_debug(gc, trim)("NativeTrimmer pause (%s) (%d)", reason, lvl); + } + + void unpause(const char* reason) { + assert(TrimNativeHeap, "Only call if enabled"); + assert(TrimNativeHeapInterval > 0, "Only call if periodic trimming is enabled"); + int lvl = 0; + { + MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag); + lvl = _pausers--; + if (_pausers == 0) { + _next_trim_time = _next_trim_time_saved; + _next_trim_time_saved = 0; + ml.notify_all(); + } + } + log_debug(gc, trim)("NativeTrimmer unpause (%s) (%d)", reason, lvl); + } + +}; // NativeTrimmer + +static NativeTrimmerThread* g_trimmer_thread = nullptr; + +/// GCTrimNative outside facing methods + +void TrimNative::initialize() { + if (TrimNativeHeap) { + if (!os::can_trim_native_heap()) { + FLAG_SET_ERGO(TrimNativeHeap, false); + log_info(gc, trim)("Native trim not supported on this platform."); + return; + } + g_trimmer_thread = new NativeTrimmerThread(); + log_info(gc, trim)("Periodic native trim enabled (interval: %u seconds)", TrimNativeHeapInterval); + } +} + +void TrimNative::cleanup() { + if (g_trimmer_thread != nullptr) { + g_trimmer_thread->stop(); + } +} + +void TrimNative::pause_periodic_trim(const char* reason) { + if (g_trimmer_thread != nullptr) { + g_trimmer_thread->pause(reason); + } +} + +void TrimNative::unpause_periodic_trim(const char* reason) { + if (g_trimmer_thread != nullptr) { + g_trimmer_thread->unpause(reason); + } +} + diff --git a/src/hotspot/share/gc/shared/trimNative.hpp b/src/hotspot/share/gc/shared/trimNative.hpp new file mode 100644 index 0000000000000..440872e2d45d8 --- /dev/null +++ b/src/hotspot/share/gc/shared/trimNative.hpp @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2023 SAP SE. 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_GC_SHARED_TRIMNATIVE_HPP +#define SHARE_GC_SHARED_TRIMNATIVE_HPP + +#include "memory/allStatic.hpp" +#include "gc/shared/gc_globals.hpp" + +class TrimNative : public AllStatic { +public: + + static void initialize(); + static void cleanup(); + + // Pause periodic trim (if enabled). + static void pause_periodic_trim(const char* reason); + + // Unpause periodic trim (if enabled). + static void unpause_periodic_trim(const char* reason); + + // Pause periodic trimming while in scope; when leaving scope, + // resume periodic trimming. + struct PauseMark { + const char* const _reason; + PauseMark(const char* reason = "unknown") : _reason(reason) { + if (TrimNativeHeap) { + pause_periodic_trim(_reason); + } + } + ~PauseMark() { + if (TrimNativeHeap) { + unpause_periodic_trim(_reason); + } + } + }; +}; + +#endif // SHARE_GC_SHARED_TRIMNATIVE_HPP diff --git a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp index 3b755e4366a6e..7aa649f8cd804 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp @@ -40,6 +40,7 @@ #include "gc/shenandoah/shenandoahVMOperations.hpp" #include "gc/shenandoah/shenandoahWorkerPolicy.hpp" #include "gc/shenandoah/heuristics/shenandoahHeuristics.hpp" +#include "gc/shared/trimNative.hpp" #include "memory/iterator.hpp" #include "memory/metaspaceUtils.hpp" #include "memory/metaspaceStats.hpp" @@ -284,6 +285,7 @@ void ShenandoahControlThread::run_service() { if (ShenandoahPacing) { heap->pacer()->setup_for_idle(); } + } else { // Allow allocators to know we have seen this much regions if (ShenandoahPacing && (allocs_seen > 0)) { @@ -422,6 +424,7 @@ void ShenandoahControlThread::stop_service() { } void ShenandoahControlThread::service_stw_full_cycle(GCCause::Cause cause) { + TrimNative::PauseMark trim_native_pause("gc"); GCIdMark gc_id_mark; ShenandoahGCSession session(cause); @@ -436,6 +439,7 @@ void ShenandoahControlThread::service_stw_full_cycle(GCCause::Cause cause) { void ShenandoahControlThread::service_stw_degenerated_cycle(GCCause::Cause cause, ShenandoahGC::ShenandoahDegenPoint point) { assert (point != ShenandoahGC::_degenerated_unset, "Degenerated point should be set"); + TrimNative::PauseMark trim_native_pause("gc"); GCIdMark gc_id_mark; ShenandoahGCSession session(cause); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.hpp b/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.hpp index a6ca335a0d7de..e3d16a40c14c2 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.hpp @@ -174,6 +174,7 @@ class outputStream; f(full_gc_heapdump_post, " Post Heap Dump") \ \ f(conc_uncommit, "Concurrent Uncommit") \ + f(conc_trim, "Concurrent Trim") \ f(pacing, "Pacing") \ \ f(heap_iteration_roots, "Heap Iteration") \ diff --git a/src/hotspot/share/gc/x/xDriver.cpp b/src/hotspot/share/gc/x/xDriver.cpp index f76d9f4e58688..c20e69ef7be9e 100644 --- a/src/hotspot/share/gc/x/xDriver.cpp +++ b/src/hotspot/share/gc/x/xDriver.cpp @@ -26,6 +26,7 @@ #include "gc/shared/gcLocker.hpp" #include "gc/shared/gcVMOperations.hpp" #include "gc/shared/isGCActiveMark.hpp" +#include "gc/shared/trimNative.hpp" #include "gc/x/xAbort.inline.hpp" #include "gc/x/xBreakpoint.hpp" #include "gc/x/xCollectedHeap.hpp" @@ -114,6 +115,8 @@ class VM_XOperation : public VM_Operation { return; } + TrimNative::PauseMark trim_native_pause("gc"); + // Setup GC id and active marker GCIdMark gc_id_mark(_gc_id); IsGCActiveMark gc_active_mark; diff --git a/src/hotspot/share/gc/z/zGeneration.cpp b/src/hotspot/share/gc/z/zGeneration.cpp index 0ee5ce7a6c89e..50dc8fab55cfa 100644 --- a/src/hotspot/share/gc/z/zGeneration.cpp +++ b/src/hotspot/share/gc/z/zGeneration.cpp @@ -28,6 +28,7 @@ #include "gc/shared/gcVMOperations.hpp" #include "gc/shared/isGCActiveMark.hpp" #include "gc/shared/suspendibleThreadSet.hpp" +#include "gc/shared/trimNative.hpp" #include "gc/z/zAllocator.inline.hpp" #include "gc/z/zBarrierSet.hpp" #include "gc/z/zBarrierSetAssembler.hpp" @@ -431,6 +432,8 @@ class VM_ZOperation : public VM_Operation { } virtual void doit() { + TrimNative::PauseMark trim_native_pause("gc"); + // Setup GC id and active marker GCIdMark gc_id_mark(_gc_id); IsGCActiveMark gc_active_mark; diff --git a/src/hotspot/share/logging/logTag.hpp b/src/hotspot/share/logging/logTag.hpp index 8e04ae11046af..15b93aadab4e3 100644 --- a/src/hotspot/share/logging/logTag.hpp +++ b/src/hotspot/share/logging/logTag.hpp @@ -196,6 +196,7 @@ class outputStream; LOG_TAG(timer) \ LOG_TAG(tlab) \ LOG_TAG(tracking) \ + LOG_TAG(trim) \ LOG_TAG(unload) /* Trace unloading of classes */ \ LOG_TAG(unmap) \ LOG_TAG(unshareable) \ diff --git a/src/hotspot/share/memory/arena.cpp b/src/hotspot/share/memory/arena.cpp index 98449085a0eee..97cd150a83a85 100644 --- a/src/hotspot/share/memory/arena.cpp +++ b/src/hotspot/share/memory/arena.cpp @@ -24,6 +24,7 @@ */ #include "precompiled.hpp" +#include "gc/shared/trimNative.hpp" #include "memory/allocation.hpp" #include "memory/allocation.inline.hpp" #include "memory/resourceArea.hpp" @@ -52,7 +53,7 @@ class ChunkPool { const size_t _size; // (inner payload) size of the chunks this pool serves // Our four static pools - static const int _num_pools = 4; + static constexpr int _num_pools = 4; static ChunkPool _pools[_num_pools]; public: @@ -80,7 +81,6 @@ class ChunkPool { void prune() { // Free all chunks while in ThreadCritical lock // so NMT adjustment is stable. - ThreadCritical tc; Chunk* cur = _first; Chunk* next = nullptr; while (cur != nullptr) { @@ -91,9 +91,23 @@ class ChunkPool { _first = nullptr; } + bool empty() const { + return _first == nullptr; + } + + static bool needs_cleaning() { + STATIC_ASSERT(_num_pools == 4); + return !_pools[0].empty() || !_pools[1].empty() || + !_pools[2].empty() || !_pools[3].empty(); + } + static void clean() { - for (int i = 0; i < _num_pools; i++) { - _pools[i].prune(); + ThreadCritical tc; + if (needs_cleaning()) { + TrimNative::PauseMark trim_native_pause("chunk pool cleaner"); + for (int i = 0; i < _num_pools; i++) { + _pools[i].prune(); + } } } diff --git a/src/hotspot/share/memory/universe.cpp b/src/hotspot/share/memory/universe.cpp index 8dcdb6a5f92cf..5228e032060bf 100644 --- a/src/hotspot/share/memory/universe.cpp +++ b/src/hotspot/share/memory/universe.cpp @@ -47,6 +47,7 @@ #include "gc/shared/plab.hpp" #include "gc/shared/stringdedup/stringDedup.hpp" #include "gc/shared/tlab_globals.hpp" +#include "gc/shared/trimNative.hpp" #include "logging/log.hpp" #include "logging/logStream.hpp" #include "memory/metadataFactory.hpp" @@ -1055,6 +1056,11 @@ bool universe_post_init() { #if INCLUDE_CDS MetaspaceShared::post_initialize(CHECK_false); #endif + + if (TrimNativeHeap) { + TrimNative::initialize(); + } + return true; } diff --git a/src/hotspot/share/runtime/java.cpp b/src/hotspot/share/runtime/java.cpp index 140d0281e2f33..afb67144391e3 100644 --- a/src/hotspot/share/runtime/java.cpp +++ b/src/hotspot/share/runtime/java.cpp @@ -34,6 +34,7 @@ #include "compiler/compileBroker.hpp" #include "compiler/compilerOracle.hpp" #include "gc/shared/collectedHeap.hpp" +#include "gc/shared/trimNative.hpp" #include "gc/shared/stringdedup/stringDedup.hpp" #include "interpreter/bytecodeHistogram.hpp" #include "jfr/jfrEvents.hpp" @@ -479,6 +480,8 @@ void before_exit(JavaThread* thread, bool halt) { StatSampler::disengage(); StatSampler::destroy(); + TrimNative::cleanup(); + // Stop concurrent GC threads Universe::heap()->stop(); diff --git a/src/hotspot/share/runtime/synchronizer.cpp b/src/hotspot/share/runtime/synchronizer.cpp index 09ed8d1a7f811..026dc1e2d707e 100644 --- a/src/hotspot/share/runtime/synchronizer.cpp +++ b/src/hotspot/share/runtime/synchronizer.cpp @@ -25,6 +25,7 @@ #include "precompiled.hpp" #include "classfile/vmSymbols.hpp" #include "gc/shared/suspendibleThreadSet.hpp" +#include "gc/shared/trimNative.hpp" #include "jfr/jfrEvents.hpp" #include "logging/log.hpp" #include "logging/logStream.hpp" @@ -1646,6 +1647,7 @@ class VM_RendezvousGCThreads : public VM_Operation { }; static size_t delete_monitors(GrowableArray* delete_list) { + TrimNative::PauseMark trim_native_pause("monitor deletion"); size_t count = 0; for (ObjectMonitor* monitor: *delete_list) { delete monitor; diff --git a/test/hotspot/jtreg/gc/TestTrimNative.java b/test/hotspot/jtreg/gc/TestTrimNative.java new file mode 100644 index 0000000000000..84ed53d1f5723 --- /dev/null +++ b/test/hotspot/jtreg/gc/TestTrimNative.java @@ -0,0 +1,307 @@ +/* + * 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. + * + */ + +package gc; + +/* + * @test id=serial + * @requires vm.gc.Serial + * @requires (os.family=="linux") & !vm.musl + * @modules java.base/jdk.internal.misc + * @library /test/lib + * @run driver gc.TestTrimNative test -XX:+UseSerialGC + */ + +/* + * @test id=parallel + * @requires vm.gc.Serial + * @requires (os.family=="linux") & !vm.musl + * @modules java.base/jdk.internal.misc + * @library /test/lib + * @run driver gc.TestTrimNative test -XX:+UseParallelGC + */ + +/* + * @test id=shenandoah + * @requires vm.gc.Serial + * @requires (os.family=="linux") & !vm.musl + * @modules java.base/jdk.internal.misc + * @library /test/lib + * @run driver gc.TestTrimNative test -XX:+UseShenandoahGC + */ + +/* + * @test id=Znongen + * @requires vm.gc.Serial + * @requires (os.family=="linux") & !vm.musl + * @modules java.base/jdk.internal.misc + * @library /test/lib + * @run driver gc.TestTrimNative test -XX:+UseZGC -XX:-ZGenerational + */ + +/* + * @test id=Zgen + * @requires vm.gc.Serial + * @requires (os.family=="linux") & !vm.musl + * @modules java.base/jdk.internal.misc + * @library /test/lib + * @run driver gc.TestTrimNative test -XX:+UseZGC -XX:+ZGenerational + */ + +// Other tests + +/* + * @test id=testOffByDefault + * @summary Test that -GCTrimNative disables the feature + * @requires (os.family=="linux") & !vm.musl + * @modules java.base/jdk.internal.misc + * @library /test/lib + * @run driver gc.TestTrimNative testOffByDefault + */ + +/* + * @test id=testOffExplicit + * @summary Test that GCTrimNative is off by default + * @requires (os.family=="linux") & !vm.musl + * @modules java.base/jdk.internal.misc + * @library /test/lib + * @run driver gc.TestTrimNative testOffExplicit + */ + +/* + * @test id=testOffOnNonCompliantPlatforms + * @summary Test that GCTrimNative is off on unsupportive platforms + * @requires (os.family!="linux") | vm.musl + * @modules java.base/jdk.internal.misc + * @library /test/lib + * @run driver gc.TestTrimNative testOffOnNonCompliantPlatforms + */ + +import jdk.internal.misc.Unsafe; +import jdk.test.lib.Platform; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +import java.io.IOException; +import java.lang.reflect.Array; +import java.rmi.RemoteException; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +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 = 16; + static final int totalAllocationsSize = 16 * 1024 * 1024; // 16 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 OutputAnalyzer runTestWithOptions(String[] extraOptions) throws IOException { + + List allOptions = new ArrayList(); + allOptions.addAll(Arrays.asList(extraOptions)); + allOptions.add("-Xmx128m"); + allOptions.add("-Xms128m"); // Stabilize RSS + allOptions.add("-XX:+AlwaysPreTouch"); // Stabilize RSS + allOptions.add("-XX:-ExplicitGCInvokesConcurrent"); // Invoke explicit GC on System.gc + allOptions.add("-Xlog:gc+trim=debug"); + allOptions.add("--add-exports=java.base/jdk.internal.misc=ALL-UNNAMED"); + + allOptions.add(TestTrimNative.class.getName()); + allOptions.add("RUN"); + ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(allOptions.toArray(new String[0])); + 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 + " seconds"); + output.shouldContain("NativeTrimmer start"); + output.shouldContain("NativeTrimmer stop"); + } else { + output.shouldNotContain("Native trim"); + } + } + + /** + * 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 lines = output.asLines(); + Pattern pat = Pattern.compile(".*\\[gc,trim\\] Trim native heap: RSS\\+Swap: (\\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; + System.out.println("Parsed Trim Line. rss1: " + rss1 + " rss2: " + rss2); + 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 + ")."); + } + // 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 private final void runTest(String[] VMargs) throws IOException { + OutputAnalyzer output = runTestWithOptions ( + new String[] { "-XX:+UnlockExperimentalVMOptions", + "-XX:+TrimNativeHeap", + "-XX:TrimNativeHeapInterval=1" + } + ); + + checkExpectedLogMessages(output, true, 1); + + // We expect to see at least one GC-related trimming pause + output.shouldMatch("NativeTrimmer pause.*(gc)"); + output.shouldMatch("NativeTrimmer unpause.*(gc)"); + + parseOutputAndLookForNegativeTrim(output,0, /* minTrimsExpected */ 10 /* maxTrimsExpected */); + } + + // Test that a high trim interval effectively disables trimming + static private final void testHighTrimInterval() throws IOException { + OutputAnalyzer output = runTestWithOptions ( + new String[] { "-XX:+UnlockExperimentalVMOptions", + "-XX:+TrimNativeHeap", + "-XX:TrimNativeHeapInterval=3600" + }); + checkExpectedLogMessages(output, true, 3600); + parseOutputAndLookForNegativeTrim(output,0, /* minTrimsExpected */ 0 /* maxTrimsExpected */); + } + + // Test that trim-native gets disabled on platforms that don't support it. + static private final void testOffOnNonCompliantPlatforms() throws IOException { + OutputAnalyzer output = runTestWithOptions ( + new String[] { "-XX:+UnlockExperimentalVMOptions", + "-XX:+TrimNativeHeap" + }); + checkExpectedLogMessages(output, false, 0); + } + + // Test trim native is disabled if explicitly switched off + static private final void testOffExplicit() throws IOException { + OutputAnalyzer output = runTestWithOptions ( + new String[] { "-XX:+UnlockExperimentalVMOptions", + "-XX:-TrimNativeHeap" + }); + checkExpectedLogMessages(output, false, 0); + } + + // Test trim native is disabled if explicitly switched off + static private final void testOffByDefault() throws IOException { + OutputAnalyzer output = runTestWithOptions (new String[] { }); + checkExpectedLogMessages(output, false, 0); + } + + public static void main(String[] args) throws Exception { + + if (args.length == 0) { + throw new RuntimeException("Argument error"); + } + + if (args[0].equals("RUN")) { + + System.out.println("Will spike now..."); + for (int i = 0; i < numAllocations; i++) { + ptrs[i] = Unsafe.getUnsafe().allocateMemory(szAllocations); + Unsafe.getUnsafe().putByte(ptrs[i], (byte)0); + Unsafe.getUnsafe().putByte(ptrs[i] + szAllocations / 2, (byte)0); + } + for (int i = 0; i < numAllocations; i++) { + Unsafe.getUnsafe().freeMemory(ptrs[i]); + } + System.out.println("Done spiking."); + + // Do a system GC. Native trimming should be paused in that time. + System.out.println("GC..."); + System.gc(); + + // give GC time to react + System.out.println("Sleeping..."); + Thread.sleep(3000); + System.out.println("Done."); + + return; + + } else if (args[0].equals("test")) { + runTest(Arrays.copyOfRange(args, 1, args.length)); + } else if (args[0].equals("testOffOnNonCompliantPlatforms")) { + testOffOnNonCompliantPlatforms(); + } else if (args[0].equals("testOffExplicit")) { + testOffExplicit(); + } else if (args[0].equals("testOffByDefault")) { + testOffByDefault(); + } else { + throw new RuntimeException("Invalid test " + args[0]); + } + } +} diff --git a/test/hotspot/jtreg/serviceability/dcmd/vm/TrimLibcHeapTest.java b/test/hotspot/jtreg/serviceability/dcmd/vm/TrimLibcHeapTest.java index 5c18d711c7ee7..63624221ce035 100644 --- a/test/hotspot/jtreg/serviceability/dcmd/vm/TrimLibcHeapTest.java +++ b/test/hotspot/jtreg/serviceability/dcmd/vm/TrimLibcHeapTest.java @@ -22,6 +22,7 @@ * questions. */ +import jdk.test.lib.Platform; import org.testng.annotations.Test; import jdk.test.lib.dcmd.CommandExecutor; import jdk.test.lib.dcmd.JMXExecutor; @@ -31,7 +32,7 @@ * @test * @summary Test of diagnostic command VM.trim_libc_heap * @library /test/lib - * @requires (os.family=="linux") & !vm.musl + * @requires os.family == "linux" * @modules java.base/jdk.internal.misc * java.compiler * java.management @@ -42,7 +43,11 @@ public class TrimLibcHeapTest { public void run(CommandExecutor executor) { OutputAnalyzer output = executor.execute("System.trim_native_heap"); 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