From 03bd55e0d2a892a5f33946e2bc32c94798df872c Mon Sep 17 00:00:00 2001 From: tstuefe Date: Tue, 30 Aug 2022 16:33:08 +0200 Subject: [PATCH 01/27] trim-native --- src/hotspot/os/aix/os_aix.cpp | 5 + src/hotspot/os/bsd/os_bsd.cpp | 5 + src/hotspot/os/linux/os_linux.cpp | 162 ++++++- src/hotspot/os/linux/os_linux.hpp | 57 +-- src/hotspot/os/linux/trimCHeapDCmd.cpp | 56 +-- src/hotspot/os/windows/os_windows.cpp | 5 + src/hotspot/share/gc/g1/g1CollectedHeap.cpp | 7 + src/hotspot/share/gc/g1/g1CollectedHeap.hpp | 1 + .../gc/parallel/parallelScavengeHeap.cpp | 3 + .../share/gc/parallel/psParallelCompact.cpp | 3 + src/hotspot/share/gc/shared/gcTimer.cpp | 9 +- .../share/gc/shared/gcTrimNativeHeap.cpp | 191 ++++++++ .../share/gc/shared/gcTrimNativeHeap.hpp | 60 +++ src/hotspot/share/gc/shared/gc_globals.hpp | 21 +- .../share/gc/shared/genCollectedHeap.cpp | 8 + .../gc/shenandoah/shenandoahControlThread.cpp | 11 + .../share/gc/shenandoah/shenandoahHeap.cpp | 3 + .../gc/shenandoah/shenandoahPhaseTimings.hpp | 1 + src/hotspot/share/gc/z/zCollectedHeap.cpp | 3 + src/hotspot/share/gc/z/zDriver.cpp | 8 +- src/hotspot/share/logging/logTag.hpp | 1 + src/hotspot/share/runtime/java.cpp | 3 + src/hotspot/share/runtime/os.hpp | 11 + test/hotspot/jtreg/gc/TestTrimNative.java | 422 ++++++++++++++++++ .../dcmd/vm/TrimLibcHeapTest.java | 2 +- 25 files changed, 957 insertions(+), 101 deletions(-) create mode 100644 src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp create mode 100644 src/hotspot/share/gc/shared/gcTrimNativeHeap.hpp create mode 100644 test/hotspot/jtreg/gc/TestTrimNative.java diff --git a/src/hotspot/os/aix/os_aix.cpp b/src/hotspot/os/aix/os_aix.cpp index 0706fdd3275ce..7cb862ce36025 100644 --- a/src/hotspot/os/aix/os_aix.cpp +++ b/src/hotspot/os/aix/os_aix.cpp @@ -2986,3 +2986,8 @@ bool os::supports_map_sync() { } void os::print_memory_mappings(char* addr, size_t bytes, outputStream* st) {} + +// stubbed-out trim-native support +bool os::can_trim_native_heap() { return false; } +bool os::should_trim_native_heap(size_t retain_size) { return false; } +bool os::trim_native_heap(os::size_change_t* rss_change, size_t retain_size) { return false; } diff --git a/src/hotspot/os/bsd/os_bsd.cpp b/src/hotspot/os/bsd/os_bsd.cpp index e241e2d37adee..a1c0ccb972cf9 100644 --- a/src/hotspot/os/bsd/os_bsd.cpp +++ b/src/hotspot/os/bsd/os_bsd.cpp @@ -2445,3 +2445,8 @@ bool os::start_debugging(char *buf, int buflen) { } void os::print_memory_mappings(char* addr, size_t bytes, outputStream* st) {} + +// stubbed-out trim-native support +bool os::can_trim_native_heap() { return false; } +bool os::should_trim_native_heap(size_t retain_size) { return false; } +bool os::trim_native_heap(os::size_change_t* rss_change, size_t retain_size) { return false; } diff --git a/src/hotspot/os/linux/os_linux.cpp b/src/hotspot/os/linux/os_linux.cpp index f3c9c4a2c8fd3..1bd205120dac2 100644 --- a/src/hotspot/os/linux/os_linux.cpp +++ b/src/hotspot/os/linux/os_linux.cpp @@ -169,8 +169,31 @@ const char * os::Linux::_libpthread_version = NULL; size_t os::Linux::_default_large_page_size = 0; #ifdef __GLIBC__ -os::Linux::mallinfo_func_t os::Linux::_mallinfo = NULL; -os::Linux::mallinfo2_func_t os::Linux::_mallinfo2 = NULL; +// Note: mallinfo(3) vs mallinfo2(3): +// We want to be runnable with both old and new glibcs. Old glibcs just offer mallinfo(3). +// New glibcs deprecate mallinfo(3) and offer mallinfo2(3) as replacement. Future glibc's +// may remove mallinfo(3) altogether. +// Therefore we may have one, both, or possibly neither (?). Code should tolerate all +// cases, which is why we resolve the functions dynamically. Outside code uses +// os::Linux::get_mallinfo() utility function that hides this mess. +struct glibc_mallinfo { + int arena; + int ordblks; + int smblks; + int hblks; + int hblkhd; + int usmblks; + int fsmblks; + int uordblks; + int fordblks; + int keepcost; +}; +// struct glibc_mallinfo2 lives in os_linux.hpp since it does dual duty as output +// structure for both the native mallinfo2(3) as for the wrapper function +typedef struct glibc_mallinfo (*mallinfo_func_t)(void); +typedef struct os::Linux::glibc_mallinfo2 (*mallinfo2_func_t)(void); +static mallinfo_func_t g_mallinfo = NULL; +static mallinfo2_func_t g_mallinfo2 = NULL; #endif // __GLIBC__ static int clock_tics_per_sec = 100; @@ -2172,22 +2195,18 @@ void os::Linux::print_process_memory_info(outputStream* st) { size_t total_allocated = 0; size_t free_retained = 0; bool might_have_wrapped = false; - if (_mallinfo2 != NULL) { - struct glibc_mallinfo2 mi = _mallinfo2(); - total_allocated = mi.uordblks + mi.hblkhd; - free_retained = mi.fordblks; - } else if (_mallinfo != NULL) { - // mallinfo is an old API. Member names mean next to nothing and, beyond that, are 32-bit signed. - // So for larger footprints the values may have wrapped around. We try to detect this here: if the - // process whole resident set size is smaller than 4G, malloc footprint has to be less than that - // and the numbers are reliable. - struct glibc_mallinfo mi = _mallinfo(); - total_allocated = (size_t)(unsigned)mi.uordblks + (size_t)(unsigned)mi.hblkhd; - free_retained = (size_t)(unsigned)mi.fordblks; - // Since mallinfo members are int, glibc values may have wrapped. Warn about this. - might_have_wrapped = (info.vmrss * K) > UINT_MAX && (info.vmrss * K) > (total_allocated + UINT_MAX); - } - if (_mallinfo2 != NULL || _mallinfo != NULL) { + glibc_mallinfo2 mi; + mallinfo_retval_t mirc = os::Linux::get_mallinfo(&mi); + if (mirc != mallinfo_retval_t::error) { + size_t total_allocated = mi.uordblks + mi.hblkhd; + size_t free_retained = mi.fordblks; +#ifdef _LP64 + // If all we had is old mallinf(3), the values may have wrapped. Since that can confuse readers + // of this output, print a hint. + // We do this by checking virtual size of the process: if that is <4g, we could not have wrapped. + might_have_wrapped = (mirc == mallinfo_retval_t::ok_but_possibly_wrapped) && + ((info.vmsize * K) > UINT_MAX); +#endif st->print_cr("C-Heap outstanding allocations: " SIZE_FORMAT "K, retained: " SIZE_FORMAT "K%s", total_allocated / K, free_retained / K, might_have_wrapped ? " (may have wrapped)" : ""); @@ -4343,8 +4362,8 @@ void os::init(void) { Linux::initialize_system_info(); #ifdef __GLIBC__ - Linux::_mallinfo = CAST_TO_FN_PTR(Linux::mallinfo_func_t, dlsym(RTLD_DEFAULT, "mallinfo")); - Linux::_mallinfo2 = CAST_TO_FN_PTR(Linux::mallinfo2_func_t, dlsym(RTLD_DEFAULT, "mallinfo2")); + g_mallinfo = CAST_TO_FN_PTR(mallinfo_func_t, dlsym(RTLD_DEFAULT, "mallinfo")); + g_mallinfo2 = CAST_TO_FN_PTR(mallinfo2_func_t, dlsym(RTLD_DEFAULT, "mallinfo2")); #endif // __GLIBC__ os::Linux::CPUPerfTicks pticks; @@ -4632,6 +4651,11 @@ jint os::init_2(void) { FLAG_SET_DEFAULT(UseCodeCacheFlushing, false); } +#ifdef __GLIBC__ + log_debug(os)("mallinfo: %s", g_mallinfo ? "yes" : "no"); + log_debug(os)("mallinfo2: %s", g_mallinfo2 ? "yes" : "no"); +#endif + return JNI_OK; } @@ -5401,3 +5425,101 @@ void os::print_memory_mappings(char* addr, size_t bytes, outputStream* st) { st->cr(); } } + +#ifdef __GLIBC__ +os::Linux::mallinfo_retval_t os::Linux::get_mallinfo(glibc_mallinfo2* out) { + if (g_mallinfo2) { + // preferred. + glibc_mallinfo2 mi = g_mallinfo2(); + *out = mi; + return mallinfo_retval_t::ok; + } else if (g_mallinfo) { + // not perfect but ok if process virt size < 4g or if wrapping does not matter to the caller + glibc_mallinfo mi = g_mallinfo(); + out->arena = (int) mi.arena; + out->ordblks = (int) mi.ordblks; + out->smblks = (int) mi.smblks; + out->hblks = (int) mi.hblks; + out->hblkhd = (int) mi.hblkhd; + out->usmblks = (int) mi.usmblks; + out->fsmblks = (int) mi.fsmblks; + out->uordblks = (int) mi.uordblks; + out->fordblks = (int) mi.fordblks; + out->keepcost = (int) mi.keepcost; + return mallinfo_retval_t::ok_but_possibly_wrapped; + } + return mallinfo_retval_t::ok; +} +#endif // __GLIBC__ + +// Trim-native support +bool os::can_trim_native_heap() { +#ifdef __GLIBC__ + return true; +#else + return false; // musl +#endif +} + +bool os::should_trim_native_heap(size_t retain_size) { +#ifdef __GLIBC__ + bool rc = true; + // It is difficult to predict the effect a malloc_trim(3) will have. + // mallinfo(3) looks like a good candidate but does not work that well + // in practice. + // + // "mallinfo::keepcost" is no help even if manpage claims this to be the + // projected trim size. In practice it is just a very small value with + // no relation to the actual effect trimming will have. + // + // The best we have is "mallinfo::fordblks", the total chunk size of free + // blocks. Since only free blocks can be trimmed, a very low bar is to require + // that size to be higher than our retain size. Unfortunately, "mallinfo::fordblks" + // does not react to malloc_trim(3). Glibc trims by calling madvice(MADV_DONT_NEED) + // on suitable chunks, but does not destruct the chunks nor updates its + // bookkeeping. + // + // In the end we want to prevent obvious bogus attempts to trim, and for that + // fordblks is good enough. + os::Linux::glibc_mallinfo2 mi; + os::Linux::mallinfo_retval_t mirc = os::Linux::get_mallinfo(&mi); + const size_t total_free = mi.fordblks; + if (mirc == os::Linux::mallinfo_retval_t::ok) { + rc = retain_size < total_free; + } + return rc; +#else + return false; // musl +#endif +} + +bool os::trim_native_heap(os::size_change_t* rss_change, size_t retain_size) { +#ifdef __GLIBC__ + os::Linux::meminfo_t info1; + os::Linux::meminfo_t info2; + + // Note: query_process_memory_info returns values in K + + // Query memory before... + bool have_info1 = (rss_change != nullptr) ? os::Linux::query_process_memory_info(&info1) : false; + + ::malloc_trim(retain_size); + + // ...and after trim. + bool have_info2 = (rss_change != nullptr) ? os::Linux::query_process_memory_info(&info2) : false; + + ssize_t delta = (ssize_t) -1; + if (have_info1 && have_info2 && + info1.vmrss != -1 && info2.vmrss != -1 && + info1.vmswap != -1 && info2.vmswap != -1) { + rss_change->before = (info1.vmrss + info1.vmswap) * K; + rss_change->after = (info2.vmrss + info2.vmswap) * K; + } else { + rss_change->after = rss_change->before = SIZE_MAX; + } + + return true; +#else + return 0; // musl +#endif +} diff --git a/src/hotspot/os/linux/os_linux.hpp b/src/hotspot/os/linux/os_linux.hpp index 95e604967619e..5c6a1a9c179cf 100644 --- a/src/hotspot/os/linux/os_linux.hpp +++ b/src/hotspot/os/linux/os_linux.hpp @@ -172,7 +172,7 @@ class os::Linux { // Return the namespace pid if so, otherwise -1. static int get_namespace_pid(int vmid); - // Output structure for query_process_memory_info() + // Output structure for query_process_memory_info() (all values in KB) struct meminfo_t { ssize_t vmsize; // current virtual size ssize_t vmpeak; // peak virtual size @@ -265,40 +265,6 @@ class os::Linux { }; static NumaAllocationPolicy _current_numa_policy; -#ifdef __GLIBC__ - struct glibc_mallinfo { - int arena; - int ordblks; - int smblks; - int hblks; - int hblkhd; - int usmblks; - int fsmblks; - int uordblks; - int fordblks; - int keepcost; - }; - - struct glibc_mallinfo2 { - size_t arena; - size_t ordblks; - size_t smblks; - size_t hblks; - size_t hblkhd; - size_t usmblks; - size_t fsmblks; - size_t uordblks; - size_t fordblks; - size_t keepcost; - }; - - typedef struct glibc_mallinfo (*mallinfo_func_t)(void); - typedef struct glibc_mallinfo2 (*mallinfo2_func_t)(void); - - static mallinfo_func_t _mallinfo; - static mallinfo2_func_t _mallinfo2; -#endif - public: static int sched_getcpu() { return _sched_getcpu != NULL ? _sched_getcpu() : -1; } static int numa_node_to_cpus(int node, unsigned long *buffer, int bufferlen); @@ -426,6 +392,27 @@ class os::Linux { } static void* resolve_function_descriptor(void* p); + +#ifdef __GLIBC__ + // get_mallinfo() is a wrapper for either mallinfo or mallinfo2, depending on what + // we find available. It will prefer mallinfo2() over mallinfo() if possible. If we only + // have mallinfo(), values may be 32-bit truncated and that is signalled via the return + // code "ok_but_possibly_wrapped". + struct glibc_mallinfo2 { + size_t arena; + size_t ordblks; + size_t smblks; + size_t hblks; + size_t hblkhd; + size_t usmblks; + size_t fsmblks; + size_t uordblks; + size_t fordblks; + size_t keepcost; + }; + enum class mallinfo_retval_t { ok, error, ok_but_possibly_wrapped }; + static mallinfo_retval_t get_mallinfo(glibc_mallinfo2* out); +#endif }; #endif // OS_LINUX_OS_LINUX_HPP diff --git a/src/hotspot/os/linux/trimCHeapDCmd.cpp b/src/hotspot/os/linux/trimCHeapDCmd.cpp index ec212e2c70cbe..8b89fc83534af 100644 --- a/src/hotspot/os/linux/trimCHeapDCmd.cpp +++ b/src/hotspot/os/linux/trimCHeapDCmd.cpp @@ -34,47 +34,21 @@ #include void TrimCLibcHeapDCmd::execute(DCmdSource source, TRAPS) { -#ifdef __GLIBC__ - stringStream ss_report(1024); // Note: before calling trim - - os::Linux::meminfo_t info1; - os::Linux::meminfo_t info2; - // Query memory before... - bool have_info1 = os::Linux::query_process_memory_info(&info1); - - _output->print_cr("Attempting trim..."); - ::malloc_trim(0); - _output->print_cr("Done."); - - // ...and after trim. - bool have_info2 = os::Linux::query_process_memory_info(&info2); - - // Print report both to output stream as well to UL - bool wrote_something = false; - if (have_info1 && have_info2) { - if (info1.vmsize != -1 && info2.vmsize != -1) { - ss_report.print_cr("Virtual size before: " SSIZE_FORMAT "k, after: " SSIZE_FORMAT "k, (" SSIZE_FORMAT "k)", - info1.vmsize, info2.vmsize, (info2.vmsize - info1.vmsize)); - wrote_something = true; - } - if (info1.vmrss != -1 && info2.vmrss != -1) { - ss_report.print_cr("RSS before: " SSIZE_FORMAT "k, after: " SSIZE_FORMAT "k, (" SSIZE_FORMAT "k)", - info1.vmrss, info2.vmrss, (info2.vmrss - info1.vmrss)); - wrote_something = true; - } - if (info1.vmswap != -1 && info2.vmswap != -1) { - ss_report.print_cr("Swap before: " SSIZE_FORMAT "k, after: " SSIZE_FORMAT "k, (" SSIZE_FORMAT "k)", - info1.vmswap, info2.vmswap, (info2.vmswap - info1.vmswap)); - wrote_something = true; + if (os::can_trim_native_heap()) { + os::size_change_t sc; + if (os::trim_native_heap(&sc)) { + if (sc.after == SIZE_MAX) { + _output->print_cr("Done (no details available)."); + } else { + const size_t scale_to_use = sc.before > (10 * M) ? M : K; + const char scale_c = scale_to_use == K ? 'K' : 'M'; + const size_t m_before = sc.before / scale_to_use; + const size_t m_after = sc.after / scale_to_use; + _output->print_cr("Done. RSS+Swap reduction: " SIZE_FORMAT "%c->" SIZE_FORMAT "%c (" SSIZE_FORMAT "%c))", + m_before, scale_c, m_after, scale_c, (ssize_t)m_after - (ssize_t)m_before, scale_c); + } } + } else { + _output->print_cr("Not available."); } - if (!wrote_something) { - ss_report.print_raw("No details available."); - } - - _output->print_raw(ss_report.base()); - log_info(os)("malloc_trim:\n%s", ss_report.base()); -#else - _output->print_cr("Not available."); -#endif } diff --git a/src/hotspot/os/windows/os_windows.cpp b/src/hotspot/os/windows/os_windows.cpp index df75dc140f000..c92d226e384a4 100644 --- a/src/hotspot/os/windows/os_windows.cpp +++ b/src/hotspot/os/windows/os_windows.cpp @@ -5945,3 +5945,8 @@ void os::print_user_info(outputStream* st) { void os::print_active_locale(outputStream* st) { // not implemented yet } + +// stubbed-out trim-native support +bool os::can_trim_native_heap() { return false; } +bool os::should_trim_native_heap(size_t retain_size) { return false; } +bool os::trim_native_heap(os::size_change_t* rss_change, size_t retain_size) { return false; } diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp index 270d619022c2a..573677b0a1cae 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp @@ -82,6 +82,7 @@ #include "gc/shared/gcLocker.hpp" #include "gc/shared/gcTimer.hpp" #include "gc/shared/gcTraceTime.inline.hpp" +#include "gc/shared/gcTrimNativeHeap.hpp" #include "gc/shared/generationSpec.hpp" #include "gc/shared/isGCActiveMark.hpp" #include "gc/shared/locationPrinter.inline.hpp" @@ -1031,6 +1032,7 @@ void G1CollectedHeap::prepare_heap_for_mutators() { abort_refinement(); resize_heap_if_necessary(); uncommit_regions_if_necessary(); + trim_native_heap(); // Rebuild the code root lists for each region rebuild_code_roots(); @@ -1765,6 +1767,7 @@ void G1CollectedHeap::safepoint_synchronize_end() { void G1CollectedHeap::post_initialize() { CollectedHeap::post_initialize(); ref_processing_init(); + GCTrimNative::initialize(true); } void G1CollectedHeap::ref_processing_init() { @@ -2623,6 +2626,10 @@ void G1CollectedHeap::uncommit_regions_if_necessary() { } } +void G1CollectedHeap::trim_native_heap() { + GCTrimNative::schedule_trim(); +} + void G1CollectedHeap::verify_numa_regions(const char* desc) { LogTarget(Trace, gc, heap, verify) lt; diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp index fa3cdd2abd913..1efe1017ee1be 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp @@ -545,6 +545,7 @@ class G1CollectedHeap : public CollectedHeap { // Immediately uncommit uncommittable regions. uint uncommit_regions(uint region_limit); bool has_uncommittable_regions(); + void trim_native_heap(); G1NUMA* numa() const { return _numa; } diff --git a/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp b/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp index 71630b8564d3f..65b9ad5ef461f 100644 --- a/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp +++ b/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp @@ -42,6 +42,7 @@ #include "gc/shared/locationPrinter.inline.hpp" #include "gc/shared/scavengableNMethods.hpp" #include "gc/shared/suspendibleThreadSet.hpp" +#include "gc/shared/gcTrimNativeHeap.hpp" #include "logging/log.hpp" #include "memory/iterator.hpp" #include "memory/metaspaceCounters.hpp" @@ -190,6 +191,8 @@ void ParallelScavengeHeap::post_initialize() { PSPromotionManager::initialize(); ScavengableNMethods::initialize(&_is_scavengable); + + GCTrimNative::initialize(true); } void ParallelScavengeHeap::update_counters() { diff --git a/src/hotspot/share/gc/parallel/psParallelCompact.cpp b/src/hotspot/share/gc/parallel/psParallelCompact.cpp index 25bc767e5728a..5dadcbdd1740b 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/gcTrimNativeHeap.hpp" #include "gc/shared/isGCActiveMark.hpp" #include "gc/shared/oopStorage.inline.hpp" #include "gc/shared/oopStorageSet.inline.hpp" @@ -1867,6 +1868,8 @@ bool PSParallelCompact::invoke_no_policy(bool maximum_heap_compaction) { // Resize the metaspace capacity after a collection MetaspaceGC::compute_new_size(); + GCTrimNative::schedule_trim(); + if (log_is_enabled(Debug, gc, heap, exit)) { accumulated_time()->stop(); } diff --git a/src/hotspot/share/gc/shared/gcTimer.cpp b/src/hotspot/share/gc/shared/gcTimer.cpp index 00a712c6a9767..8f5f39ad23e1a 100644 --- a/src/hotspot/share/gc/shared/gcTimer.cpp +++ b/src/hotspot/share/gc/shared/gcTimer.cpp @@ -131,7 +131,14 @@ void TimePartitions::clear() { } void TimePartitions::report_gc_phase_start(const char* name, const Ticks& time, GCPhase::PhaseType type) { - assert(UseZGC || _phases->length() <= 1000, "Too many recorded phases? (count: %d)", _phases->length()); +#ifdef ASSERT + bool avoid_phase_length_check = UseZGC; +#if INCLUDE_SHENANDOAHGC + // With Shenandoah, skip this check if we do high frequency trimming. + avoid_phase_length_check |= (UseShenandoahGC && GCTrimNativeHeap && GCTrimNativeHeapInterval < 2); +#endif + assert(avoid_phase_length_check || _phases->length() <= 1000, "Too many recorded phases? (count: %d)", _phases->length()); +#endif // ASSERT int level = _active_phases.count(); diff --git a/src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp b/src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp new file mode 100644 index 0000000000000..c990dac0ad3a2 --- /dev/null +++ b/src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2022 SAP SE. All rights reserved. + * Copyright (c) 2022, 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 + * questions. + * + */ + +#include "precompiled.hpp" +#include "gc/shared/concurrentGCThread.hpp" +#include "gc/shared/gc_globals.hpp" +#include "gc/shared/gcTrimNativeHeap.hpp" +#include "logging/log.hpp" +#include "runtime/globals_extension.hpp" +#include "runtime/mutex.hpp" +#include "runtime/mutexLocker.hpp" +#include "runtime/os.hpp" +#include "utilities/debug.hpp" +#include "utilities/globalDefinitions.hpp" +#include "utilities/ticks.hpp" + +bool GCTrimNative::_async_mode = false; +double GCTrimNative::_next_trim_not_before = 0; + +// GCTrimNative works in two modes: +// +// - async mode, where GCTrimNative runs a trimmer thread on behalf of the GC. +// The trimmer thread will be doing all the trims, either periodically or +// triggered from outside via GCTrimNative::schedule_trim(). +// +// - synchronous mode, where the GC does the trimming itself in its own thread, +// via GCTrimNative::should_trim() and GCTrimNative::execute_trim(). +// +// The mode is set as argument to GCTrimNative::initialize(). + +class NativeTrimmer : public ConcurrentGCThread { + + Monitor* _lock; + static NativeTrimmer* _the_trimmer; + +protected: + + virtual void run_service() { + assert(GCTrimNativeHeap, "Sanity"); + assert(os::can_trim_native_heap(), "Sanity"); + + const int64_t delay_ms = GCTrimNativeHeapInterval * 1000; + for (;;) { + MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag); + ml.wait(delay_ms); // Note: GCTrimNativeHeapDelay == 0 disables periodic trim, no timeout then + if (should_terminate()) { + log_debug(gc, trim)("NativeTrimmer stopped."); + break; + } + if (os::should_trim_native_heap(GCTrimNativeHeapRetainSize)) { + GCTrimNative::do_trim(); + } + } + } + + void wakeup() { + MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag); + ml.notify_all(); + } + + virtual void stop_service() { + wakeup(); + } + +public: + + NativeTrimmer() : + _lock(new (std::nothrow) PaddedMonitor(Mutex::nosafepoint, "NativeTrimmer_lock")) + {} + + static bool is_enabled() { + return _the_trimmer != nullptr; + } + + static void start_trimmer() { + _the_trimmer = new NativeTrimmer(); + _the_trimmer->create_and_start(NormPriority); + } + + static void stop_trimmer() { + _the_trimmer->stop(); + } + + static void schedule_trim_now() { + _the_trimmer->wakeup(); + } + +}; // NativeTrimmer + +NativeTrimmer* NativeTrimmer::_the_trimmer = nullptr; + +#define PROPERFMT SIZE_FORMAT "%s" +#define PROPERARGS(S) byte_size_in_proper_unit(S), proper_unit_for_byte_size(S) + +void GCTrimNative::do_trim() { + Ticks start = Ticks::now(); + os::size_change_t sc; + 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_debug(gc, trim)("Trim native heap (retain size: " PROPERFMT "): " + "RSS+Swap: " PROPERFMT "->" PROPERFMT " (%c" PROPERFMT "), %1.3fms", + PROPERARGS(GCTrimNativeHeapRetainSize), + PROPERARGS(sc.before), PROPERARGS(sc.after), + sign, PROPERARGS(delta), + trim_time.seconds() * 1000); + } else { + log_debug(gc, trim)("Trim native heap (no details)"); + } + } +} + +/// GCTrimNative outside facing methods + +void GCTrimNative::initialize(bool async_mode) { + + if (GCTrimNativeHeap) { + + if (!os::can_trim_native_heap()) { + FLAG_SET_ERGO(GCTrimNativeHeap, false); + log_info(gc, trim)("GCTrimNativeHeap disabled - trim-native not supported on this platform."); + return; + } + + log_debug(gc, trim)("GCTrimNativeHeap enabled."); + + _async_mode = async_mode; + + // If we are to run the trimmer on behalf of the GC: + if (_async_mode) { + NativeTrimmer::start_trimmer(); + log_debug(gc, trim)("Native Trimmer enabled."); + } + + _next_trim_not_before = GCTrimNativeHeapInterval; + } +} + +void GCTrimNative::cleanup() { + if (GCTrimNativeHeap) { + if (_async_mode) { + NativeTrimmer::stop_trimmer(); + } + } +} + +bool GCTrimNative::should_trim(bool ignore_delay) { + return + GCTrimNativeHeap && os::can_trim_native_heap() && + (ignore_delay || os::elapsedTime() > _next_trim_not_before) && + os::should_trim_native_heap(GCTrimNativeHeapRetainSize); +} + +void GCTrimNative::execute_trim() { + if (GCTrimNativeHeap) { + assert(!_async_mode, "Only call for non-async mode"); + do_trim(); + _next_trim_not_before = os::elapsedTime() + GCTrimNativeHeapInterval; + } +} + +// Schedule trim-native for execution in the trimmer thread; return immediately. +void GCTrimNative::schedule_trim() { + if (GCTrimNativeHeap) { + assert(_async_mode, "Only call for async mode"); + NativeTrimmer::schedule_trim_now(); + } +} diff --git a/src/hotspot/share/gc/shared/gcTrimNativeHeap.hpp b/src/hotspot/share/gc/shared/gcTrimNativeHeap.hpp new file mode 100644 index 0000000000000..8cec43d4bf0fe --- /dev/null +++ b/src/hotspot/share/gc/shared/gcTrimNativeHeap.hpp @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2022 SAP SE. All rights reserved. + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_GC_SHARED_GCTRIMNATIVEHEAP_HPP +#define SHARE_GC_SHARED_GCTRIMNATIVEHEAP_HPP + +#include "memory/allStatic.hpp" + +class NativeTrimmer; + +class GCTrimNative : public AllStatic { + friend class NativeTrimmer; + + static bool _async_mode; + static double _next_trim_not_before; + + static void do_trim(); + +public: + + static void initialize(bool async_mode); + static void cleanup(); + + // Returns true if: + // - trimming is enabled and possible + // - trimming may have an actual effect (guess) + // - delay timer has expired (unless ignore_delay is true) + static bool should_trim(bool ignore_delay); + + // Execute trim-native in this thread + static void execute_trim(); + + // Schedule trim-native for execution in the trimmer thread; return immediately. + static void schedule_trim(); + +}; + +#endif // SHARE_GC_SHARED_GCTRIMNATIVEHEAP_HPP diff --git a/src/hotspot/share/gc/shared/gc_globals.hpp b/src/hotspot/share/gc/shared/gc_globals.hpp index 12f11f894c15a..3b13f7c387004 100644 --- a/src/hotspot/share/gc/shared/gc_globals.hpp +++ b/src/hotspot/share/gc/shared/gc_globals.hpp @@ -690,8 +690,25 @@ 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, GCTrimNativeHeap, false, EXPERIMENTAL, \ + "GC will attempt to trim the native heap periodically and at " \ + "full GCs.") \ + \ + product(uint, GCTrimNativeHeapInterval, 60, EXPERIMENTAL, \ + "If GCTrimNativeHeap is enabled: interval time, in seconds, in " \ + "which the VM will attempt to trim the native heap. A value of " \ + "0 disables periodic trimming while leaving trimming at full gc " \ + "enabled.") \ + range(0, max_juint) \ + \ + develop(size_t, GCTrimNativeHeapRetainSize, 5 * M, \ + "If GCTrimNativeHeap is enabled: Baseline threshold before that " \ + "trimming will not be attempted.") \ + range(0, SIZE_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 7ec0156735d65..05c3ce5048ba5 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/gcTrimNativeHeap.hpp" #include "gc/shared/weakProcessor.hpp" #include "gc/shared/workerThread.hpp" #include "memory/iterator.hpp" @@ -207,6 +208,8 @@ void GenCollectedHeap::post_initialize() { MarkSweep::initialize(); ScavengableNMethods::initialize(&_is_scavengable); + + GCTrimNative::initialize(false); // false since we will call trim inside the collecting thread } void GenCollectedHeap::ref_processing_init() { @@ -631,6 +634,11 @@ void GenCollectedHeap::do_collection(bool full, MetaspaceGC::compute_new_size(); update_full_collections_completed(); + // Trim the native heap, without a delay since this is a full gc + if (GCTrimNative::should_trim(true)) { + GCTrimNative::execute_trim(); + } + print_heap_change(pre_gc_values); // Track memory usage and detect low memory after GC finishes diff --git a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp index 3bcaa142763a2..88623662f464b 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp @@ -40,11 +40,14 @@ #include "gc/shenandoah/shenandoahVMOperations.hpp" #include "gc/shenandoah/shenandoahWorkerPolicy.hpp" #include "gc/shenandoah/heuristics/shenandoahHeuristics.hpp" +#include "gc/shared/gcTrimNativeHeap.hpp" #include "memory/iterator.hpp" #include "memory/metaspaceUtils.hpp" #include "memory/metaspaceStats.hpp" #include "memory/universe.hpp" #include "runtime/atomic.hpp" +#include "utilities/events.hpp" + ShenandoahControlThread::ShenandoahControlThread() : ConcurrentGCThread(), @@ -311,6 +314,14 @@ void ShenandoahControlThread::run_service() { last_shrink_time = current; } + if (GCTrimNative::should_trim(explicit_gc_requested)) { + static const char *msg = "Concurrent trim-native"; + ShenandoahConcurrentPhase gc_phase(msg, ShenandoahPhaseTimings::conc_trim, false); + EventMark em("%s", msg); + GCTrimNative::execute_trim(); + heap->phase_timings()->flush_cycle_to_global(); + } + // Wait before performing the next action. If allocation happened during this wait, // we exit sooner, to let heuristics re-evaluate new conditions. If we are at idle, // back off exponentially. diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp index 1adb2d5637e12..e14c0f7c52367 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp @@ -29,6 +29,7 @@ #include "gc/shared/gcArguments.hpp" #include "gc/shared/gcTimer.hpp" #include "gc/shared/gcTraceTime.inline.hpp" +#include "gc/shared/gcTrimNativeHeap.hpp" #include "gc/shared/locationPrinter.inline.hpp" #include "gc/shared/memAllocator.hpp" #include "gc/shared/plab.hpp" @@ -624,6 +625,8 @@ void ShenandoahHeap::post_initialize() { _heuristics->initialize(); JFR_ONLY(ShenandoahJFRSupport::register_jfr_type_serializers()); + + GCTrimNative::initialize(false); // false since this is taken care of inside the service thread } size_t ShenandoahHeap::used() const { 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/z/zCollectedHeap.cpp b/src/hotspot/share/gc/z/zCollectedHeap.cpp index f60fa38da130f..7237732dfae3c 100644 --- a/src/hotspot/share/gc/z/zCollectedHeap.cpp +++ b/src/hotspot/share/gc/z/zCollectedHeap.cpp @@ -24,6 +24,7 @@ #include "precompiled.hpp" #include "classfile/classLoaderData.hpp" #include "gc/shared/gcHeapSummary.hpp" +#include "gc/shared/gcTrimNativeHeap.hpp" #include "gc/shared/suspendibleThreadSet.hpp" #include "gc/z/zCollectedHeap.hpp" #include "gc/z/zDirector.hpp" @@ -73,6 +74,8 @@ jint ZCollectedHeap::initialize() { Universe::calculate_verify_data((HeapWord*)0, (HeapWord*)UINTPTR_MAX); + GCTrimNative::initialize(true); + return JNI_OK; } diff --git a/src/hotspot/share/gc/z/zDriver.cpp b/src/hotspot/share/gc/z/zDriver.cpp index 60daf185956c2..e21994db45275 100644 --- a/src/hotspot/share/gc/z/zDriver.cpp +++ b/src/hotspot/share/gc/z/zDriver.cpp @@ -24,8 +24,9 @@ #include "precompiled.hpp" #include "gc/shared/gcId.hpp" #include "gc/shared/gcLocker.hpp" -#include "gc/shared/gcVMOperations.hpp" #include "gc/shared/isGCActiveMark.hpp" +#include "gc/shared/gcVMOperations.hpp" +#include "gc/shared/gcTrimNativeHeap.hpp" #include "gc/z/zAbort.inline.hpp" #include "gc/z/zBreakpoint.hpp" #include "gc/z/zCollectedHeap.hpp" @@ -480,6 +481,11 @@ void ZDriver::gc(const ZDriverRequest& request) { // Phase 10: Concurrent Relocate concurrent(relocate); + + // GCTrimNativeHeap: A user-requested GC should trim the C-Heap right away + if (GCCause::is_user_requested_gc(request.cause())) { + GCTrimNative::schedule_trim(); + } } void ZDriver::run_service() { diff --git a/src/hotspot/share/logging/logTag.hpp b/src/hotspot/share/logging/logTag.hpp index 4705a5d376ddb..784cc1b6e8d65 100644 --- a/src/hotspot/share/logging/logTag.hpp +++ b/src/hotspot/share/logging/logTag.hpp @@ -192,6 +192,7 @@ class outputStream; LOG_TAG(timer) \ LOG_TAG(tlab) \ LOG_TAG(tracking) \ + LOG_TAG(trim) \ LOG_TAG(unload) /* Trace unloading of classes */ \ LOG_TAG(unshareable) \ NOT_PRODUCT(LOG_TAG(upcall)) \ diff --git a/src/hotspot/share/runtime/java.cpp b/src/hotspot/share/runtime/java.cpp index 6a1704f7249b7..13e213f560b07 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/gcTrimNativeHeap.hpp" #include "gc/shared/stringdedup/stringDedup.hpp" #include "interpreter/bytecodeHistogram.hpp" #include "jfr/jfrEvents.hpp" @@ -449,6 +450,8 @@ void before_exit(JavaThread* thread, bool halt) { StringDedup::stop(); } + GCTrimNative::cleanup(); + // Stop concurrent GC threads Universe::heap()->stop(); diff --git a/src/hotspot/share/runtime/os.hpp b/src/hotspot/share/runtime/os.hpp index 4f53b54cdb55f..cb1211d17dd41 100644 --- a/src/hotspot/share/runtime/os.hpp +++ b/src/hotspot/share/runtime/os.hpp @@ -440,6 +440,17 @@ class os: AllStatic { static bool uncommit_memory(char* addr, size_t bytes, bool executable = false); static bool release_memory(char* addr, size_t bytes); + // Does the platform support trimming the native heap? + static bool can_trim_native_heap(); + + // Given a planned retain size of x, would the platform recommend trimming? + static bool should_trim_native_heap(size_t retain_size); + + // Trim the C-heap. Returns RSS size change and optionally return the rss size change. + // If trim was done but size change could not be obtained, SIZE_MAX is returned for after size. + struct size_change_t { size_t before; size_t after; }; + static bool trim_native_heap(size_change_t* rss_change, size_t retain_size = 0); + // A diagnostic function to print memory mappings in the given range. static void print_memory_mappings(char* addr, size_t bytes, outputStream* st); // Prints all mappings diff --git a/test/hotspot/jtreg/gc/TestTrimNative.java b/test/hotspot/jtreg/gc/TestTrimNative.java new file mode 100644 index 0000000000000..c96ea62ccbab7 --- /dev/null +++ b/test/hotspot/jtreg/gc/TestTrimNative.java @@ -0,0 +1,422 @@ +/* + * Copyright (c) 2022 SAP SE. All rights reserved. + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +package gc; + +/* + * All these tests test the trim-native feature for all GCs. + * Trim-native is the ability to trim the C-heap as part of the GC cycle. + * This feature is controlled by -XX:+GCTrimNativeHeap (by default off). + * Trimming happens on full gc for all gcs. Shenandoah and G1 also support + * concurrent trimming (Shenandoah supports this without any ties to java + * heap occupancy). + * + */ + +//// full gc tests ///// + +/* + * @test id=fullgc-serial + * @summary Test that GCTrimNativeHeap works with 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-fullgc serial + */ + +/* + * @test id=fullgc-parallel + * @summary Test that GCTrimNativeHeap works with Parallel + * @requires vm.gc.Parallel + * @requires (os.family=="linux") & !vm.musl + * @modules java.base/jdk.internal.misc + * @library /test/lib + * @run driver gc.TestTrimNative test-fullgc parallel + */ + +/* + * @test id=fullgc-shenandoah + * @summary Test that GCTrimNativeHeap works with Shenandoah + * @requires vm.gc.Shenandoah + * @requires (os.family=="linux") & !vm.musl + * @modules java.base/jdk.internal.misc + * @library /test/lib + * @run driver gc.TestTrimNative test-fullgc shenandoah + */ + +/* + * @test id=fullgc-g1 + * @summary Test that GCTrimNativeHeap works with G1 + * @requires vm.gc.G1 + * @requires (os.family=="linux") & !vm.musl + * @modules java.base/jdk.internal.misc + * @library /test/lib + * @run driver gc.TestTrimNative test-fullgc g1 + */ + +/* + * @test id=fullgc-z + * @summary Test that GCTrimNativeHeap works with Z + * @requires vm.gc.Z + * @requires (os.family=="linux") & !vm.musl + * @modules java.base/jdk.internal.misc + * @library /test/lib + * @run driver gc.TestTrimNative test-fullgc z + */ + +//// auto mode tests ///// + +// Note: not serial, since it does not do periodic trimming, only trimming on full gc + +/* + * @test id=auto-parallel + * @summary Test that GCTrimNativeHeap works with Parallel + * @requires vm.gc.Parallel + * @requires (os.family=="linux") & !vm.musl + * @modules java.base/jdk.internal.misc + * @library /test/lib + * @run driver gc.TestTrimNative test-auto parallel + */ + +/* + * @test id=auto-shenandoah + * @summary Test that GCTrimNativeHeap works with Shenandoah + * @requires vm.gc.Shenandoah + * @requires (os.family=="linux") & !vm.musl + * @modules java.base/jdk.internal.misc + * @library /test/lib + * @run driver gc.TestTrimNative test-auto shenandoah + */ + +/* + * @test id=auto-g1 + * @summary Test that GCTrimNativeHeap works with G1 + * @requires vm.gc.G1 + * @requires (os.family=="linux") & !vm.musl + * @modules java.base/jdk.internal.misc + * @library /test/lib + * @run driver gc.TestTrimNative test-auto g1 + */ + +/* + * @test id=auto-z + * @summary Test that GCTrimNativeHeap works with Z + * @requires vm.gc.Z + * @requires (os.family=="linux") & !vm.musl + * @modules java.base/jdk.internal.misc + * @library /test/lib + * @run driver gc.TestTrimNative test-auto z + */ + +//// test-auto-high-interval interval test ///// + +// Note: not serial, since it does not do periodic trimming, only trimming on full gc + +/* + * @test id=auto-high-interval-parallel + * @summary Test that a high GCTrimNativeHeapInterval effectively disables automatic trimming + * @requires vm.gc.Parallel + * @requires (os.family=="linux") & !vm.musl + * @modules java.base/jdk.internal.misc + * @library /test/lib + * @run driver gc.TestTrimNative test-auto-high-interval parallel + */ + +/* + * @test id=auto-high-interval-g1 + * @summary Test that a high GCTrimNativeHeapInterval effectively disables automatic trimming + * @requires vm.gc.G1 + * @requires (os.family=="linux") & !vm.musl + * @modules java.base/jdk.internal.misc + * @library /test/lib + * @run driver gc.TestTrimNative test-auto-high-interval g1 + */ + +/* + * @test id=auto-high-interval-shenandoah + * @summary Test that a high GCTrimNativeHeapInterval effectively disables automatic trimming + * @requires vm.gc.Shenandoah + * @requires (os.family=="linux") & !vm.musl + * @modules java.base/jdk.internal.misc + * @library /test/lib + * @run driver gc.TestTrimNative test-auto-high-interval shenandoah + */ + +/* + * @test id=auto-high-interval-z + * @summary Test that a high GCTrimNativeHeapInterval effectively disables automatic trimming + * @requires vm.gc.Z + * @requires (os.family=="linux") & !vm.musl + * @modules java.base/jdk.internal.misc + * @library /test/lib + * @run driver gc.TestTrimNative test-auto-high-interval z + */ + +// Other tests + +/* + * @test id=off-explicit + * @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 test-off-explicit + */ + +/* + * @test id=off-by-default + * @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 test-off-by-default + */ + +/* + * @test id=off-on-other-platforms + * @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 test-off-on-other-platforms + */ + +import jdk.internal.misc.Unsafe; +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; + +public class TestTrimNative { + + // Actual RSS increase is a lot larger than 16MB. 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 numAllocations = 1024 * 1024; + static final int szAllocations = 16; + 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; } + } + + enum GC { + serial, parallel, g1, shenandoah, z; + String getSwitchName() { + String s = name(); + return "-XX:+Use" + s.substring(0, 1).toUpperCase() + s.substring(1) + "GC"; + } + boolean isShenandoah() { return this == GC.shenandoah; } + } + + static private final OutputAnalyzer runTestWithOptions(String[] extraOptions, String[] testArgs) throws IOException { + + List allOptions = new ArrayList(); + allOptions.add("-XX:+UnlockExperimentalVMOptions"); + allOptions.addAll(Arrays.asList(extraOptions)); + allOptions.add("-Xmx128m"); + allOptions.add("-Xms128m"); // Stabilize RSS + allOptions.add("-XX:+AlwaysPreTouch"); // Stabilize RSS + 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"); + allOptions.addAll(Arrays.asList(testArgs)); + ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(allOptions.toArray(new String[0])); + OutputAnalyzer output = new OutputAnalyzer(pb.start()); + output.shouldHaveExitValue(0); + return output; + + } + + /** + * Given JVM output, look for a log line that describes a successful negative trim in the megabyte range + * like this: + * "[2.053s][debug][gc,trim] Trim native heap (retain size: 5120K): RSS+Swap: 271M->223M (-49112K), 2.834ms" + * (Note: we use the "properXXX" print routines, therefore units can differ) + * Check that the sum of all trim log lines comes to a total RSS reduction in the MB range + * @param output + * @param minExpected min number of trim lines expected in UL log + * @param maxExpected max number of trim lines expected in UL log + */ + private final static void parseOutputAndLookForNegativeTrim(OutputAnalyzer output, int minExpected, int maxExpected) { + output.reportDiagnosticSummary(); + List lines = output.asLines(); + Pattern pat = Pattern.compile(".*\\[debug\\]\\[gc,trim\\] Trim native heap.*RSS\\+Swap: (\\d+)([KMB])->(\\d+)([KMB]).*"); + 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); + System.out.println("rss1: " + rss1 + " rss2 " + rss2); + } + numTrimsFound ++; + } + if (numTrimsFound > maxExpected) { + throw new RuntimeException("Abnormal high number of trim attempts found (more than " + maxExpected + + "). Does the interval setting not work?"); + } + } + if (numTrimsFound < minExpected) { + throw new RuntimeException("We found fewer trim lines in UL log than expected (expected " + minExpected + + ", found " + numTrimsFound + "."); + } + if (rssReductionTotal < (numAllocations * szAllocations)) { + throw new RuntimeException("We did not see the expected RSS reduction in the UL log."); + } + } + + // Test that GCTrimNativeHeap=1 causes a trim-native on full gc + static private final void testWithFullGC(GC gc) throws IOException { + System.out.println("testWithFullGC"); + OutputAnalyzer output = runTestWithOptions ( + new String[] { gc.getSwitchName(), "-XX:+GCTrimNativeHeap" }, + new String[] { "true" /* full gc */, "3000" /* ms after peak */ } + ); + // With default interval time of 30 seconds, auto trimming should never kick in, so the only + // log line we expect to see is the one from the full-gc induced trim. + parseOutputAndLookForNegativeTrim(output, 1, 1); + } + + // Test that GCTrimNativeHeap=1 causes a trim-native automatically, without GC (for now, shenandoah only) + static private final void testAuto(GC gc) throws IOException { + System.out.println("testAuto"); + OutputAnalyzer output = runTestWithOptions ( + new String[] { gc.getSwitchName(), "-XX:+GCTrimNativeHeap", "-XX:GCTrimNativeHeapInterval=1" }, + new String[] { "false" /* full gc */, "6000" /* ms after peak */ } + ); + // With an interval time of 1 second and a runtime of 6 seconds we expect to see 6 log lines (+ fudge factor). + parseOutputAndLookForNegativeTrim(output, 5, 7); + } + + // Test that trim-native correctly honors interval + static private final void testAutoWithHighInterval(GC gc) throws IOException { + // We pass a very high interval. This should disable the feature for this short-lived test, we should see no trim + System.out.println("testAutoWithHighInterval"); + OutputAnalyzer output = runTestWithOptions ( + new String[] { gc.getSwitchName(), "-XX:+GCTrimNativeHeap", "-XX:GCTrimNativeHeapInterval=30" }, + new String[] { "false" /* full gc */, "6000" /* ms after peak */ } + ); + output.shouldNotContain("Trim native heap"); + } + + // Test that trim-native gets disabled on platforms that don't support it. + static private final void testOffOnNonCompliantPlatforms() throws IOException { + // Logic is shared, so no need to test with every GC. Just use the default GC. + System.out.println("testOffOnNonCompliantPlatforms"); + OutputAnalyzer output = runTestWithOptions ( + new String[] { "-XX:+GCTrimNativeHeap" }, + new String[] { "true" /* full gc */, "2000" /* ms after peak */ } + ); + output.shouldContain("GCTrimNativeHeap disabled"); + output.shouldNotContain("Trim native heap"); + } + + // Test that GCTrimNativeHeap=0 switches trim-native off + static private final void testOffExplicit() throws IOException { + // Logic is shared, so no need to test with every GC. Just use the default GC. + System.out.println("testOffExplicit"); + OutputAnalyzer output = runTestWithOptions ( + new String[] { "-XX:-GCTrimNativeHeap" }, + new String[] { "true" /* full gc */, "2000" /* ms after peak */ } + ); + output.shouldNotContain("Trim native heap"); + } + + // Test that trim-native is disabled by default + static private final void testOffByDefault() throws IOException { + // Logic is shared, so no need to test with every GC. Just use the default GC. + System.out.println("testOffByDefault"); + OutputAnalyzer output = runTestWithOptions ( + new String[] { }, + new String[] { "true" /* full gc */, "2000" /* ms after peak */ } + ); + output.shouldNotContain("Trim native heap"); + } + + public static void main(String[] args) throws Exception { + + if (args.length == 0) { + throw new RuntimeException("Argument error"); + } + + if (args[0].equals("RUN")) { + boolean doFullGC = Boolean.parseBoolean(args[1]); + + System.out.println("Will spike now..."); + for (int i = 0; i < numAllocations; i++) { + ptrs[i] = Unsafe.getUnsafe().allocateMemory(szAllocations); + } + for (int i = 0; i < numAllocations; i++) { + Unsafe.getUnsafe().freeMemory(ptrs[i]); + } + System.out.println("Done spiking."); + + if (doFullGC) { + System.out.println("GC..."); + System.gc(); + } + + // give GC time to react + int time = Integer.parseInt(args[2]); + System.out.println("Sleeping..."); + Thread.sleep(time); + System.out.println("Done."); + + return; + + } else if (args[0].equals("test-fullgc")) { + final GC gc = GC.valueOf(args[1]); + testWithFullGC(gc); + } else if (args[0].equals("test-auto")) { + final GC gc = GC.valueOf(args[1]); + testAuto(gc); + } else if (args[0].equals("test-auto-high-interval")) { + final GC gc = GC.valueOf(args[1]); + testAutoWithHighInterval(gc); + } else if (args[0].equals("test-off-explicit")) { + testOffExplicit(); + } else if (args[0].equals("test-off-by-default")) { + testOffByDefault(); + } else if (args[0].equals("test-off-on-other-platforms")) { + testOffOnNonCompliantPlatforms(); + } 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 2688c8e8fe7e5..dfcff28fbfe71 100644 --- a/test/hotspot/jtreg/serviceability/dcmd/vm/TrimLibcHeapTest.java +++ b/test/hotspot/jtreg/serviceability/dcmd/vm/TrimLibcHeapTest.java @@ -44,7 +44,7 @@ public void run(CommandExecutor executor) { output.reportDiagnosticSummary(); output.shouldMatch("(Done|Not available)"); // Not available could happen on Linux + non-glibc (eg. muslc) if (output.firstMatch("Done") != null) { - output.shouldMatch("(Virtual size before|RSS before|Swap before|No details available)"); + output.shouldMatch("Done. RSS\\+Swap reduction:"); } } From 4b843df60a2d63fc52ae2047e8cbfa498a6d3507 Mon Sep 17 00:00:00 2001 From: tstuefe Date: Tue, 30 Aug 2022 17:17:09 +0200 Subject: [PATCH 02/27] some simplifications --- src/hotspot/os/aix/os_aix.cpp | 4 +- src/hotspot/os/bsd/os_bsd.cpp | 4 +- src/hotspot/os/linux/os_linux.cpp | 52 ++++++++----------- src/hotspot/os/linux/os_linux.hpp | 7 ++- src/hotspot/os/windows/os_windows.cpp | 4 +- .../share/gc/shared/gcTrimNativeHeap.cpp | 11 ++-- src/hotspot/share/gc/shared/gc_globals.hpp | 5 -- src/hotspot/share/runtime/os.hpp | 6 +-- 8 files changed, 38 insertions(+), 55 deletions(-) diff --git a/src/hotspot/os/aix/os_aix.cpp b/src/hotspot/os/aix/os_aix.cpp index 7cb862ce36025..1898898e41d41 100644 --- a/src/hotspot/os/aix/os_aix.cpp +++ b/src/hotspot/os/aix/os_aix.cpp @@ -2989,5 +2989,5 @@ void os::print_memory_mappings(char* addr, size_t bytes, outputStream* st) {} // stubbed-out trim-native support bool os::can_trim_native_heap() { return false; } -bool os::should_trim_native_heap(size_t retain_size) { return false; } -bool os::trim_native_heap(os::size_change_t* rss_change, size_t retain_size) { return false; } +bool os::should_trim_native_heap() { return false; } +bool os::trim_native_heap(os::size_change_t* rss_change) { return false; } diff --git a/src/hotspot/os/bsd/os_bsd.cpp b/src/hotspot/os/bsd/os_bsd.cpp index a1c0ccb972cf9..058e751a7b618 100644 --- a/src/hotspot/os/bsd/os_bsd.cpp +++ b/src/hotspot/os/bsd/os_bsd.cpp @@ -2448,5 +2448,5 @@ void os::print_memory_mappings(char* addr, size_t bytes, outputStream* st) {} // stubbed-out trim-native support bool os::can_trim_native_heap() { return false; } -bool os::should_trim_native_heap(size_t retain_size) { return false; } -bool os::trim_native_heap(os::size_change_t* rss_change, size_t retain_size) { return false; } +bool os::should_trim_native_heap() { return false; } +bool os::trim_native_heap(os::size_change_t* rss_change) { return false; } diff --git a/src/hotspot/os/linux/os_linux.cpp b/src/hotspot/os/linux/os_linux.cpp index 1bd205120dac2..150237bb4679d 100644 --- a/src/hotspot/os/linux/os_linux.cpp +++ b/src/hotspot/os/linux/os_linux.cpp @@ -169,13 +169,12 @@ const char * os::Linux::_libpthread_version = NULL; size_t os::Linux::_default_large_page_size = 0; #ifdef __GLIBC__ -// Note: mallinfo(3) vs mallinfo2(3): -// We want to be runnable with both old and new glibcs. Old glibcs just offer mallinfo(3). -// New glibcs deprecate mallinfo(3) and offer mallinfo2(3) as replacement. Future glibc's -// may remove mallinfo(3) altogether. +// We want to be runnable with both old and new glibcs. +// Old glibcs offer mallinfo(). New glibcs deprecate mallinfo() and offer mallinfo2() +// as replacement. Future glibc's may remove the deprecated mallinfo(). // Therefore we may have one, both, or possibly neither (?). Code should tolerate all -// cases, which is why we resolve the functions dynamically. Outside code uses -// os::Linux::get_mallinfo() utility function that hides this mess. +// cases, which is why we resolve the functions dynamically. Outside code should use +// the Linux::get_mallinfo() utility function which exists to hide this mess. struct glibc_mallinfo { int arena; int ordblks; @@ -188,8 +187,6 @@ struct glibc_mallinfo { int fordblks; int keepcost; }; -// struct glibc_mallinfo2 lives in os_linux.hpp since it does dual duty as output -// structure for both the native mallinfo2(3) as for the wrapper function typedef struct glibc_mallinfo (*mallinfo_func_t)(void); typedef struct os::Linux::glibc_mallinfo2 (*mallinfo2_func_t)(void); static mallinfo_func_t g_mallinfo = NULL; @@ -4651,11 +4648,6 @@ jint os::init_2(void) { FLAG_SET_DEFAULT(UseCodeCacheFlushing, false); } -#ifdef __GLIBC__ - log_debug(os)("mallinfo: %s", g_mallinfo ? "yes" : "no"); - log_debug(os)("mallinfo2: %s", g_mallinfo2 ? "yes" : "no"); -#endif - return JNI_OK; } @@ -5434,7 +5426,8 @@ os::Linux::mallinfo_retval_t os::Linux::get_mallinfo(glibc_mallinfo2* out) { *out = mi; return mallinfo_retval_t::ok; } else if (g_mallinfo) { - // not perfect but ok if process virt size < 4g or if wrapping does not matter to the caller + // mallinfo() returns 32-bit values. Not perfect but still useful if + // process virt size < 4g glibc_mallinfo mi = g_mallinfo(); out->arena = (int) mi.arena; out->ordblks = (int) mi.ordblks; @@ -5461,26 +5454,25 @@ bool os::can_trim_native_heap() { #endif } -bool os::should_trim_native_heap(size_t retain_size) { +static const size_t retain_size = 2 * M; + +bool os::should_trim_native_heap() { #ifdef __GLIBC__ bool rc = true; - // It is difficult to predict the effect a malloc_trim(3) will have. - // mallinfo(3) looks like a good candidate but does not work that well - // in practice. + // We try, using mallinfo, to predict whether a malloc_trim(3) will be beneficial. // - // "mallinfo::keepcost" is no help even if manpage claims this to be the - // projected trim size. In practice it is just a very small value with - // no relation to the actual effect trimming will have. + // "mallinfo::keepcost" is no help even if manpage claims this to be the projected + // trim size. In practice it is just a very small value with no relation to the actual + // effect trimming will have. // - // The best we have is "mallinfo::fordblks", the total chunk size of free - // blocks. Since only free blocks can be trimmed, a very low bar is to require - // that size to be higher than our retain size. Unfortunately, "mallinfo::fordblks" - // does not react to malloc_trim(3). Glibc trims by calling madvice(MADV_DONT_NEED) - // on suitable chunks, but does not destruct the chunks nor updates its - // bookkeeping. + // Our best bet is "mallinfo::fordblks", the total chunk size of free blocks. Since + // only free blocks can be trimmed, a very low bar is to require their combined size + // to be higher than our retain size. Note, however, that "mallinfo::fordblks" includes + // already-trimmed blocks, since glibc trims by calling madvice(MADV_DONT_NEED) on free + // chunks but does not update its bookkeeping. // - // In the end we want to prevent obvious bogus attempts to trim, and for that - // fordblks is good enough. + // In the end we want to prevent obvious bogus attempts to trim, and for that fordblks + // is good enough. os::Linux::glibc_mallinfo2 mi; os::Linux::mallinfo_retval_t mirc = os::Linux::get_mallinfo(&mi); const size_t total_free = mi.fordblks; @@ -5493,7 +5485,7 @@ bool os::should_trim_native_heap(size_t retain_size) { #endif } -bool os::trim_native_heap(os::size_change_t* rss_change, size_t retain_size) { +bool os::trim_native_heap(os::size_change_t* rss_change) { #ifdef __GLIBC__ os::Linux::meminfo_t info1; os::Linux::meminfo_t info2; diff --git a/src/hotspot/os/linux/os_linux.hpp b/src/hotspot/os/linux/os_linux.hpp index 5c6a1a9c179cf..7eba36dbf7391 100644 --- a/src/hotspot/os/linux/os_linux.hpp +++ b/src/hotspot/os/linux/os_linux.hpp @@ -394,10 +394,6 @@ class os::Linux { static void* resolve_function_descriptor(void* p); #ifdef __GLIBC__ - // get_mallinfo() is a wrapper for either mallinfo or mallinfo2, depending on what - // we find available. It will prefer mallinfo2() over mallinfo() if possible. If we only - // have mallinfo(), values may be 32-bit truncated and that is signalled via the return - // code "ok_but_possibly_wrapped". struct glibc_mallinfo2 { size_t arena; size_t ordblks; @@ -411,6 +407,9 @@ class os::Linux { size_t keepcost; }; enum class mallinfo_retval_t { ok, error, ok_but_possibly_wrapped }; + // get_mallinfo() is a wrapper for mallinfo/mallinfo2. It will prefer mallinfo2() if found. + // If we only have mallinfo(), values may be 32-bit truncated, which is signaled via + // "ok_but_possibly_wrapped". static mallinfo_retval_t get_mallinfo(glibc_mallinfo2* out); #endif }; diff --git a/src/hotspot/os/windows/os_windows.cpp b/src/hotspot/os/windows/os_windows.cpp index c92d226e384a4..c30d35a3d345a 100644 --- a/src/hotspot/os/windows/os_windows.cpp +++ b/src/hotspot/os/windows/os_windows.cpp @@ -5948,5 +5948,5 @@ void os::print_active_locale(outputStream* st) { // stubbed-out trim-native support bool os::can_trim_native_heap() { return false; } -bool os::should_trim_native_heap(size_t retain_size) { return false; } -bool os::trim_native_heap(os::size_change_t* rss_change, size_t retain_size) { return false; } +bool os::should_trim_native_heap() { return false; } +bool os::trim_native_heap(os::size_change_t* rss_change) { return false; } diff --git a/src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp b/src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp index c990dac0ad3a2..f96dc31e4f222 100644 --- a/src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp +++ b/src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp @@ -68,7 +68,7 @@ class NativeTrimmer : public ConcurrentGCThread { log_debug(gc, trim)("NativeTrimmer stopped."); break; } - if (os::should_trim_native_heap(GCTrimNativeHeapRetainSize)) { + if (os::should_trim_native_heap()) { GCTrimNative::do_trim(); } } @@ -121,11 +121,8 @@ void GCTrimNative::do_trim() { 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_debug(gc, trim)("Trim native heap (retain size: " PROPERFMT "): " - "RSS+Swap: " PROPERFMT "->" PROPERFMT " (%c" PROPERFMT "), %1.3fms", - PROPERARGS(GCTrimNativeHeapRetainSize), - PROPERARGS(sc.before), PROPERARGS(sc.after), - sign, PROPERARGS(delta), + log_debug(gc, trim)("Trim native heap: RSS+Swap: " PROPERFMT "->" PROPERFMT " (%c" PROPERFMT "), %1.3fms", + PROPERARGS(sc.before), PROPERARGS(sc.after), sign, PROPERARGS(delta), trim_time.seconds() * 1000); } else { log_debug(gc, trim)("Trim native heap (no details)"); @@ -171,7 +168,7 @@ bool GCTrimNative::should_trim(bool ignore_delay) { return GCTrimNativeHeap && os::can_trim_native_heap() && (ignore_delay || os::elapsedTime() > _next_trim_not_before) && - os::should_trim_native_heap(GCTrimNativeHeapRetainSize); + os::should_trim_native_heap(); } void GCTrimNative::execute_trim() { diff --git a/src/hotspot/share/gc/shared/gc_globals.hpp b/src/hotspot/share/gc/shared/gc_globals.hpp index 3b13f7c387004..ec4bf0133d49a 100644 --- a/src/hotspot/share/gc/shared/gc_globals.hpp +++ b/src/hotspot/share/gc/shared/gc_globals.hpp @@ -703,11 +703,6 @@ "enabled.") \ range(0, max_juint) \ \ - develop(size_t, GCTrimNativeHeapRetainSize, 5 * M, \ - "If GCTrimNativeHeap is enabled: Baseline threshold before that " \ - "trimming will not be attempted.") \ - range(0, SIZE_MAX) \ - \ // end of GC_FLAGS DECLARE_FLAGS(GC_FLAGS) diff --git a/src/hotspot/share/runtime/os.hpp b/src/hotspot/share/runtime/os.hpp index cb1211d17dd41..e59fd62220d70 100644 --- a/src/hotspot/share/runtime/os.hpp +++ b/src/hotspot/share/runtime/os.hpp @@ -443,13 +443,13 @@ class os: AllStatic { // Does the platform support trimming the native heap? static bool can_trim_native_heap(); - // Given a planned retain size of x, would the platform recommend trimming? - static bool should_trim_native_heap(size_t retain_size); + // Does the platform recommend trimming? + static bool should_trim_native_heap(); // Trim the C-heap. Returns RSS size change and optionally return the rss size change. // If trim was done but size change could not be obtained, SIZE_MAX is returned for after size. struct size_change_t { size_t before; size_t after; }; - static bool trim_native_heap(size_change_t* rss_change, size_t retain_size = 0); + static bool trim_native_heap(size_change_t* rss_change); // A diagnostic function to print memory mappings in the given range. static void print_memory_mappings(char* addr, size_t bytes, outputStream* st); From 7c7a3e0800f8d85a448b8225e4aea5b15531ce82 Mon Sep 17 00:00:00 2001 From: tstuefe Date: Tue, 30 Aug 2022 20:49:28 +0200 Subject: [PATCH 03/27] Fixes and Simplifications --- src/hotspot/os/linux/os_linux.cpp | 14 ++--- src/hotspot/os/linux/trimCHeapDCmd.cpp | 22 ++++--- src/hotspot/share/gc/shared/gcTimer.cpp | 9 +-- .../share/gc/shared/gcTrimNativeHeap.cpp | 6 +- .../share/utilities/globalDefinitions.hpp | 3 + test/hotspot/jtreg/gc/TestTrimNative.java | 58 +++++++++++++++++++ .../dcmd/vm/TrimLibcHeapTest.java | 2 +- 7 files changed, 79 insertions(+), 35 deletions(-) diff --git a/src/hotspot/os/linux/os_linux.cpp b/src/hotspot/os/linux/os_linux.cpp index 150237bb4679d..38058a4e1df87 100644 --- a/src/hotspot/os/linux/os_linux.cpp +++ b/src/hotspot/os/linux/os_linux.cpp @@ -5421,7 +5421,6 @@ void os::print_memory_mappings(char* addr, size_t bytes, outputStream* st) { #ifdef __GLIBC__ os::Linux::mallinfo_retval_t os::Linux::get_mallinfo(glibc_mallinfo2* out) { if (g_mallinfo2) { - // preferred. glibc_mallinfo2 mi = g_mallinfo2(); *out = mi; return mallinfo_retval_t::ok; @@ -5490,20 +5489,15 @@ bool os::trim_native_heap(os::size_change_t* rss_change) { os::Linux::meminfo_t info1; os::Linux::meminfo_t info2; - // Note: query_process_memory_info returns values in K - - // Query memory before... - bool have_info1 = (rss_change != nullptr) ? os::Linux::query_process_memory_info(&info1) : false; - + bool have_info1 = os::Linux::query_process_memory_info(&info1); ::malloc_trim(retain_size); - - // ...and after trim. - bool have_info2 = (rss_change != nullptr) ? os::Linux::query_process_memory_info(&info2) : false; + bool have_info2 = have_info1 && os::Linux::query_process_memory_info(&info2); ssize_t delta = (ssize_t) -1; if (have_info1 && have_info2 && info1.vmrss != -1 && info2.vmrss != -1 && info1.vmswap != -1 && info2.vmswap != -1) { + // Note: query_process_memory_info returns values in K rss_change->before = (info1.vmrss + info1.vmswap) * K; rss_change->after = (info2.vmrss + info2.vmswap) * K; } else { @@ -5512,6 +5506,6 @@ bool os::trim_native_heap(os::size_change_t* rss_change) { return true; #else - return 0; // musl + return false; // musl #endif } diff --git a/src/hotspot/os/linux/trimCHeapDCmd.cpp b/src/hotspot/os/linux/trimCHeapDCmd.cpp index 8b89fc83534af..7bdf97e442c98 100644 --- a/src/hotspot/os/linux/trimCHeapDCmd.cpp +++ b/src/hotspot/os/linux/trimCHeapDCmd.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 SAP SE. All rights reserved. + * Copyright (c) 2022 SAP SE. All rights reserved. * Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -24,12 +24,11 @@ */ #include "precompiled.hpp" -#include "logging/log.hpp" -#include "os_linux.hpp" #include "runtime/os.hpp" +#include "trimCHeapDCmd.hpp" #include "utilities/debug.hpp" #include "utilities/ostream.hpp" -#include "trimCHeapDCmd.hpp" +#include "utilities/globalDefinitions.hpp" #include @@ -37,15 +36,14 @@ void TrimCLibcHeapDCmd::execute(DCmdSource source, TRAPS) { if (os::can_trim_native_heap()) { os::size_change_t sc; if (os::trim_native_heap(&sc)) { - if (sc.after == SIZE_MAX) { - _output->print_cr("Done (no details available)."); + _output->print("Trim native heap: "); + 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 ? '-' : '+'; + _output->print_cr("Trim native heap: RSS+Swap: " PROPERFMT "->" PROPERFMT " (%c" PROPERFMT ")", + PROPERFMTARGS(sc.before), PROPERFMTARGS(sc.after), sign, PROPERFMTARGS(delta)); } else { - const size_t scale_to_use = sc.before > (10 * M) ? M : K; - const char scale_c = scale_to_use == K ? 'K' : 'M'; - const size_t m_before = sc.before / scale_to_use; - const size_t m_after = sc.after / scale_to_use; - _output->print_cr("Done. RSS+Swap reduction: " SIZE_FORMAT "%c->" SIZE_FORMAT "%c (" SSIZE_FORMAT "%c))", - m_before, scale_c, m_after, scale_c, (ssize_t)m_after - (ssize_t)m_before, scale_c); + _output->print_cr("Trim native heap: (no details available)."); } } } else { diff --git a/src/hotspot/share/gc/shared/gcTimer.cpp b/src/hotspot/share/gc/shared/gcTimer.cpp index 8f5f39ad23e1a..00a712c6a9767 100644 --- a/src/hotspot/share/gc/shared/gcTimer.cpp +++ b/src/hotspot/share/gc/shared/gcTimer.cpp @@ -131,14 +131,7 @@ void TimePartitions::clear() { } void TimePartitions::report_gc_phase_start(const char* name, const Ticks& time, GCPhase::PhaseType type) { -#ifdef ASSERT - bool avoid_phase_length_check = UseZGC; -#if INCLUDE_SHENANDOAHGC - // With Shenandoah, skip this check if we do high frequency trimming. - avoid_phase_length_check |= (UseShenandoahGC && GCTrimNativeHeap && GCTrimNativeHeapInterval < 2); -#endif - assert(avoid_phase_length_check || _phases->length() <= 1000, "Too many recorded phases? (count: %d)", _phases->length()); -#endif // ASSERT + assert(UseZGC || _phases->length() <= 1000, "Too many recorded phases? (count: %d)", _phases->length()); int level = _active_phases.count(); diff --git a/src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp b/src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp index f96dc31e4f222..504696aaf0f23 100644 --- a/src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp +++ b/src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp @@ -110,8 +110,6 @@ class NativeTrimmer : public ConcurrentGCThread { NativeTrimmer* NativeTrimmer::_the_trimmer = nullptr; -#define PROPERFMT SIZE_FORMAT "%s" -#define PROPERARGS(S) byte_size_in_proper_unit(S), proper_unit_for_byte_size(S) void GCTrimNative::do_trim() { Ticks start = Ticks::now(); @@ -122,7 +120,7 @@ void GCTrimNative::do_trim() { const size_t delta = sc.after < sc.before ? (sc.before - sc.after) : (sc.after - sc.before); const char sign = sc.after < sc.before ? '-' : '+'; log_debug(gc, trim)("Trim native heap: RSS+Swap: " PROPERFMT "->" PROPERFMT " (%c" PROPERFMT "), %1.3fms", - PROPERARGS(sc.before), PROPERARGS(sc.after), sign, PROPERARGS(delta), + PROPERFMTARGS(sc.before), PROPERFMTARGS(sc.after), sign, PROPERFMTARGS(delta), trim_time.seconds() * 1000); } else { log_debug(gc, trim)("Trim native heap (no details)"); @@ -167,7 +165,7 @@ void GCTrimNative::cleanup() { bool GCTrimNative::should_trim(bool ignore_delay) { return GCTrimNativeHeap && os::can_trim_native_heap() && - (ignore_delay || os::elapsedTime() > _next_trim_not_before) && + (ignore_delay || (GCTrimNativeHeapInterval > 0 && os::elapsedTime() > _next_trim_not_before)) && os::should_trim_native_heap(); } diff --git a/src/hotspot/share/utilities/globalDefinitions.hpp b/src/hotspot/share/utilities/globalDefinitions.hpp index f9fa4d081f7a5..01bd56e35e45b 100644 --- a/src/hotspot/share/utilities/globalDefinitions.hpp +++ b/src/hotspot/share/utilities/globalDefinitions.hpp @@ -352,6 +352,9 @@ inline T byte_size_in_proper_unit(T s) { } } +#define PROPERFMT SIZE_FORMAT "%s" +#define PROPERFMTARGS(S) byte_size_in_proper_unit(S), proper_unit_for_byte_size(S) + inline const char* exact_unit_for_byte_size(size_t s) { #ifdef _LP64 if (s >= G && (s % G) == 0) { diff --git a/test/hotspot/jtreg/gc/TestTrimNative.java b/test/hotspot/jtreg/gc/TestTrimNative.java index c96ea62ccbab7..625d332237c35 100644 --- a/test/hotspot/jtreg/gc/TestTrimNative.java +++ b/test/hotspot/jtreg/gc/TestTrimNative.java @@ -175,6 +175,50 @@ * @run driver gc.TestTrimNative test-auto-high-interval z */ +//// test-auto-interval-0 test ///// + +// Note: not serial, since it does not do periodic trimming, only trimming on full gc + +/* + * @test id=auto-zero-interval-parallel + * @summary Test that a GCTrimNativeHeapInterval=0 disables periodic trimming + * @requires vm.gc.Parallel + * @requires (os.family=="linux") & !vm.musl + * @modules java.base/jdk.internal.misc + * @library /test/lib + * @run driver gc.TestTrimNative test-auto-zero-interval parallel + */ + +/* + * @test id=auto-zero-interval-g1 + * @summary Test that a GCTrimNativeHeapInterval=0 disables periodic trimming + * @requires vm.gc.G1 + * @requires (os.family=="linux") & !vm.musl + * @modules java.base/jdk.internal.misc + * @library /test/lib + * @run driver gc.TestTrimNative test-auto-zero-interval g1 + */ + +/* + * @test id=auto-zero-interval-shenandoah + * @summary Test that a GCTrimNativeHeapInterval=0 disables periodic trimming + * @requires vm.gc.Shenandoah + * @requires (os.family=="linux") & !vm.musl + * @modules java.base/jdk.internal.misc + * @library /test/lib + * @run driver gc.TestTrimNative test-auto-zero-interval shenandoah + */ + +/* + * @test id=auto-zero-interval-z + * @summary Test that a GCTrimNativeHeapInterval=0 disables periodic trimming + * @requires vm.gc.Z + * @requires (os.family=="linux") & !vm.musl + * @modules java.base/jdk.internal.misc + * @library /test/lib + * @run driver gc.TestTrimNative test-auto-zero-interval z + */ + // Other tests /* @@ -333,6 +377,17 @@ static private final void testAutoWithHighInterval(GC gc) throws IOException { output.shouldNotContain("Trim native heap"); } + // Test that trim-native correctly honors interval + static private final void testAutoWithZeroInterval(GC gc) throws IOException { + // We pass a very high interval. This should disable the feature for this short-lived test, we should see no trim + System.out.println("testAutoWithHighInterval"); + OutputAnalyzer output = runTestWithOptions ( + new String[] { gc.getSwitchName(), "-XX:+GCTrimNativeHeap", "-XX:GCTrimNativeHeapInterval=0" }, + new String[] { "false" /* full gc */, "6000" /* ms after peak */ } + ); + output.shouldNotContain("Trim native heap"); + } + // Test that trim-native gets disabled on platforms that don't support it. static private final void testOffOnNonCompliantPlatforms() throws IOException { // Logic is shared, so no need to test with every GC. Just use the default GC. @@ -407,6 +462,9 @@ public static void main(String[] args) throws Exception { } else if (args[0].equals("test-auto-high-interval")) { final GC gc = GC.valueOf(args[1]); testAutoWithHighInterval(gc); + } else if (args[0].equals("test-auto-zero-interval")) { + final GC gc = GC.valueOf(args[1]); + testAutoWithZeroInterval(gc); } else if (args[0].equals("test-off-explicit")) { testOffExplicit(); } else if (args[0].equals("test-off-by-default")) { diff --git a/test/hotspot/jtreg/serviceability/dcmd/vm/TrimLibcHeapTest.java b/test/hotspot/jtreg/serviceability/dcmd/vm/TrimLibcHeapTest.java index dfcff28fbfe71..9eb49cd048c3a 100644 --- a/test/hotspot/jtreg/serviceability/dcmd/vm/TrimLibcHeapTest.java +++ b/test/hotspot/jtreg/serviceability/dcmd/vm/TrimLibcHeapTest.java @@ -44,7 +44,7 @@ public void run(CommandExecutor executor) { output.reportDiagnosticSummary(); output.shouldMatch("(Done|Not available)"); // Not available could happen on Linux + non-glibc (eg. muslc) if (output.firstMatch("Done") != null) { - output.shouldMatch("Done. RSS\\+Swap reduction:"); + output.shouldContain("Done. RSS+Swap reduction:"); } } From 864b3fb58b037967637b066c1a37d25e30f17da2 Mon Sep 17 00:00:00 2001 From: tstuefe Date: Wed, 31 Aug 2022 08:01:54 +0200 Subject: [PATCH 04/27] wip --- src/hotspot/os/linux/trimCHeapDCmd.cpp | 4 +- src/hotspot/share/gc/g1/g1CollectedHeap.cpp | 5 -- src/hotspot/share/gc/g1/g1CollectedHeap.hpp | 1 - src/hotspot/share/gc/g1/g1FullCollector.cpp | 7 +++ .../share/gc/parallel/psParallelCompact.cpp | 3 + .../share/gc/shared/gcTrimNativeHeap.cpp | 60 +++++++++++++++---- .../share/gc/shared/gcTrimNativeHeap.hpp | 7 ++- src/hotspot/share/gc/z/zDriver.cpp | 19 +++++- src/hotspot/share/gc/z/zDriver.hpp | 3 + test/hotspot/jtreg/gc/TestTrimNative.java | 20 ++++++- .../dcmd/vm/TrimLibcHeapTest.java | 7 +-- 11 files changed, 107 insertions(+), 29 deletions(-) diff --git a/src/hotspot/os/linux/trimCHeapDCmd.cpp b/src/hotspot/os/linux/trimCHeapDCmd.cpp index 7bdf97e442c98..a17579e6e8c55 100644 --- a/src/hotspot/os/linux/trimCHeapDCmd.cpp +++ b/src/hotspot/os/linux/trimCHeapDCmd.cpp @@ -40,10 +40,10 @@ void TrimCLibcHeapDCmd::execute(DCmdSource source, TRAPS) { 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 ? '-' : '+'; - _output->print_cr("Trim native heap: RSS+Swap: " PROPERFMT "->" PROPERFMT " (%c" PROPERFMT ")", + _output->print_cr("RSS+Swap: " PROPERFMT "->" PROPERFMT " (%c" PROPERFMT ")", PROPERFMTARGS(sc.before), PROPERFMTARGS(sc.after), sign, PROPERFMTARGS(delta)); } else { - _output->print_cr("Trim native heap: (no details available)."); + _output->print_cr("(no details available)."); } } } else { diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp index 573677b0a1cae..3217463d51787 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp @@ -1032,7 +1032,6 @@ void G1CollectedHeap::prepare_heap_for_mutators() { abort_refinement(); resize_heap_if_necessary(); uncommit_regions_if_necessary(); - trim_native_heap(); // Rebuild the code root lists for each region rebuild_code_roots(); @@ -2626,10 +2625,6 @@ void G1CollectedHeap::uncommit_regions_if_necessary() { } } -void G1CollectedHeap::trim_native_heap() { - GCTrimNative::schedule_trim(); -} - void G1CollectedHeap::verify_numa_regions(const char* desc) { LogTarget(Trace, gc, heap, verify) lt; diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp index 1efe1017ee1be..fa3cdd2abd913 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp @@ -545,7 +545,6 @@ class G1CollectedHeap : public CollectedHeap { // Immediately uncommit uncommittable regions. uint uncommit_regions(uint region_limit); bool has_uncommittable_regions(); - void trim_native_heap(); G1NUMA* numa() const { return _numa; } diff --git a/src/hotspot/share/gc/g1/g1FullCollector.cpp b/src/hotspot/share/gc/g1/g1FullCollector.cpp index 3287a79169621..e07ae0f462c5b 100644 --- a/src/hotspot/share/gc/g1/g1FullCollector.cpp +++ b/src/hotspot/share/gc/g1/g1FullCollector.cpp @@ -38,6 +38,7 @@ #include "gc/g1/g1Policy.hpp" #include "gc/g1/g1RegionMarkStatsCache.inline.hpp" #include "gc/shared/gcTraceTime.inline.hpp" +#include "gc/shared/gcTrimNativeHeap.hpp" #include "gc/shared/preservedMarks.hpp" #include "gc/shared/referenceProcessor.hpp" #include "gc/shared/verifyOption.hpp" @@ -171,6 +172,10 @@ class PrepareRegionsClosure : public HeapRegionClosure { }; void G1FullCollector::prepare_collection() { + + // Pause native trimming for the duration of the GC + GCTrimNative::pause_periodic_trim(); + _heap->policy()->record_full_collection_start(); // Verification needs the bitmap, so we should clear the bitmap only later. @@ -232,6 +237,8 @@ void G1FullCollector::complete_collection() { _heap->gc_epilogue(true); _heap->verify_after_full_collection(); + + GCTrimNative::schedule_trim(); } void G1FullCollector::before_marking_update_attribute_table(HeapRegion* hr) { diff --git a/src/hotspot/share/gc/parallel/psParallelCompact.cpp b/src/hotspot/share/gc/parallel/psParallelCompact.cpp index 5dadcbdd1740b..c93538da8aed6 100644 --- a/src/hotspot/share/gc/parallel/psParallelCompact.cpp +++ b/src/hotspot/share/gc/parallel/psParallelCompact.cpp @@ -1711,6 +1711,9 @@ bool PSParallelCompact::invoke_no_policy(bool maximum_heap_compaction) { return false; } + // Pause native trimming for the duration of the GC + GCTrimNative::pause_periodic_trim(); + ParallelScavengeHeap* heap = ParallelScavengeHeap::heap(); GCIdMark gc_id_mark; diff --git a/src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp b/src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp index 504696aaf0f23..5499e63f630a6 100644 --- a/src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp +++ b/src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp @@ -41,7 +41,7 @@ double GCTrimNative::_next_trim_not_before = 0; // GCTrimNative works in two modes: // // - async mode, where GCTrimNative runs a trimmer thread on behalf of the GC. -// The trimmer thread will be doing all the trims, either periodically or +// The trimmer thread will be doing all the trims, both periodically and // triggered from outside via GCTrimNative::schedule_trim(). // // - synchronous mode, where the GC does the trimming itself in its own thread, @@ -52,6 +52,7 @@ double GCTrimNative::_next_trim_not_before = 0; class NativeTrimmer : public ConcurrentGCThread { Monitor* _lock; + volatile bool _paused; static NativeTrimmer* _the_trimmer; protected: @@ -60,15 +61,19 @@ class NativeTrimmer : public ConcurrentGCThread { assert(GCTrimNativeHeap, "Sanity"); assert(os::can_trim_native_heap(), "Sanity"); + log_info(gc, trim)("NativeTrimmer started."); + + // Note: GCTrimNativeHeapInterval=0 -> zero wait time -> indefinite waits, disabling periodic trim const int64_t delay_ms = GCTrimNativeHeapInterval * 1000; for (;;) { MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag); - ml.wait(delay_ms); // Note: GCTrimNativeHeapDelay == 0 disables periodic trim, no timeout then + ml.wait(delay_ms); if (should_terminate()) { - log_debug(gc, trim)("NativeTrimmer stopped."); + log_info(gc, trim)("NativeTrimmer stopped."); break; } - if (os::should_trim_native_heap()) { + bool paused = Atomic::load(&_paused); + if (!paused && os::should_trim_native_heap()) { GCTrimNative::do_trim(); } } @@ -79,6 +84,16 @@ class NativeTrimmer : public ConcurrentGCThread { ml.notify_all(); } + void pause() { + Atomic::store(&_paused, true); + log_debug(gc, trim)("NativeTrimmer paused"); + } + + void unpause() { + Atomic::store(&_paused, false); + log_debug(gc, trim)("NativeTrimmer unpaused"); + } + virtual void stop_service() { wakeup(); } @@ -86,7 +101,8 @@ class NativeTrimmer : public ConcurrentGCThread { public: NativeTrimmer() : - _lock(new (std::nothrow) PaddedMonitor(Mutex::nosafepoint, "NativeTrimmer_lock")) + _lock(new (std::nothrow) PaddedMonitor(Mutex::nosafepoint, "NativeTrimmer_lock")), + _paused(false) {} static bool is_enabled() { @@ -102,7 +118,16 @@ class NativeTrimmer : public ConcurrentGCThread { _the_trimmer->stop(); } + static void pause_periodic_trim() { + _the_trimmer->pause(); + } + + static void unpause_periodic_trim() { + _the_trimmer->unpause(); + } + static void schedule_trim_now() { + _the_trimmer->unpause(); _the_trimmer->wakeup(); } @@ -110,7 +135,6 @@ class NativeTrimmer : public ConcurrentGCThread { NativeTrimmer* NativeTrimmer::_the_trimmer = nullptr; - void GCTrimNative::do_trim() { Ticks start = Ticks::now(); os::size_change_t sc; @@ -119,11 +143,11 @@ void GCTrimNative::do_trim() { 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_debug(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); + 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_debug(gc, trim)("Trim native heap (no details)"); + log_info(gc, trim)("Trim native heap (no details)"); } } } @@ -147,7 +171,6 @@ void GCTrimNative::initialize(bool async_mode) { // If we are to run the trimmer on behalf of the GC: if (_async_mode) { NativeTrimmer::start_trimmer(); - log_debug(gc, trim)("Native Trimmer enabled."); } _next_trim_not_before = GCTrimNativeHeapInterval; @@ -177,7 +200,20 @@ void GCTrimNative::execute_trim() { } } -// Schedule trim-native for execution in the trimmer thread; return immediately. +void GCTrimNative::pause_periodic_trim() { + if (GCTrimNativeHeap) { + assert(_async_mode, "Only call for async mode"); + NativeTrimmer::pause_periodic_trim(); + } +} + +void GCTrimNative::unpause_periodic_trim() { + if (GCTrimNativeHeap) { + assert(_async_mode, "Only call for async mode"); + NativeTrimmer::unpause_periodic_trim(); + } +} + void GCTrimNative::schedule_trim() { if (GCTrimNativeHeap) { assert(_async_mode, "Only call for async mode"); diff --git a/src/hotspot/share/gc/shared/gcTrimNativeHeap.hpp b/src/hotspot/share/gc/shared/gcTrimNativeHeap.hpp index 8cec43d4bf0fe..7ac5a5723f713 100644 --- a/src/hotspot/share/gc/shared/gcTrimNativeHeap.hpp +++ b/src/hotspot/share/gc/shared/gcTrimNativeHeap.hpp @@ -52,7 +52,12 @@ class GCTrimNative : public AllStatic { // Execute trim-native in this thread static void execute_trim(); - // Schedule trim-native for execution in the trimmer thread; return immediately. + // Pause/unpause periodic trim + static void pause_periodic_trim(); + static void unpause_periodic_trim(); + + // Schedule an explicit trim now; if periodic trims had been + // paused, they are unpaused. static void schedule_trim(); }; diff --git a/src/hotspot/share/gc/z/zDriver.cpp b/src/hotspot/share/gc/z/zDriver.cpp index e21994db45275..28f97f895cc07 100644 --- a/src/hotspot/share/gc/z/zDriver.cpp +++ b/src/hotspot/share/gc/z/zDriver.cpp @@ -449,6 +449,8 @@ class ZDriverGCScope : public StackObj { void ZDriver::gc(const ZDriverRequest& request) { ZDriverGCScope scope(request); + pause_native_trim(); + // Phase 1: Pause Mark Start pause_mark_start(); @@ -482,9 +484,22 @@ void ZDriver::gc(const ZDriverRequest& request) { // Phase 10: Concurrent Relocate concurrent(relocate); - // GCTrimNativeHeap: A user-requested GC should trim the C-Heap right away - if (GCCause::is_user_requested_gc(request.cause())) { + resume_native_trim(request.cause()); + +} + +void ZDriver::pause_native_trim() { + // Pause native trimming for the duration of the GC + GCTrimNative::pause_periodic_trim(); +} + +void ZDriver::resume_native_trim(GCCause::Cause cause) { + bool schedule_trim_now = + cause == GCCause::_z_high_usage || GCCause::is_user_requested_gc(cause); + if (schedule_trim_now) { GCTrimNative::schedule_trim(); + } else { + GCTrimNative::unpause_periodic_trim(); } } diff --git a/src/hotspot/share/gc/z/zDriver.hpp b/src/hotspot/share/gc/z/zDriver.hpp index 08b1b80f2aa35..7601b83230ed8 100644 --- a/src/hotspot/share/gc/z/zDriver.hpp +++ b/src/hotspot/share/gc/z/zDriver.hpp @@ -67,6 +67,9 @@ class ZDriver : public ConcurrentGCThread { void check_out_of_memory(); + void pause_native_trim(); + void resume_native_trim(GCCause::Cause cause); + void gc(const ZDriverRequest& request); protected: diff --git a/test/hotspot/jtreg/gc/TestTrimNative.java b/test/hotspot/jtreg/gc/TestTrimNative.java index 625d332237c35..313d772f65c59 100644 --- a/test/hotspot/jtreg/gc/TestTrimNative.java +++ b/test/hotspot/jtreg/gc/TestTrimNative.java @@ -278,9 +278,17 @@ String getSwitchName() { String s = name(); return "-XX:+Use" + s.substring(0, 1).toUpperCase() + s.substring(1) + "GC"; } + boolean isZ() { return this == GC.z; } + boolean isSerial() { return this == GC.serial; } + boolean isParallel() { return this == GC.parallel; } + boolean isG1() { return this == GC.g1; } boolean isShenandoah() { return this == GC.shenandoah; } } + static private boolean usesNativeTrimmer(GC gc) { + return gc.isG1() || gc.isParallel() || gc.isZ(); + } + static private final OutputAnalyzer runTestWithOptions(String[] extraOptions, String[] testArgs) throws IOException { List allOptions = new ArrayList(); @@ -315,7 +323,7 @@ static private final OutputAnalyzer runTestWithOptions(String[] extraOptions, St private final static void parseOutputAndLookForNegativeTrim(OutputAnalyzer output, int minExpected, int maxExpected) { output.reportDiagnosticSummary(); List lines = output.asLines(); - Pattern pat = Pattern.compile(".*\\[debug\\]\\[gc,trim\\] Trim native heap.*RSS\\+Swap: (\\d+)([KMB])->(\\d+)([KMB]).*"); + Pattern pat = Pattern.compile(".*\\[gc,trim\\] Trim native heap.*RSS\\+Swap: (\\d+)([KMB])->(\\d+)([KMB]).*"); int numTrimsFound = 0; long rssReductionTotal = 0; for (String line : lines) { @@ -353,6 +361,16 @@ static private final void testWithFullGC(GC gc) throws IOException { // With default interval time of 30 seconds, auto trimming should never kick in, so the only // log line we expect to see is the one from the full-gc induced trim. parseOutputAndLookForNegativeTrim(output, 1, 1); + // For GCs that use the NativeTrimmer, we want to see the NativeTrimmer paused during the GC, as well as + // started and shut down properly. + if (usesNativeTrimmer(gc)) { + output.shouldContain("NativeTrimmer started"); + output.shouldContain("NativeTrimmer paused"); + output.shouldContain("NativeTrimmer unpaused"); + output.shouldContain("NativeTrimmer stopped"); + } else { + output.shouldNotContain("NativeTrimmer"); + } } // Test that GCTrimNativeHeap=1 causes a trim-native automatically, without GC (for now, shenandoah only) diff --git a/test/hotspot/jtreg/serviceability/dcmd/vm/TrimLibcHeapTest.java b/test/hotspot/jtreg/serviceability/dcmd/vm/TrimLibcHeapTest.java index 9eb49cd048c3a..5c18d711c7ee7 100644 --- a/test/hotspot/jtreg/serviceability/dcmd/vm/TrimLibcHeapTest.java +++ b/test/hotspot/jtreg/serviceability/dcmd/vm/TrimLibcHeapTest.java @@ -31,7 +31,7 @@ * @test * @summary Test of diagnostic command VM.trim_libc_heap * @library /test/lib - * @requires os.family == "linux" + * @requires (os.family=="linux") & !vm.musl * @modules java.base/jdk.internal.misc * java.compiler * java.management @@ -42,10 +42,7 @@ public class TrimLibcHeapTest { public void run(CommandExecutor executor) { OutputAnalyzer output = executor.execute("System.trim_native_heap"); output.reportDiagnosticSummary(); - output.shouldMatch("(Done|Not available)"); // Not available could happen on Linux + non-glibc (eg. muslc) - if (output.firstMatch("Done") != null) { - output.shouldContain("Done. RSS+Swap reduction:"); - } + output.shouldMatch(".*Trim native heap: RSS\\+Swap: \\d+[BKM]->\\d+[BKM].*"); } @Test From cbf027a0640c69d2de0af0c93506d04d9b74a57e Mon Sep 17 00:00:00 2001 From: tstuefe Date: Thu, 1 Sep 2022 08:43:34 +0200 Subject: [PATCH 05/27] make tests more stable on slow hardware --- test/hotspot/jtreg/gc/TestTrimNative.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/hotspot/jtreg/gc/TestTrimNative.java b/test/hotspot/jtreg/gc/TestTrimNative.java index 313d772f65c59..fa7e73732d58a 100644 --- a/test/hotspot/jtreg/gc/TestTrimNative.java +++ b/test/hotspot/jtreg/gc/TestTrimNative.java @@ -376,12 +376,15 @@ static private final void testWithFullGC(GC gc) throws IOException { // Test that GCTrimNativeHeap=1 causes a trim-native automatically, without GC (for now, shenandoah only) static private final void testAuto(GC gc) throws IOException { System.out.println("testAuto"); + long t1 = System.currentTimeMillis(); OutputAnalyzer output = runTestWithOptions ( new String[] { gc.getSwitchName(), "-XX:+GCTrimNativeHeap", "-XX:GCTrimNativeHeapInterval=1" }, new String[] { "false" /* full gc */, "6000" /* ms after peak */ } ); - // With an interval time of 1 second and a runtime of 6 seconds we expect to see 6 log lines (+ fudge factor). - parseOutputAndLookForNegativeTrim(output, 5, 7); + long t2 = System.currentTimeMillis(); + int runtime_s = (int)((t2 - t1) / 1000); + // With an interval time of 1 second and a runtime of 6..x seconds we expect to see x log lines (+- fudge factor). + parseOutputAndLookForNegativeTrim(output, runtime_s - 4, runtime_s + 2); } // Test that trim-native correctly honors interval From 35938c69b1684a646251426fe1c786942f710dcc Mon Sep 17 00:00:00 2001 From: tstuefe Date: Thu, 1 Sep 2022 08:44:00 +0200 Subject: [PATCH 06/27] reduce test runtime on slow hardware --- test/hotspot/jtreg/gc/TestTrimNative.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/test/hotspot/jtreg/gc/TestTrimNative.java b/test/hotspot/jtreg/gc/TestTrimNative.java index fa7e73732d58a..b720bcccb8308 100644 --- a/test/hotspot/jtreg/gc/TestTrimNative.java +++ b/test/hotspot/jtreg/gc/TestTrimNative.java @@ -259,11 +259,13 @@ public class TestTrimNative { - // Actual RSS increase is a lot larger than 16MB. Depends on glibc overhead, and NMT malloc headers in debug VMs. + // 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 numAllocations = 1024 * 1024; static final int szAllocations = 16; + static final int totalAllocationsSize = 4 * 1024 * 1024; + static final int numAllocations = totalAllocationsSize / szAllocations; + static long[] ptrs = new long[numAllocations]; enum Unit { @@ -354,9 +356,10 @@ private final static void parseOutputAndLookForNegativeTrim(OutputAnalyzer outpu // Test that GCTrimNativeHeap=1 causes a trim-native on full gc static private final void testWithFullGC(GC gc) throws IOException { System.out.println("testWithFullGC"); + int sleeptime_secs = 2; OutputAnalyzer output = runTestWithOptions ( new String[] { gc.getSwitchName(), "-XX:+GCTrimNativeHeap" }, - new String[] { "true" /* full gc */, "3000" /* ms after peak */ } + new String[] { "true" /* full gc */, String.valueOf(sleeptime_secs * 1000) /* ms after peak */ } ); // With default interval time of 30 seconds, auto trimming should never kick in, so the only // log line we expect to see is the one from the full-gc induced trim. @@ -377,9 +380,10 @@ static private final void testWithFullGC(GC gc) throws IOException { static private final void testAuto(GC gc) throws IOException { System.out.println("testAuto"); long t1 = System.currentTimeMillis(); + int sleeptime_secs = 4; OutputAnalyzer output = runTestWithOptions ( new String[] { gc.getSwitchName(), "-XX:+GCTrimNativeHeap", "-XX:GCTrimNativeHeapInterval=1" }, - new String[] { "false" /* full gc */, "6000" /* ms after peak */ } + new String[] { "false" /* full gc */, String.valueOf(sleeptime_secs * 1000) /* ms after peak */ } ); long t2 = System.currentTimeMillis(); int runtime_s = (int)((t2 - t1) / 1000); @@ -455,6 +459,8 @@ public static void main(String[] args) throws Exception { 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]); From 40b3e362ee3b92b71810d887c65a24051f2aa5ad Mon Sep 17 00:00:00 2001 From: tstuefe Date: Wed, 19 Oct 2022 15:45:36 +0200 Subject: [PATCH 07/27] Make test more fault tolerant --- test/hotspot/jtreg/gc/TestTrimNative.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/test/hotspot/jtreg/gc/TestTrimNative.java b/test/hotspot/jtreg/gc/TestTrimNative.java index b720bcccb8308..494e6d20534eb 100644 --- a/test/hotspot/jtreg/gc/TestTrimNative.java +++ b/test/hotspot/jtreg/gc/TestTrimNative.java @@ -263,7 +263,7 @@ public class TestTrimNative { // 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 = 4 * 1024 * 1024; + static final int totalAllocationsSize = 16 * 1024 * 1024; // 16 MB total static final int numAllocations = totalAllocationsSize / szAllocations; static long[] ptrs = new long[numAllocations]; @@ -333,9 +333,9 @@ private final static void parseOutputAndLookForNegativeTrim(OutputAnalyzer outpu 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); - System.out.println("rss1: " + rss1 + " rss2 " + rss2); } numTrimsFound ++; } @@ -348,8 +348,14 @@ private final static void parseOutputAndLookForNegativeTrim(OutputAnalyzer outpu throw new RuntimeException("We found fewer trim lines in UL log than expected (expected " + minExpected + ", found " + numTrimsFound + "."); } - if (rssReductionTotal < (numAllocations * szAllocations)) { - throw new RuntimeException("We did not see the expected RSS reduction in the UL log."); + // This is very fuzzy. We malloced X, free'd X, trimmed, measured the combined effect of all reductions. + // This does not take into effect mallocs or frees that may happen concurrently. But we expect to see *some* + // reduction somewhere. Test with a fudge factor. + float fudge = 0.8f; + 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 + "."); } } From 5b997d6595e3733e111bc59e8c131aa444cfc8b8 Mon Sep 17 00:00:00 2001 From: tstuefe Date: Thu, 12 Jan 2023 09:40:19 +0100 Subject: [PATCH 08/27] Fix merge errors --- src/hotspot/os/aix/os_aix.cpp | 5 ----- src/hotspot/os/aix/os_aix.inline.hpp | 1 + src/hotspot/os/bsd/os_bsd.cpp | 5 ----- src/hotspot/os/bsd/os_bsd.inline.hpp | 1 + src/hotspot/os/linux/os_linux.cpp | 4 ---- src/hotspot/os/windows/os_windows.cpp | 5 ----- src/hotspot/os/windows/os_windows.inline.hpp | 1 + 7 files changed, 3 insertions(+), 19 deletions(-) diff --git a/src/hotspot/os/aix/os_aix.cpp b/src/hotspot/os/aix/os_aix.cpp index 7dbc7302e5520..ff7eefa7f7d82 100644 --- a/src/hotspot/os/aix/os_aix.cpp +++ b/src/hotspot/os/aix/os_aix.cpp @@ -2984,8 +2984,3 @@ bool os::supports_map_sync() { } void os::print_memory_mappings(char* addr, size_t bytes, outputStream* st) {} - -// stubbed-out trim-native support -bool os::can_trim_native_heap() { return false; } -bool os::should_trim_native_heap() { return false; } -bool os::trim_native_heap(os::size_change_t* rss_change) { return false; } diff --git a/src/hotspot/os/aix/os_aix.inline.hpp b/src/hotspot/os/aix/os_aix.inline.hpp index 5f7415e4a5181..dd10aab76710f 100644 --- a/src/hotspot/os/aix/os_aix.inline.hpp +++ b/src/hotspot/os/aix/os_aix.inline.hpp @@ -54,6 +54,7 @@ inline void os::map_stack_shadow_pages(address sp) { // stubbed-out trim-native support inline bool os::can_trim_native_heap() { return false; } +inline bool os::should_trim_native_heap() { return false; } inline bool os::trim_native_heap(os::size_change_t* rss_change) { return false; } #endif // OS_AIX_OS_AIX_INLINE_HPP diff --git a/src/hotspot/os/bsd/os_bsd.cpp b/src/hotspot/os/bsd/os_bsd.cpp index b6aa9e66a6b57..c9b21f2cabbfb 100644 --- a/src/hotspot/os/bsd/os_bsd.cpp +++ b/src/hotspot/os/bsd/os_bsd.cpp @@ -2429,8 +2429,3 @@ bool os::start_debugging(char *buf, int buflen) { } void os::print_memory_mappings(char* addr, size_t bytes, outputStream* st) {} - -// stubbed-out trim-native support -bool os::can_trim_native_heap() { return false; } -bool os::should_trim_native_heap() { return false; } -bool os::trim_native_heap(os::size_change_t* rss_change) { return false; } diff --git a/src/hotspot/os/bsd/os_bsd.inline.hpp b/src/hotspot/os/bsd/os_bsd.inline.hpp index f30ac61e463ff..055a164e14495 100644 --- a/src/hotspot/os/bsd/os_bsd.inline.hpp +++ b/src/hotspot/os/bsd/os_bsd.inline.hpp @@ -57,6 +57,7 @@ inline void os::map_stack_shadow_pages(address sp) { // stubbed-out trim-native support inline bool os::can_trim_native_heap() { return false; } +inline bool os::should_trim_native_heap() { return false; } inline bool os::trim_native_heap(os::size_change_t* rss_change) { return false; } #endif // OS_BSD_OS_BSD_INLINE_HPP diff --git a/src/hotspot/os/linux/os_linux.cpp b/src/hotspot/os/linux/os_linux.cpp index bc0fcf357a312..f30118e439e92 100644 --- a/src/hotspot/os/linux/os_linux.cpp +++ b/src/hotspot/os/linux/os_linux.cpp @@ -5445,7 +5445,3 @@ bool os::should_trim_native_heap() { return false; // musl #endif } - - - - diff --git a/src/hotspot/os/windows/os_windows.cpp b/src/hotspot/os/windows/os_windows.cpp index 63d5bbfc41955..1d2954014a3f6 100644 --- a/src/hotspot/os/windows/os_windows.cpp +++ b/src/hotspot/os/windows/os_windows.cpp @@ -5949,8 +5949,3 @@ void os::print_user_info(outputStream* st) { void os::print_active_locale(outputStream* st) { // not implemented yet } - -// stubbed-out trim-native support -bool os::can_trim_native_heap() { return false; } -bool os::should_trim_native_heap() { return false; } -bool os::trim_native_heap(os::size_change_t* rss_change) { return false; } diff --git a/src/hotspot/os/windows/os_windows.inline.hpp b/src/hotspot/os/windows/os_windows.inline.hpp index 276863e95b5f8..59065680c1462 100644 --- a/src/hotspot/os/windows/os_windows.inline.hpp +++ b/src/hotspot/os/windows/os_windows.inline.hpp @@ -100,6 +100,7 @@ inline void PlatformMonitor::notify_all() { // stubbed-out trim-native support inline bool os::can_trim_native_heap() { return false; } +inline bool os::should_trim_native_heap() { return false; } inline bool os::trim_native_heap(os::size_change_t* rss_change) { return false; } #endif // OS_WINDOWS_OS_WINDOWS_INLINE_HPP From a8ae8a2e89c49dd00ce0122ee46966f30ff22e22 Mon Sep 17 00:00:00 2001 From: tstuefe Date: Wed, 25 Jan 2023 10:51:26 +0100 Subject: [PATCH 09/27] make tests for ppc more lenient --- test/hotspot/jtreg/gc/TestTrimNative.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test/hotspot/jtreg/gc/TestTrimNative.java b/test/hotspot/jtreg/gc/TestTrimNative.java index 494e6d20534eb..152bb8c6ad03e 100644 --- a/test/hotspot/jtreg/gc/TestTrimNative.java +++ b/test/hotspot/jtreg/gc/TestTrimNative.java @@ -249,6 +249,7 @@ */ import jdk.internal.misc.Unsafe; +import jdk.test.lib.Platform; import jdk.test.lib.process.OutputAnalyzer; import jdk.test.lib.process.ProcessTools; @@ -351,7 +352,13 @@ private final static void parseOutputAndLookForNegativeTrim(OutputAnalyzer outpu // This is very fuzzy. We malloced X, free'd X, trimmed, measured the combined effect of all reductions. // This does not take into effect mallocs or frees that may happen concurrently. But we expect to see *some* // reduction somewhere. Test with a fudge factor. - float fudge = 0.8f; + float fudge = 0.7f; + // 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)" + From 6f1092cb43c9a5935dea69073b03cf5b68a56d26 Mon Sep 17 00:00:00 2001 From: tstuefe Date: Fri, 27 Jan 2023 07:04:13 +0100 Subject: [PATCH 10/27] revamp wip --- .../share/gc/shared/gcTrimNativeHeap.cpp | 97 +++++++++++++------ .../share/gc/shared/gcTrimNativeHeap.hpp | 15 +-- src/hotspot/share/gc/shared/gc_globals.hpp | 25 +++-- .../share/gc/shared/jvmFlagConstraintsGC.cpp | 11 +++ .../share/gc/shared/jvmFlagConstraintsGC.hpp | 3 +- 5 files changed, 99 insertions(+), 52 deletions(-) diff --git a/src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp b/src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp index 2920bcddeb264..ef97fe33de466 100644 --- a/src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp +++ b/src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp @@ -1,6 +1,6 @@ /* - * Copyright (c) 2022 SAP SE. All rights reserved. - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * 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 @@ -35,55 +35,65 @@ #include "utilities/globalDefinitions.hpp" #include "utilities/ticks.hpp" -bool GCTrimNative::_async_mode = false; -double GCTrimNative::_next_trim_not_before = 0; - -// GCTrimNative works in two modes: -// -// - async mode, where GCTrimNative runs a trimmer thread on behalf of the GC. -// The trimmer thread will be doing all the trims, both periodically and -// triggered from outside via GCTrimNative::schedule_trim(). -// -// - synchronous mode, where the GC does the trimming itself in its own thread, -// via GCTrimNative::should_trim() and GCTrimNative::execute_trim(). -// -// The mode is set as argument to GCTrimNative::initialize(). +struct trim_result class NativeTrimmer : public ConcurrentGCThread { Monitor* _lock; + + int64_t _interval_ms; volatile bool _paused; + static NativeTrimmer* _the_trimmer; -protected: + bool will_trim_periodically() const { return trim_interval() > 0; } + + // Intervals in milliseconds; + int64_t trim_interval() const { return _interval_ms; } + int64_t trim_interval_min() const { return GCTrimNativeHeapInterval * 1000; } + int64_t trim_interval_max() const { return GCTrimNativeHeapIntervalMax * 1000; } + + void set_trim_interval(uint64_t ms) { + assert(ms >= trim_interval_min() && ms < trim_interval_max(), "Oob"); + _interval_ms = ms; + } - virtual void run_service() { + void run_service() override { assert(GCTrimNativeHeap, "Sanity"); assert(os::can_trim_native_heap(), "Sanity"); log_info(gc, trim)("NativeTrimmer started."); - // Note: GCTrimNativeHeapInterval=0 -> zero wait time -> indefinite waits, disabling periodic trim - const int64_t delay_ms = GCTrimNativeHeapInterval * 1000; for (;;) { + const int64_t delay_ms = + will_trim_periodically() ? trim_interval() : 0; MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag); ml.wait(delay_ms); + if (should_terminate()) { log_info(gc, trim)("NativeTrimmer stopped."); break; } - bool paused = Atomic::load(&_paused); - if (!paused && os::should_trim_native_heap()) { + + if (!is_paused() && os::should_trim_native_heap()) { GCTrimNative::do_trim(); } } } + void stop_service() override { + wakeup(); + } + void wakeup() { MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag); ml.notify_all(); } + bool is_paused() const { + return Atomic::load(&_paused); + } + void pause() { Atomic::store(&_paused, true); log_debug(gc, trim)("NativeTrimmer paused"); @@ -94,14 +104,28 @@ class NativeTrimmer : public ConcurrentGCThread { log_debug(gc, trim)("NativeTrimmer unpaused"); } - virtual void stop_service() { - wakeup(); + void do_trim() { + Ticks start = Ticks::now(); + os::size_change_t sc; + 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: NativeTrimmer() : - _lock(new (std::nothrow) PaddedMonitor(Mutex::nosafepoint, "NativeTrimmer_lock")), + _lock(nullptr), + _interval_seconds(GCTrimNativeHeapInterval), _paused(false) {} @@ -154,7 +178,7 @@ void GCTrimNative::do_trim() { /// GCTrimNative outside facing methods -void GCTrimNative::initialize(bool async_mode) { +void GCTrimNative::initialize() { if (GCTrimNativeHeap) { @@ -164,15 +188,26 @@ void GCTrimNative::initialize(bool async_mode) { return; } - log_debug(gc, trim)("GCTrimNativeHeap enabled."); + log_info(gc, trim)("Native trim enabled."); - _async_mode = async_mode; - - // If we are to run the trimmer on behalf of the GC: - if (_async_mode) { - NativeTrimmer::start_trimmer(); + if (GCTrimNativeHeapInterval > 0) { // periodic trimming enabled + assert(GCTrimNativeHeapIntervalMax == 0 || + GCTrimNativeHeapIntervalMax > GCTrimNativeHeapInterval, "Sanity"); // see flag constraint + if (GCTrimNativeHeapIntervalMax == 0) { // default + // The default for interval upper bound: 10 * the lower bound, but at least 3 minutes. + const uint upper_bound = MAX2(GCTrimNativeHeapInterval * 10, (uint)(3 * 60)); + log_debug(gc, trim)("Setting GCTrimNativeHeapIntervalMax to %u.", upper_bound); + FLAG_SET_ERGO(GCTrimNativeHeapIntervalMax, upper_bound); + } + log_info(gc, trim)("Periodic native trim enabled (interval: %u-%u seconds).", + GCTrimNativeHeapInterval, GCTrimNativeHeapIntervalMax); + } else { + log_info(gc, trim)("Periodic native trim disabled (we trim at full gc only).", + GCTrimNativeHeapInterval, GCTrimNativeHeapIntervalMax); } + NativeTrimmer::start_trimmer(); + _next_trim_not_before = GCTrimNativeHeapInterval; } } diff --git a/src/hotspot/share/gc/shared/gcTrimNativeHeap.hpp b/src/hotspot/share/gc/shared/gcTrimNativeHeap.hpp index 7ac5a5723f713..f6d2e4e13bf72 100644 --- a/src/hotspot/share/gc/shared/gcTrimNativeHeap.hpp +++ b/src/hotspot/share/gc/shared/gcTrimNativeHeap.hpp @@ -40,24 +40,15 @@ class GCTrimNative : public AllStatic { public: - static void initialize(bool async_mode); + static void initialize(); static void cleanup(); - // Returns true if: - // - trimming is enabled and possible - // - trimming may have an actual effect (guess) - // - delay timer has expired (unless ignore_delay is true) - static bool should_trim(bool ignore_delay); - - // Execute trim-native in this thread - static void execute_trim(); - // Pause/unpause periodic trim static void pause_periodic_trim(); static void unpause_periodic_trim(); - // Schedule an explicit trim now; if periodic trims had been - // paused, they are unpaused. + // Schedule an explicit trim now. If periodic trims are enabled and have been paused, + // they are unpaused. static void schedule_trim(); }; diff --git a/src/hotspot/share/gc/shared/gc_globals.hpp b/src/hotspot/share/gc/shared/gc_globals.hpp index ec4bf0133d49a..bb49240250e51 100644 --- a/src/hotspot/share/gc/shared/gc_globals.hpp +++ b/src/hotspot/share/gc/shared/gc_globals.hpp @@ -693,15 +693,24 @@ constraint(GCCardSizeInBytesConstraintFunc,AtParse) \ \ product(bool, GCTrimNativeHeap, false, EXPERIMENTAL, \ - "GC will attempt to trim the native heap periodically and at " \ - "full GCs.") \ - \ - product(uint, GCTrimNativeHeapInterval, 60, EXPERIMENTAL, \ - "If GCTrimNativeHeap is enabled: interval time, in seconds, in " \ - "which the VM will attempt to trim the native heap. A value of " \ - "0 disables periodic trimming while leaving trimming at full gc " \ - "enabled.") \ + "GC will attempt to trim the native heap periodically and " \ + "after full GCs.") \ + \ + product(uint, GCTrimNativeHeapInterval, 30, EXPERIMENTAL, \ + "If GCTrimNativeHeap is enabled: interval, in seconds, in which " \ + "the VM will attempt to trim the native heap. This is a lower " \ + "bound; the JVM may increase the interval time up to " \ + "GCTrimNativeHeapIntervalMax." \ + "A value of 0 disables periodic trimming altogether while " \ + "leaving trimming after full gc enabled.") \ + range(0, (24 * 60 * 60)) \ + \ + product(uint, GCTrimNativeHeapIntervalMax, 0, EXPERIMENTAL, \ + "If GCTrimNativeHeap is enabled and GCTrimNativeHeapInterval is " \ + "not 0: upper bound for the interval time, in seconds, in which " \ + "the VM will attempt to trim the native heap.") \ range(0, max_juint) \ + constraint(GCTrimNativeHeapIntervalMaxFunc,AtParse) \ \ // end of GC_FLAGS diff --git a/src/hotspot/share/gc/shared/jvmFlagConstraintsGC.cpp b/src/hotspot/share/gc/shared/jvmFlagConstraintsGC.cpp index dbe4e31da8ef4..873b5e257678f 100644 --- a/src/hotspot/share/gc/shared/jvmFlagConstraintsGC.cpp +++ b/src/hotspot/share/gc/shared/jvmFlagConstraintsGC.cpp @@ -436,3 +436,14 @@ JVMFlag::Error GCCardSizeInBytesConstraintFunc(uint value, bool verbose) { } } +JVMFlag::Error GCTrimNativeHeapIntervalMaxFunc(uint value, bool verbose) { + if (GCTrimNativeHeap && GCTrimNativeHeapIntervalMax < GCTrimNativeHeapInterval) { + JVMFlag::printError(verbose, + "GCTrimNativeHeapIntervalMax ( %u ) must be " + "greater than or equal to GCTrimNativeHeapInterval ( %u )\n", + value, GCTrimNativeHeapInterval); + return JVMFlag::VIOLATES_CONSTRAINT; + } else { + return JVMFlag::SUCCESS; + } +} diff --git a/src/hotspot/share/gc/shared/jvmFlagConstraintsGC.hpp b/src/hotspot/share/gc/shared/jvmFlagConstraintsGC.hpp index da320944b0ef2..67904e278c01c 100644 --- a/src/hotspot/share/gc/shared/jvmFlagConstraintsGC.hpp +++ b/src/hotspot/share/gc/shared/jvmFlagConstraintsGC.hpp @@ -67,7 +67,8 @@ f(uintx, SurvivorRatioConstraintFunc) \ f(size_t, MetaspaceSizeConstraintFunc) \ f(size_t, MaxMetaspaceSizeConstraintFunc) \ - f(uint, GCCardSizeInBytesConstraintFunc) + f(uint, GCCardSizeInBytesConstraintFunc) \ + f(uint, GCTrimNativeHeapIntervalMaxFunc) SHARED_GC_CONSTRAINTS(DECLARE_CONSTRAINT) From 7dc36c1db5d6121b803060923e814030317bbaa4 Mon Sep 17 00:00:00 2001 From: tstuefe Date: Sat, 28 Jan 2023 07:29:51 +0100 Subject: [PATCH 11/27] wip --- src/hotspot/share/gc/g1/g1CollectedHeap.cpp | 4 +- src/hotspot/share/gc/g1/g1FullCollector.cpp | 5 - .../gc/parallel/parallelScavengeHeap.cpp | 2 +- .../share/gc/parallel/psParallelCompact.cpp | 4 +- .../share/gc/shared/gcTrimNativeHeap.cpp | 203 ++++++++---------- .../share/gc/shared/gcTrimNativeHeap.hpp | 19 +- .../share/gc/shared/genCollectedHeap.cpp | 6 +- .../gc/shenandoah/shenandoahControlThread.cpp | 10 +- .../share/gc/shenandoah/shenandoahHeap.cpp | 2 +- src/hotspot/share/gc/z/zCollectedHeap.cpp | 2 +- 10 files changed, 111 insertions(+), 146 deletions(-) diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp index 611e1f590c33d..13a98e5518635 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp @@ -1077,6 +1077,8 @@ bool G1CollectedHeap::do_full_collection(bool explicit_gc, bool do_maximal_compaction) { assert_at_safepoint_on_vm_thread(); + GCTrimNative::PauseThenTrimMark trim_pause_mark; + if (GCLocker::check_active_before_gc()) { // Full GC was not completed. return false; @@ -1751,7 +1753,7 @@ void G1CollectedHeap::safepoint_synchronize_end() { void G1CollectedHeap::post_initialize() { CollectedHeap::post_initialize(); ref_processing_init(); - GCTrimNative::initialize(true); + GCTrimNative::initialize(); } void G1CollectedHeap::ref_processing_init() { diff --git a/src/hotspot/share/gc/g1/g1FullCollector.cpp b/src/hotspot/share/gc/g1/g1FullCollector.cpp index 0a585dba8b40a..70c8ab5a14aa0 100644 --- a/src/hotspot/share/gc/g1/g1FullCollector.cpp +++ b/src/hotspot/share/gc/g1/g1FullCollector.cpp @@ -175,9 +175,6 @@ class PrepareRegionsClosure : public HeapRegionClosure { void G1FullCollector::prepare_collection() { - // Pause native trimming for the duration of the GC - GCTrimNative::pause_periodic_trim(); - _heap->policy()->record_full_collection_start(); // Verification needs the bitmap, so we should clear the bitmap only later. @@ -242,8 +239,6 @@ void G1FullCollector::complete_collection() { _heap->gc_epilogue(true); _heap->verify_after_full_collection(); - - GCTrimNative::schedule_trim(); } void G1FullCollector::before_marking_update_attribute_table(HeapRegion* hr) { diff --git a/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp b/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp index 8d6e6fb0b52c8..e7d07d7dc3276 100644 --- a/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp +++ b/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp @@ -192,7 +192,7 @@ void ParallelScavengeHeap::post_initialize() { ScavengableNMethods::initialize(&_is_scavengable); - GCTrimNative::initialize(true); + GCTrimNative::initialize(); } void ParallelScavengeHeap::update_counters() { diff --git a/src/hotspot/share/gc/parallel/psParallelCompact.cpp b/src/hotspot/share/gc/parallel/psParallelCompact.cpp index 3d42510fbe164..4742761d8eb27 100644 --- a/src/hotspot/share/gc/parallel/psParallelCompact.cpp +++ b/src/hotspot/share/gc/parallel/psParallelCompact.cpp @@ -1712,7 +1712,7 @@ bool PSParallelCompact::invoke_no_policy(bool maximum_heap_compaction) { } // Pause native trimming for the duration of the GC - GCTrimNative::pause_periodic_trim(); + GCTrimNative::PauseThenTrimMark trim_native_pause; ParallelScavengeHeap* heap = ParallelScavengeHeap::heap(); @@ -1871,8 +1871,6 @@ bool PSParallelCompact::invoke_no_policy(bool maximum_heap_compaction) { // Resize the metaspace capacity after a collection MetaspaceGC::compute_new_size(); - GCTrimNative::schedule_trim(); - if (log_is_enabled(Debug, gc, heap, exit)) { accumulated_time()->stop(); } diff --git a/src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp b/src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp index ef97fe33de466..2c5f8f261e37c 100644 --- a/src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp +++ b/src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp @@ -27,6 +27,7 @@ #include "gc/shared/gc_globals.hpp" #include "gc/shared/gcTrimNativeHeap.hpp" #include "logging/log.hpp" +#include "runtime/atomic.hpp" #include "runtime/globals_extension.hpp" #include "runtime/mutex.hpp" #include "runtime/mutexLocker.hpp" @@ -35,28 +36,26 @@ #include "utilities/globalDefinitions.hpp" #include "utilities/ticks.hpp" -struct trim_result - class NativeTrimmer : public ConcurrentGCThread { Monitor* _lock; - int64_t _interval_ms; - volatile bool _paused; - - static NativeTrimmer* _the_trimmer; + // Time of next trim; INT64_MAX: periodic trim disabled + int64_t _next_trim_time; + int64_t _next_trim_time_saved; + static const int64_t never = INT64_MAX; - bool will_trim_periodically() const { return trim_interval() > 0; } + int64_t _interval_ms; // Intervals in milliseconds; - int64_t trim_interval() const { return _interval_ms; } + bool periodic_trim_enabled() const { return GCTrimNativeHeapInterval != 0; } int64_t trim_interval_min() const { return GCTrimNativeHeapInterval * 1000; } int64_t trim_interval_max() const { return GCTrimNativeHeapIntervalMax * 1000; } - void set_trim_interval(uint64_t ms) { - assert(ms >= trim_interval_min() && ms < trim_interval_max(), "Oob"); - _interval_ms = ms; - } + int64_t trim_interval() const { return _interval_ms; } + int64_t next_trim_time() const { return _next_trim_time; } + + static int64_t now() { return os::javaTimeMillis(); } void run_service() override { assert(GCTrimNativeHeap, "Sanity"); @@ -65,46 +64,52 @@ class NativeTrimmer : public ConcurrentGCThread { log_info(gc, trim)("NativeTrimmer started."); for (;;) { - const int64_t delay_ms = - will_trim_periodically() ? trim_interval() : 0; - MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag); - ml.wait(delay_ms); - if (should_terminate()) { - log_info(gc, trim)("NativeTrimmer stopped."); - break; + int64_t ntt = 0; + int64_t tnow = 0; + + { + MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag); + do { + ntt = next_trim_time(); + tnow = now(); + if (ntt > tnow) { + const int64_t sleep_ms = (ntt == never) ? 0 : ntt - tnow; + ml.wait(sleep_ms); + ntt = next_trim_time(); + tnow = now(); + } + if (should_terminate()) { + log_info(gc, trim)("NativeTrimmer stopped."); + return; + } + } while (ntt > tnow); } - if (!is_paused() && os::should_trim_native_heap()) { - GCTrimNative::do_trim(); + do_trim(); // may take some time... + + // Update next trim time, but give outside setters preference. + { + MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag); + const int64_t ntt2 = next_trim_time(); + if (ntt2 == ntt) { + _next_trim_time = (periodic_trim_enabled() ? + (now() + trim_interval()) : never); + } } } } void stop_service() override { - wakeup(); - } - - void wakeup() { MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag); ml.notify_all(); } - bool is_paused() const { - return Atomic::load(&_paused); - } - - void pause() { - Atomic::store(&_paused, true); - log_debug(gc, trim)("NativeTrimmer paused"); - } - - void unpause() { - Atomic::store(&_paused, false); - log_debug(gc, trim)("NativeTrimmer unpaused"); - } - void do_trim() { + if (!os::should_trim_native_heap()) { + log_trace(gc, trim)("Trim native heap: not necessary"); + return; + } Ticks start = Ticks::now(); os::size_change_t sc; if (os::trim_native_heap(&sc)) { @@ -124,57 +129,52 @@ class NativeTrimmer : public ConcurrentGCThread { public: NativeTrimmer() : - _lock(nullptr), - _interval_seconds(GCTrimNativeHeapInterval), - _paused(false) - {} - - static bool is_enabled() { - return _the_trimmer != nullptr; - } - - static void start_trimmer() { - _the_trimmer = new NativeTrimmer(); - _the_trimmer->create_and_start(NormPriority); + _lock(new (std::nothrow) PaddedMonitor(Mutex::nosafepoint, "NativeTrimmer_lock")), + _next_trim_time((GCTrimNativeHeapInterval == 0) ? never : GCTrimNativeHeapInterval), + _next_trim_time_saved(0), + _interval_ms(GCTrimNativeHeapInterval) + { + set_name("Native Heap Trimmer"); + create_and_start(); } - static void stop_trimmer() { - _the_trimmer->stop(); - } - - static void pause_periodic_trim() { - _the_trimmer->pause(); + void pause() { + if (!periodic_trim_enabled()) { + return; + } + { + MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag); + _next_trim_time_saved = _next_trim_time; + _next_trim_time = never; + ml.notify_all(); + } + log_debug(gc, trim)("NativeTrimmer paused"); } - static void unpause_periodic_trim() { - _the_trimmer->unpause(); + void unpause() { + if (!periodic_trim_enabled()) { + return; + } + { + MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag); + _next_trim_time = _next_trim_time_saved; + ml.notify_all(); + } + log_debug(gc, trim)("NativeTrimmer paused"); } - static void schedule_trim_now() { - _the_trimmer->unpause(); - _the_trimmer->wakeup(); + void schedule_trim() { + { + MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag); + _next_trim_time = 0; + ml.notify_all(); + } + log_debug(gc, trim)("NativeTrimmer immediate trim"); } }; // NativeTrimmer -NativeTrimmer* NativeTrimmer::_the_trimmer = nullptr; - -void GCTrimNative::do_trim() { - Ticks start = Ticks::now(); - os::size_change_t sc; - 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)"); - } - } -} +static NativeTrimmer* g_trimmer_thread = nullptr; /// GCTrimNative outside facing methods @@ -202,56 +202,33 @@ void GCTrimNative::initialize() { log_info(gc, trim)("Periodic native trim enabled (interval: %u-%u seconds).", GCTrimNativeHeapInterval, GCTrimNativeHeapIntervalMax); } else { - log_info(gc, trim)("Periodic native trim disabled (we trim at full gc only).", - GCTrimNativeHeapInterval, GCTrimNativeHeapIntervalMax); + log_info(gc, trim)("Periodic native trim disabled (we trim at full gc only)."); } - NativeTrimmer::start_trimmer(); - - _next_trim_not_before = GCTrimNativeHeapInterval; + g_trimmer_thread = new NativeTrimmer(); } } void GCTrimNative::cleanup() { - if (GCTrimNativeHeap) { - if (_async_mode) { - NativeTrimmer::stop_trimmer(); - } - } -} - -bool GCTrimNative::should_trim(bool ignore_delay) { - return - GCTrimNativeHeap && os::can_trim_native_heap() && - (ignore_delay || (GCTrimNativeHeapInterval > 0 && os::elapsedTime() > _next_trim_not_before)) && - os::should_trim_native_heap(); -} - -void GCTrimNative::execute_trim() { - if (GCTrimNativeHeap) { - assert(!_async_mode, "Only call for non-async mode"); - do_trim(); - _next_trim_not_before = os::elapsedTime() + GCTrimNativeHeapInterval; + if (g_trimmer_thread != nullptr) { + g_trimmer_thread->stop(); } } void GCTrimNative::pause_periodic_trim() { - if (GCTrimNativeHeap) { - assert(_async_mode, "Only call for async mode"); - NativeTrimmer::pause_periodic_trim(); + if (g_trimmer_thread != nullptr) { + g_trimmer_thread->pause(); } } void GCTrimNative::unpause_periodic_trim() { - if (GCTrimNativeHeap) { - assert(_async_mode, "Only call for async mode"); - NativeTrimmer::unpause_periodic_trim(); + if (g_trimmer_thread != nullptr) { + g_trimmer_thread->unpause(); } } void GCTrimNative::schedule_trim() { - if (GCTrimNativeHeap) { - assert(_async_mode, "Only call for async mode"); - NativeTrimmer::schedule_trim_now(); + if (g_trimmer_thread != nullptr) { + g_trimmer_thread->schedule_trim(); } } diff --git a/src/hotspot/share/gc/shared/gcTrimNativeHeap.hpp b/src/hotspot/share/gc/shared/gcTrimNativeHeap.hpp index f6d2e4e13bf72..4f899ee51e019 100644 --- a/src/hotspot/share/gc/shared/gcTrimNativeHeap.hpp +++ b/src/hotspot/share/gc/shared/gcTrimNativeHeap.hpp @@ -28,16 +28,7 @@ #include "memory/allStatic.hpp" -class NativeTrimmer; - class GCTrimNative : public AllStatic { - friend class NativeTrimmer; - - static bool _async_mode; - static double _next_trim_not_before; - - static void do_trim(); - public: static void initialize(); @@ -51,6 +42,16 @@ class GCTrimNative : public AllStatic { // they are unpaused. static void schedule_trim(); + struct PauseMark { + PauseMark() { GCTrimNative::pause_periodic_trim(); } + ~PauseMark() { GCTrimNative::unpause_periodic_trim(); } + }; + + struct PauseThenTrimMark { + PauseThenTrimMark() { GCTrimNative::pause_periodic_trim(); } + ~PauseThenTrimMark() { GCTrimNative::schedule_trim(); } + }; + }; #endif // SHARE_GC_SHARED_GCTRIMNATIVEHEAP_HPP diff --git a/src/hotspot/share/gc/shared/genCollectedHeap.cpp b/src/hotspot/share/gc/shared/genCollectedHeap.cpp index 10ace25bf6748..8536a53fefeb4 100644 --- a/src/hotspot/share/gc/shared/genCollectedHeap.cpp +++ b/src/hotspot/share/gc/shared/genCollectedHeap.cpp @@ -203,7 +203,7 @@ void GenCollectedHeap::post_initialize() { ScavengableNMethods::initialize(&_is_scavengable); - GCTrimNative::initialize(false); // false since we will call trim inside the collecting thread + GCTrimNative::initialize(); } void GenCollectedHeap::ref_processing_init() { @@ -633,9 +633,7 @@ void GenCollectedHeap::do_collection(bool full, update_full_collections_completed(); // Trim the native heap, without a delay since this is a full gc - if (GCTrimNative::should_trim(true)) { - GCTrimNative::execute_trim(); - } + GCTrimNative::schedule_trim(); print_heap_change(pre_gc_values); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp index 93900243c7476..80bbbd86fe13c 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp @@ -314,14 +314,6 @@ void ShenandoahControlThread::run_service() { last_shrink_time = current; } - if (GCTrimNative::should_trim(explicit_gc_requested)) { - static const char *msg = "Concurrent trim-native"; - ShenandoahConcurrentPhase gc_phase(msg, ShenandoahPhaseTimings::conc_trim, false); - EventMark em("%s", msg); - GCTrimNative::execute_trim(); - heap->phase_timings()->flush_cycle_to_global(); - } - // Wait before performing the next action. If allocation happened during this wait, // we exit sooner, to let heuristics re-evaluate new conditions. If we are at idle, // back off exponentially. @@ -433,6 +425,7 @@ void ShenandoahControlThread::stop_service() { } void ShenandoahControlThread::service_stw_full_cycle(GCCause::Cause cause) { + GCTrimNative::PauseThenTrimMark trim_pause_mark; GCIdMark gc_id_mark; ShenandoahGCSession session(cause); @@ -447,6 +440,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"); + GCTrimNative::PauseMark trim_pause_mark; GCIdMark gc_id_mark; ShenandoahGCSession session(cause); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp index e088525f3e1fd..c4609f1e06b8a 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp @@ -627,7 +627,7 @@ void ShenandoahHeap::post_initialize() { JFR_ONLY(ShenandoahJFRSupport::register_jfr_type_serializers()); - GCTrimNative::initialize(false); // false since this is taken care of inside the service thread + GCTrimNative::initialize(); } size_t ShenandoahHeap::used() const { diff --git a/src/hotspot/share/gc/z/zCollectedHeap.cpp b/src/hotspot/share/gc/z/zCollectedHeap.cpp index 13820b3c0776c..9271fd9e7a349 100644 --- a/src/hotspot/share/gc/z/zCollectedHeap.cpp +++ b/src/hotspot/share/gc/z/zCollectedHeap.cpp @@ -75,7 +75,7 @@ jint ZCollectedHeap::initialize() { Universe::calculate_verify_data((HeapWord*)0, (HeapWord*)UINTPTR_MAX); - GCTrimNative::initialize(true); + GCTrimNative::initialize(); return JNI_OK; } From 943ab23e6bb1cf3e1676c0daa48755fe5798c3cb Mon Sep 17 00:00:00 2001 From: tstuefe Date: Mon, 30 Jan 2023 15:21:37 +0100 Subject: [PATCH 12/27] wip --- .../share/gc/shared/gcTrimNativeHeap.cpp | 102 ++++++++++++++---- 1 file changed, 79 insertions(+), 23 deletions(-) diff --git a/src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp b/src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp index 2c5f8f261e37c..d28125ef2ee51 100644 --- a/src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp +++ b/src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp @@ -36,55 +36,111 @@ #include "utilities/globalDefinitions.hpp" #include "utilities/ticks.hpp" +// A list of n subsequent trim results +class TrimData { + + const size_t _max = 5; + os::size_change_t _results[_max]; + int _num; + +public: + + TrimData() : _num(0) {} + + bool is_full() const { _num == _max; } + + void reset() { _num = 0; } + + bool add(const os::size_change_t* sc) { + assert(!is_full(), "full"); + _results[_num] = *sc; + _num++; + return is_full(); + } + + // Small heuristic to evaluate the pointlessness of trimming: + // If we have enough historical data, would it make sense to slow down + // periodic trimming? + // If we keep reclaiming a lot of memory on each trim, but RSS bounces back + // each time, the answer is yes. + bool recommend_pause() { + if (_num < _max) { + return false; // not enough data + } + int num_significant_trims = 0; + int num_significant_trims_bouncebacks = 0; + for (int i = 0; i < _max; i++) { + const ssize_t gain_size = (ssize_t)_results[i].before - (ssize_t)_results[i].after; + if (gain_size > MIN2(32 * M, _results[i].after / 10)) { + // This trim showed a significant gain. But did we bounce back? + num_significant_trims++; + if (i < _max - 1) { + const ssize_t bounce_back_size = (ssize_t)_results[i].after - _results[i].before; + if (bounce_back_size >= gain_size) { + num_significant_trims_bouncebacks++; + } + } + } + } + // We recommend slowing down trimming if each trim showed significant gains but + // RSS bounced back right afterwards. Note that since we compare RSS, all of this is + // fuzzy and can yield false positives and negatives. + return (num_significant_trims == _max && + num_significant_trims_bouncebacks == (num_significant_trims - 1)); + } +}; + class NativeTrimmer : public ConcurrentGCThread { Monitor* _lock; - // Time of next trim; INT64_MAX: periodic trim disabled - int64_t _next_trim_time; - int64_t _next_trim_time_saved; - static const int64_t never = INT64_MAX; + // Periodic trimming state + const int64_t _interval_ms; + const int64_t _max_interval_ms; + const bool _do_periodic_trim; - int64_t _interval_ms; + int64_t _next_trim_time; + bool _paused; - // Intervals in milliseconds; - bool periodic_trim_enabled() const { return GCTrimNativeHeapInterval != 0; } - int64_t trim_interval_min() const { return GCTrimNativeHeapInterval * 1000; } - int64_t trim_interval_max() const { return GCTrimNativeHeapIntervalMax * 1000; } + // Auto-step-down + int64_t _last_long_pause; + TrimData _trim_history; - int64_t trim_interval() const { return _interval_ms; } - int64_t next_trim_time() const { return _next_trim_time; } + static const int64_t never = INT64_MAX; static int64_t now() { return os::javaTimeMillis(); } void run_service() override { + assert(GCTrimNativeHeap, "Sanity"); assert(os::can_trim_native_heap(), "Sanity"); log_info(gc, trim)("NativeTrimmer started."); - for (;;) { - - int64_t ntt = 0; - int64_t tnow = 0; + int64_t ntt = 0; + for (;;) { { MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag); do { - ntt = next_trim_time(); - tnow = now(); - if (ntt > tnow) { - const int64_t sleep_ms = (ntt == never) ? 0 : ntt - tnow; + int64_t tnow = now(); + ntt = _next_trim_time; + wait_some = ntt > tnow || _paused; + if (wait_some) { + const int64_t sleep_ms = (_paused || ntt == never) ? + 0 /* infinite */ : + ntt - tnow; ml.wait(sleep_ms); - ntt = next_trim_time(); - tnow = now(); } if (should_terminate()) { log_info(gc, trim)("NativeTrimmer stopped."); return; } - } while (ntt > tnow); - } + ntt = _next_trim_time; + tnow = now(); + wait_some = ntt > tnow || _paused; + } while (wait_some); + } // end mutex scope do_trim(); // may take some time... From 486cd1b7339c21d2084ae20a80f658555f0d0977 Mon Sep 17 00:00:00 2001 From: tstuefe Date: Tue, 31 Jan 2023 12:49:40 +0100 Subject: [PATCH 13/27] wip --- .../share/gc/shared/gcTrimNativeHeap.cpp | 197 +++++++++++------- .../share/gc/shared/jvmFlagConstraintsGC.cpp | 2 +- .../share/runtime/flags/jvmFlagAccess.cpp | 1 + 3 files changed, 127 insertions(+), 73 deletions(-) diff --git a/src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp b/src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp index d28125ef2ee51..b4ed3b4db7ae7 100644 --- a/src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp +++ b/src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp @@ -36,57 +36,75 @@ #include "utilities/globalDefinitions.hpp" #include "utilities/ticks.hpp" -// A list of n subsequent trim results -class TrimData { +#define PING log_debug(gc, trim)("NativeTrimmer: %s %d", __FILE__, __LINE__); - const size_t _max = 5; - os::size_change_t _results[_max]; +// A FIFO of the last n trim results +class TrimHistory { + + static const int _max = 8; + + // Size changes for the last n trims, young to old + os::size_change_t _histo[_max]; int _num; -public: + void push_elements() { + for (int i = _max; i > 0; i--) { + _histo[i] = _histo[i - 1]; + } + } - TrimData() : _num(0) {} +public: - bool is_full() const { _num == _max; } + TrimHistory() : _num(0) {} void reset() { _num = 0; } - bool add(const os::size_change_t* sc) { - assert(!is_full(), "full"); - _results[_num] = *sc; - _num++; - return is_full(); + void add(const os::size_change_t* sc) { + assert(sc->before <= INT64_MAX && + sc->after <= INT64_MAX, "invalid sample"); + push_elements(); + _histo[0] = *sc; + if (_num < _max) { + _num++; + } } - // Small heuristic to evaluate the pointlessness of trimming: - // If we have enough historical data, would it make sense to slow down - // periodic trimming? - // If we keep reclaiming a lot of memory on each trim, but RSS bounces back - // each time, the answer is yes. + // Small heuristic to check if periodic trimming should continue, or be stepped + // down. + // We want to prevent situations where we keep reclaiming lots of memory yet the malloc + // load keeps bouncing back. That would be an indication for malloc spikes that are + // frequent enough to make trimming questionable. + // Note that we cannot measure malloc load directly, we only can measure RSS. So this can get + // quite fuzzy. We accept that. + // Note also that in most situations trimming is benign or at least harmless: + // - Rare malloc spikes are an ideal trimming scenario - we gain memory and get to keep it. + // - If we have no gains in the first place, trying to trim is benign since its cheap. + // - a constant malloc churn typically wont give much room for trimming since it reuses + // memory almost immediately, which again makes trimming cheap, so we continue to do it + // at the set pace. bool recommend_pause() { if (_num < _max) { return false; // not enough data } int num_significant_trims = 0; - int num_significant_trims_bouncebacks = 0; - for (int i = 0; i < _max; i++) { - const ssize_t gain_size = (ssize_t)_results[i].before - (ssize_t)_results[i].after; - if (gain_size > MIN2(32 * M, _results[i].after / 10)) { - // This trim showed a significant gain. But did we bounce back? + int num_bouncebacks = 0; + for (int i = _max - 1; i > 0; i--) { // oldest to youngest + const ssize_t sz_before = checked_cast(_histo[i].before); + const ssize_t sz_after = checked_cast(_histo[i].after); + const ssize_t gains = sz_before - sz_after; + if (gains > (ssize_t)MIN2(32 * M, _histo[i].before / 10)) { // considered significant num_significant_trims++; - if (i < _max - 1) { - const ssize_t bounce_back_size = (ssize_t)_results[i].after - _results[i].before; - if (bounce_back_size >= gain_size) { - num_significant_trims_bouncebacks++; - } + const ssize_t sz_before_next = checked_cast(_histo[i - 1].before); + const ssize_t bounceback = sz_before_next - sz_after; + // We consider it to have bounced back if RSS for the followup sample returns to + // within at least -2% of post-trim-RSS. + if (bounceback >= (gains - (gains / 50))) { + num_bouncebacks++; } } } - // We recommend slowing down trimming if each trim showed significant gains but - // RSS bounced back right afterwards. Note that since we compare RSS, all of this is - // fuzzy and can yield false positives and negatives. - return (num_significant_trims == _max && - num_significant_trims_bouncebacks == (num_significant_trims - 1)); + return (num_significant_trims >= (_max / 2) && + num_significant_trims == num_bouncebacks); } }; @@ -97,14 +115,12 @@ class NativeTrimmer : public ConcurrentGCThread { // Periodic trimming state const int64_t _interval_ms; const int64_t _max_interval_ms; - const bool _do_periodic_trim; + const bool _periodic_trim_enabled; int64_t _next_trim_time; - bool _paused; + int64_t _next_trim_time_saved; // for pause - // Auto-step-down - int64_t _last_long_pause; - TrimData _trim_history; + TrimHistory _trim_history; static const int64_t never = INT64_MAX; @@ -118,42 +134,74 @@ class NativeTrimmer : public ConcurrentGCThread { log_info(gc, trim)("NativeTrimmer started."); int64_t ntt = 0; + int64_t tnow = 0; for (;;) { + // 1 - Wait for the next trim point { MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag); do { - int64_t tnow = now(); + tnow = now(); ntt = _next_trim_time; - wait_some = ntt > tnow || _paused; - if (wait_some) { - const int64_t sleep_ms = (_paused || ntt == never) ? - 0 /* infinite */ : - ntt - tnow; - ml.wait(sleep_ms); + if (ntt == never) { + ml.wait(0); // infinite sleep + } else if (ntt > tnow) { + ml.wait(ntt - tnow); // sleep till next point } if (should_terminate()) { + PING log_info(gc, trim)("NativeTrimmer stopped."); return; } - ntt = _next_trim_time; tnow = now(); - wait_some = ntt > tnow || _paused; - } while (wait_some); - } // end mutex scope + ntt = _next_trim_time; + } while (ntt > tnow); + } + + PING - do_trim(); // may take some time... + // 2 - Trim + os::size_change_t sc; + bool have_trim_results = execute_trim_and_log(&sc); - // Update next trim time, but give outside setters preference. + // 3 - Update next trim time + // Note: outside setters have preference - if we paused/unpaused/scheduled trim concurrently while the last trim + // was in progress, we do that. Note that if this causes two back-to-back trims, that is harmless since usually + // the second trim is cheap. { MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag); - const int64_t ntt2 = next_trim_time(); - if (ntt2 == ntt) { - _next_trim_time = (periodic_trim_enabled() ? - (now() + trim_interval()) : never); + tnow = now(); + int64_t ntt2 = _next_trim_time; + if (ntt2 == ntt) { // not changed concurrently? + if (_periodic_trim_enabled) { + // Feed trim data into history; then, if it recommends stepping down the trim interval, + // do that. + bool long_pause = false; + if (have_trim_results) { + _trim_history.add(&sc); + long_pause = _trim_history.recommend_pause(); + } else { + // Sample was invalid, we lost it and hence history is torn: reset history and start from + // scratch next time. + _trim_history.reset(); + } + + if (long_pause) { + log_debug(gc, trim)("NativeTrimmer: long pause (" INT64_FORMAT " ms)", _max_interval_ms); + _trim_history.reset(); + _next_trim_time = tnow + _max_interval_ms; + } else { + _next_trim_time = tnow + _interval_ms; + } + + } else { + // periodic trim disabled + _next_trim_time = never; + } } - } + } // Mutex scope } + } void stop_service() override { @@ -161,54 +209,62 @@ class NativeTrimmer : public ConcurrentGCThread { ml.notify_all(); } - void do_trim() { + // Execute the native trim, log results. + // Return true if trim succeeded *and* we have valid size change data. + bool execute_trim_and_log(os::size_change_t* sc) { + assert(os::can_trim_native_heap(), "Unexpected"); if (!os::should_trim_native_heap()) { log_trace(gc, trim)("Trim native heap: not necessary"); - return; + return false; } Ticks start = Ticks::now(); - os::size_change_t sc; - if (os::trim_native_heap(&sc)) { + 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 ? '-' : '+'; + 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), + PROPERFMTARGS(sc->before), PROPERFMTARGS(sc->after), sign, PROPERFMTARGS(delta), trim_time.seconds() * 1000); + return true; } else { log_info(gc, trim)("Trim native heap (no details)"); } } + return false; } public: NativeTrimmer() : _lock(new (std::nothrow) PaddedMonitor(Mutex::nosafepoint, "NativeTrimmer_lock")), - _next_trim_time((GCTrimNativeHeapInterval == 0) ? never : GCTrimNativeHeapInterval), - _next_trim_time_saved(0), - _interval_ms(GCTrimNativeHeapInterval) + _interval_ms(GCTrimNativeHeapInterval * 1000), + _max_interval_ms(GCTrimNativeHeapIntervalMax * 1000), + _periodic_trim_enabled(GCTrimNativeHeapInterval > 0), + _next_trim_time(0), + _next_trim_time_saved(0) { set_name("Native Heap Trimmer"); + _next_trim_time = _periodic_trim_enabled ? (now() + _interval_ms) : never; create_and_start(); } void pause() { - if (!periodic_trim_enabled()) { + if (!_periodic_trim_enabled) { return; } { MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag); _next_trim_time_saved = _next_trim_time; _next_trim_time = never; + _trim_history.reset(); ml.notify_all(); } log_debug(gc, trim)("NativeTrimmer paused"); } void unpause() { - if (!periodic_trim_enabled()) { + if (!_periodic_trim_enabled) { return; } { @@ -250,10 +306,7 @@ void GCTrimNative::initialize() { assert(GCTrimNativeHeapIntervalMax == 0 || GCTrimNativeHeapIntervalMax > GCTrimNativeHeapInterval, "Sanity"); // see flag constraint if (GCTrimNativeHeapIntervalMax == 0) { // default - // The default for interval upper bound: 10 * the lower bound, but at least 3 minutes. - const uint upper_bound = MAX2(GCTrimNativeHeapInterval * 10, (uint)(3 * 60)); - log_debug(gc, trim)("Setting GCTrimNativeHeapIntervalMax to %u.", upper_bound); - FLAG_SET_ERGO(GCTrimNativeHeapIntervalMax, upper_bound); + FLAG_SET_ERGO(GCTrimNativeHeapIntervalMax, GCTrimNativeHeapInterval * 4); } log_info(gc, trim)("Periodic native trim enabled (interval: %u-%u seconds).", GCTrimNativeHeapInterval, GCTrimNativeHeapIntervalMax); diff --git a/src/hotspot/share/gc/shared/jvmFlagConstraintsGC.cpp b/src/hotspot/share/gc/shared/jvmFlagConstraintsGC.cpp index 873b5e257678f..98cc3af4f571c 100644 --- a/src/hotspot/share/gc/shared/jvmFlagConstraintsGC.cpp +++ b/src/hotspot/share/gc/shared/jvmFlagConstraintsGC.cpp @@ -437,7 +437,7 @@ JVMFlag::Error GCCardSizeInBytesConstraintFunc(uint value, bool verbose) { } JVMFlag::Error GCTrimNativeHeapIntervalMaxFunc(uint value, bool verbose) { - if (GCTrimNativeHeap && GCTrimNativeHeapIntervalMax < GCTrimNativeHeapInterval) { + if (GCTrimNativeHeap && value < GCTrimNativeHeapInterval) { JVMFlag::printError(verbose, "GCTrimNativeHeapIntervalMax ( %u ) must be " "greater than or equal to GCTrimNativeHeapInterval ( %u )\n", diff --git a/src/hotspot/share/runtime/flags/jvmFlagAccess.cpp b/src/hotspot/share/runtime/flags/jvmFlagAccess.cpp index 3ecaa70a95d80..1fefc5146ce2b 100644 --- a/src/hotspot/share/runtime/flags/jvmFlagAccess.cpp +++ b/src/hotspot/share/runtime/flags/jvmFlagAccess.cpp @@ -64,6 +64,7 @@ class TypedFlagAccessImpl : public FlagAccessImpl { T value = *((T*)value_addr); const JVMTypedFlagLimit* constraint = (const JVMTypedFlagLimit*)JVMFlagLimit::get_constraint(flag); if (constraint != nullptr && constraint->phase() <= static_cast(JVMFlagLimit::validating_phase())) { + verbose = verbose || (origin == JVMFlagOrigin::ERGONOMIC); JVMFlag::Error err = typed_check_constraint(constraint->constraint_func(), value, verbose); if (err != JVMFlag::SUCCESS) { return err; From 599573d97d117fcff9ed60b21f765873d3bbf544 Mon Sep 17 00:00:00 2001 From: tstuefe Date: Wed, 1 Feb 2023 11:18:31 +0100 Subject: [PATCH 14/27] wip --- .../share/gc/shared/gcTrimNativeHeap.cpp | 108 ++++++++++++------ .../share/gc/shared/genCollectedHeap.cpp | 5 +- src/hotspot/share/gc/z/zDriver.cpp | 20 +--- src/hotspot/share/gc/z/zDriver.hpp | 3 - .../share/runtime/flags/jvmFlagAccess.cpp | 1 - test/hotspot/jtreg/gc/TestTrimNative.java | 47 ++++---- 6 files changed, 100 insertions(+), 84 deletions(-) diff --git a/src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp b/src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp index b4ed3b4db7ae7..1654d13fd11ef 100644 --- a/src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp +++ b/src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp @@ -36,19 +36,44 @@ #include "utilities/globalDefinitions.hpp" #include "utilities/ticks.hpp" -#define PING log_debug(gc, trim)("NativeTrimmer: %s %d", __FILE__, __LINE__); +// A class holding trim results for a single trim operation. +class TrimResult { + + // time (ms) trim happened (javaTimeMillis) + const uint64_t _time; + // time (ms) trim itself took. + const uint64_t _duration; + // memory relief + const os::size_change_t _size_change; + +public: + TrimResult(uint64_t t, uint64_t d, os::size_change_t size_change) : + _time(t), _duration(d), _size_change(size_change) {} + TrimResult(const TrimResult& other) : + _time(other._time), _duration(other._duration), _size_change(other._size_change) {} + uint64_t time() const { return _time; } + uint64_t duration() const { return _duration; } + const os::size_change_t& size_change() const { return _size_change; } + + // Returns size reduction; positive if memory was reduced + ssize_t size_reduction() const { + return checked_cast(_size_change.before) - + checked_cast(_size_change.after); + } + +}; // A FIFO of the last n trim results class TrimHistory { - static const int _max = 8; + static const int _max = 4; // Size changes for the last n trims, young to old - os::size_change_t _histo[_max]; + TrimResult _histo[_max]; int _num; void push_elements() { - for (int i = _max; i > 0; i--) { + for (int i = _max - 1; i > 0; i--) { _histo[i] = _histo[i - 1]; } } @@ -59,37 +84,49 @@ class TrimHistory { void reset() { _num = 0; } - void add(const os::size_change_t* sc) { - assert(sc->before <= INT64_MAX && - sc->after <= INT64_MAX, "invalid sample"); + void add(const TrimResult& result) { push_elements(); - _histo[0] = *sc; + _histo[0] = result; if (_num < _max) { _num++; } } - // Small heuristic to check if periodic trimming should continue, or be stepped - // down. - // We want to prevent situations where we keep reclaiming lots of memory yet the malloc - // load keeps bouncing back. That would be an indication for malloc spikes that are - // frequent enough to make trimming questionable. - // Note that we cannot measure malloc load directly, we only can measure RSS. So this can get - // quite fuzzy. We accept that. - // Note also that in most situations trimming is benign or at least harmless: - // - Rare malloc spikes are an ideal trimming scenario - we gain memory and get to keep it. - // - If we have no gains in the first place, trying to trim is benign since its cheap. - // - a constant malloc churn typically wont give much room for trimming since it reuses - // memory almost immediately, which again makes trimming cheap, so we continue to do it - // at the set pace. + // Small heuristic to check if periodic trimming has been fruitful so far. + // If this heuristic finds trimming to be harmful, we will inject one longer + // trim interval (standard interval * GCTrimNativeStepDownFactor). + // + // Trimming costs are the trim itself plus the re-aquisition costs of memory should the + // released memory be malloced again. Trimming gains are the memory reduction over time. + // Lasting gains are good; gains that don't last are not. + // + // There are roughly three usage pattern: + // - rare malloc spikes interspersed with long idle periods. Trimming is beneficial + // since the relieved memory pressure holds for a long time. + // - a constant low-intensity malloc drone. Trimming does not help much here but its + // harmless too since trimming is cheap if it does not recover much. + // - frequent malloc spikes with short idle periods; trimmed memory will be re-aquired + // after only a short relief; here, trimming could be harmful since we pay a lot for + // not much relief. We want to alleviate these scenarios. + // + // Putting numbers on these things is difficult though. We cannot observe malloc + // load directly, only RSS. For every trim we know the RSS reduction (from, to). So + // for subsequent trims we also can glean from (.from) whether RSS bounced + // back. But that is quite vague since RSS may have been influenced by a ton of other + // developments, especially for longer trim intervals. + // + // Therefore this heuristic may produce false positives and negatives. We try to err on + // the side of too much trimming here and to identify only situations that are clearly + // harmful. Note that the GCTrimNativeStepDownFactor (4) is gentle enough for wrong + // heuristic results not to be too harmful. bool recommend_pause() { - if (_num < _max) { - return false; // not enough data + if (_num < _max / 2) { + return false; // not enough data; } int num_significant_trims = 0; int num_bouncebacks = 0; - for (int i = _max - 1; i > 0; i--) { // oldest to youngest - const ssize_t sz_before = checked_cast(_histo[i].before); + for (int i = _num - 1; i > 0; i--) { // oldest to youngest + const ssize_t sz_before = checked_cast(_histo[i].size_change().before); const ssize_t sz_after = checked_cast(_histo[i].after); const ssize_t gains = sz_before - sz_after; if (gains > (ssize_t)MIN2(32 * M, _histo[i].before / 10)) { // considered significant @@ -103,6 +140,8 @@ class TrimHistory { } } } + log_trace(gc, trim)("Last %d trims yielded significant gains; %d showed bounceback.", + num_significant_trims, num_bouncebacks); return (num_significant_trims >= (_max / 2) && num_significant_trims == num_bouncebacks); } @@ -131,7 +170,7 @@ class NativeTrimmer : public ConcurrentGCThread { assert(GCTrimNativeHeap, "Sanity"); assert(os::can_trim_native_heap(), "Sanity"); - log_info(gc, trim)("NativeTrimmer started."); + log_info(gc, trim)("NativeTrimmer start."); int64_t ntt = 0; int64_t tnow = 0; @@ -149,8 +188,7 @@ class NativeTrimmer : public ConcurrentGCThread { ml.wait(ntt - tnow); // sleep till next point } if (should_terminate()) { - PING - log_info(gc, trim)("NativeTrimmer stopped."); + log_info(gc, trim)("NativeTrimmer stop."); return; } tnow = now(); @@ -158,8 +196,6 @@ class NativeTrimmer : public ConcurrentGCThread { } while (ntt > tnow); } - PING - // 2 - Trim os::size_change_t sc; bool have_trim_results = execute_trim_and_log(&sc); @@ -260,7 +296,7 @@ class NativeTrimmer : public ConcurrentGCThread { _trim_history.reset(); ml.notify_all(); } - log_debug(gc, trim)("NativeTrimmer paused"); + log_debug(gc, trim)("NativeTrimmer pause"); } void unpause() { @@ -272,7 +308,7 @@ class NativeTrimmer : public ConcurrentGCThread { _next_trim_time = _next_trim_time_saved; ml.notify_all(); } - log_debug(gc, trim)("NativeTrimmer paused"); + log_debug(gc, trim)("NativeTrimmer unpause"); } void schedule_trim() { @@ -281,7 +317,11 @@ class NativeTrimmer : public ConcurrentGCThread { _next_trim_time = 0; ml.notify_all(); } - log_debug(gc, trim)("NativeTrimmer immediate trim"); + if (_periodic_trim_enabled) { + log_debug(gc, trim)("NativeTrimmer unpause+trim"); + } else { + log_debug(gc, trim)("NativeTrimmer trim"); + } } }; // NativeTrimmer @@ -308,7 +348,7 @@ void GCTrimNative::initialize() { if (GCTrimNativeHeapIntervalMax == 0) { // default FLAG_SET_ERGO(GCTrimNativeHeapIntervalMax, GCTrimNativeHeapInterval * 4); } - log_info(gc, trim)("Periodic native trim enabled (interval: %u-%u seconds).", + log_info(gc, trim)("Periodic native trim enabled (interval: %u seconds, step-down-interval: %u seconds).", GCTrimNativeHeapInterval, GCTrimNativeHeapIntervalMax); } else { log_info(gc, trim)("Periodic native trim disabled (we trim at full gc only)."); diff --git a/src/hotspot/share/gc/shared/genCollectedHeap.cpp b/src/hotspot/share/gc/shared/genCollectedHeap.cpp index 8536a53fefeb4..89750642c8f3c 100644 --- a/src/hotspot/share/gc/shared/genCollectedHeap.cpp +++ b/src/hotspot/share/gc/shared/genCollectedHeap.cpp @@ -509,6 +509,8 @@ void GenCollectedHeap::do_collection(bool full, "the requesting thread should have the Heap_lock"); guarantee(!is_gc_active(), "collection is not reentrant"); + GCTrimNative::PauseThenTrimMark trim_native_pause; + if (GCLocker::check_active_before_gc()) { return; // GC is disabled (e.g. JNI GetXXXCritical operation) } @@ -632,9 +634,6 @@ void GenCollectedHeap::do_collection(bool full, MetaspaceGC::compute_new_size(); update_full_collections_completed(); - // Trim the native heap, without a delay since this is a full gc - GCTrimNative::schedule_trim(); - print_heap_change(pre_gc_values); // Track memory usage and detect low memory after GC finishes diff --git a/src/hotspot/share/gc/z/zDriver.cpp b/src/hotspot/share/gc/z/zDriver.cpp index 5cdc0eea6c847..0c3bb519a9d1f 100644 --- a/src/hotspot/share/gc/z/zDriver.cpp +++ b/src/hotspot/share/gc/z/zDriver.cpp @@ -266,6 +266,7 @@ void ZDriver::collect(const ZDriverRequest& request) { template bool ZDriver::pause() { + GCTrimNative::PauseMark trim_native_pause; for (;;) { T op; VMThread::execute(&op); @@ -448,8 +449,6 @@ class ZDriverGCScope : public StackObj { void ZDriver::gc(const ZDriverRequest& request) { ZDriverGCScope scope(request); - pause_native_trim(); - // Phase 1: Pause Mark Start pause_mark_start(); @@ -483,23 +482,6 @@ void ZDriver::gc(const ZDriverRequest& request) { // Phase 10: Concurrent Relocate concurrent(relocate); - resume_native_trim(request.cause()); - -} - -void ZDriver::pause_native_trim() { - // Pause native trimming for the duration of the GC - GCTrimNative::pause_periodic_trim(); -} - -void ZDriver::resume_native_trim(GCCause::Cause cause) { - bool schedule_trim_now = - cause == GCCause::_z_high_usage || GCCause::is_user_requested_gc(cause); - if (schedule_trim_now) { - GCTrimNative::schedule_trim(); - } else { - GCTrimNative::unpause_periodic_trim(); - } } void ZDriver::run_service() { diff --git a/src/hotspot/share/gc/z/zDriver.hpp b/src/hotspot/share/gc/z/zDriver.hpp index 7601b83230ed8..08b1b80f2aa35 100644 --- a/src/hotspot/share/gc/z/zDriver.hpp +++ b/src/hotspot/share/gc/z/zDriver.hpp @@ -67,9 +67,6 @@ class ZDriver : public ConcurrentGCThread { void check_out_of_memory(); - void pause_native_trim(); - void resume_native_trim(GCCause::Cause cause); - void gc(const ZDriverRequest& request); protected: diff --git a/src/hotspot/share/runtime/flags/jvmFlagAccess.cpp b/src/hotspot/share/runtime/flags/jvmFlagAccess.cpp index 1fefc5146ce2b..3ecaa70a95d80 100644 --- a/src/hotspot/share/runtime/flags/jvmFlagAccess.cpp +++ b/src/hotspot/share/runtime/flags/jvmFlagAccess.cpp @@ -64,7 +64,6 @@ class TypedFlagAccessImpl : public FlagAccessImpl { T value = *((T*)value_addr); const JVMTypedFlagLimit* constraint = (const JVMTypedFlagLimit*)JVMFlagLimit::get_constraint(flag); if (constraint != nullptr && constraint->phase() <= static_cast(JVMFlagLimit::validating_phase())) { - verbose = verbose || (origin == JVMFlagOrigin::ERGONOMIC); JVMFlag::Error err = typed_check_constraint(constraint->constraint_func(), value, verbose); if (err != JVMFlag::SUCCESS) { return err; diff --git a/test/hotspot/jtreg/gc/TestTrimNative.java b/test/hotspot/jtreg/gc/TestTrimNative.java index 152bb8c6ad03e..6a04c012ea638 100644 --- a/test/hotspot/jtreg/gc/TestTrimNative.java +++ b/test/hotspot/jtreg/gc/TestTrimNative.java @@ -288,10 +288,6 @@ String getSwitchName() { boolean isShenandoah() { return this == GC.shenandoah; } } - static private boolean usesNativeTrimmer(GC gc) { - return gc.isG1() || gc.isParallel() || gc.isZ(); - } - static private final OutputAnalyzer runTestWithOptions(String[] extraOptions, String[] testArgs) throws IOException { List allOptions = new ArrayList(); @@ -342,7 +338,7 @@ private final static void parseOutputAndLookForNegativeTrim(OutputAnalyzer outpu } if (numTrimsFound > maxExpected) { throw new RuntimeException("Abnormal high number of trim attempts found (more than " + maxExpected + - "). Does the interval setting not work?"); + "). Does the interval setting not work?"); } } if (numTrimsFound < minExpected) { @@ -362,11 +358,11 @@ private final static void parseOutputAndLookForNegativeTrim(OutputAnalyzer outpu 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 + "."); + " to see at least a combined reduction of " + expectedMinimalReduction + "."); } } - // Test that GCTrimNativeHeap=1 causes a trim-native on full gc + // Test that we trim on full gc static private final void testWithFullGC(GC gc) throws IOException { System.out.println("testWithFullGC"); int sleeptime_secs = 2; @@ -374,19 +370,15 @@ static private final void testWithFullGC(GC gc) throws IOException { new String[] { gc.getSwitchName(), "-XX:+GCTrimNativeHeap" }, new String[] { "true" /* full gc */, String.valueOf(sleeptime_secs * 1000) /* ms after peak */ } ); + output.shouldContain("Native trim enabled."); + output.shouldContain("Periodic native trim enabled (interval: 30 seconds, step-down-interval: 120 seconds)."); + output.shouldContain("NativeTrimmer start"); + output.shouldContain("NativeTrimmer pause"); + output.shouldContain("NativeTrimmer unpause+trim"); + output.shouldContain("NativeTrimmer stop"); // With default interval time of 30 seconds, auto trimming should never kick in, so the only // log line we expect to see is the one from the full-gc induced trim. parseOutputAndLookForNegativeTrim(output, 1, 1); - // For GCs that use the NativeTrimmer, we want to see the NativeTrimmer paused during the GC, as well as - // started and shut down properly. - if (usesNativeTrimmer(gc)) { - output.shouldContain("NativeTrimmer started"); - output.shouldContain("NativeTrimmer paused"); - output.shouldContain("NativeTrimmer unpaused"); - output.shouldContain("NativeTrimmer stopped"); - } else { - output.shouldNotContain("NativeTrimmer"); - } } // Test that GCTrimNativeHeap=1 causes a trim-native automatically, without GC (for now, shenandoah only) @@ -400,29 +392,33 @@ static private final void testAuto(GC gc) throws IOException { ); long t2 = System.currentTimeMillis(); int runtime_s = (int)((t2 - t1) / 1000); + output.shouldContain("Native trim enabled."); + output.shouldContain("Periodic native trim enabled (interval: 1 seconds, step-down-interval: 4 seconds)."); // With an interval time of 1 second and a runtime of 6..x seconds we expect to see x log lines (+- fudge factor). - parseOutputAndLookForNegativeTrim(output, runtime_s - 4, runtime_s + 2); + parseOutputAndLookForNegativeTrim(output, runtime_s - 4, runtime_s); } // Test that trim-native correctly honors interval static private final void testAutoWithHighInterval(GC gc) throws IOException { - // We pass a very high interval. This should disable the feature for this short-lived test, we should see no trim + // We pass a high interval than the expected test runtime. We expect no trims. System.out.println("testAutoWithHighInterval"); OutputAnalyzer output = runTestWithOptions ( - new String[] { gc.getSwitchName(), "-XX:+GCTrimNativeHeap", "-XX:GCTrimNativeHeapInterval=30" }, + new String[] { gc.getSwitchName(), "-XX:+GCTrimNativeHeap", "-XX:GCTrimNativeHeapInterval=12" }, new String[] { "false" /* full gc */, "6000" /* ms after peak */ } ); + output.shouldContain("Native trim enabled."); + output.shouldContain("Periodic native trim enabled (interval: 12 seconds, step-down-interval: 48 seconds)."); output.shouldNotContain("Trim native heap"); } - // Test that trim-native correctly honors interval static private final void testAutoWithZeroInterval(GC gc) throws IOException { - // We pass a very high interval. This should disable the feature for this short-lived test, we should see no trim - System.out.println("testAutoWithHighInterval"); + System.out.println("testAutoWithZeroInterval"); OutputAnalyzer output = runTestWithOptions ( new String[] { gc.getSwitchName(), "-XX:+GCTrimNativeHeap", "-XX:GCTrimNativeHeapInterval=0" }, - new String[] { "false" /* full gc */, "6000" /* ms after peak */ } + new String[] { "false" /* full gc */, "4000" /* ms after peak */ } ); + output.shouldContain("Native trim enabled."); + output.shouldContain("Periodic native trim disabled"); output.shouldNotContain("Trim native heap"); } @@ -435,6 +431,7 @@ static private final void testOffOnNonCompliantPlatforms() throws IOException { new String[] { "true" /* full gc */, "2000" /* ms after peak */ } ); output.shouldContain("GCTrimNativeHeap disabled"); + output.shouldNotContain("Native trim enabled."); output.shouldNotContain("Trim native heap"); } @@ -446,6 +443,7 @@ static private final void testOffExplicit() throws IOException { new String[] { "-XX:-GCTrimNativeHeap" }, new String[] { "true" /* full gc */, "2000" /* ms after peak */ } ); + output.shouldNotContain("Native trim enabled."); output.shouldNotContain("Trim native heap"); } @@ -457,6 +455,7 @@ static private final void testOffByDefault() throws IOException { new String[] { }, new String[] { "true" /* full gc */, "2000" /* ms after peak */ } ); + output.shouldNotContain("Native trim enabled."); output.shouldNotContain("Trim native heap"); } From df3f777b2547dca64260f39e730dc44599274c31 Mon Sep 17 00:00:00 2001 From: tstuefe Date: Wed, 1 Feb 2023 12:04:46 +0100 Subject: [PATCH 15/27] wip --- .../share/gc/shared/gcTrimNativeHeap.cpp | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp b/src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp index 1654d13fd11ef..bfeb8e128d321 100644 --- a/src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp +++ b/src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp @@ -47,6 +47,7 @@ class TrimResult { const os::size_change_t _size_change; public: + TrimResult() : _time(-1) {} TrimResult(uint64_t t, uint64_t d, os::size_change_t size_change) : _time(t), _duration(d), _size_change(size_change) {} TrimResult(const TrimResult& other) : @@ -55,43 +56,44 @@ class TrimResult { uint64_t duration() const { return _duration; } const os::size_change_t& size_change() const { return _size_change; } + bool is_valid() const { return _time != -1; } + // Returns size reduction; positive if memory was reduced ssize_t size_reduction() const { return checked_cast(_size_change.before) - checked_cast(_size_change.after); } - }; // A FIFO of the last n trim results class TrimHistory { - static const int _max = 4; - // Size changes for the last n trims, young to old TrimResult _histo[_max]; - int _num; - - void push_elements() { - for (int i = _max - 1; i > 0; i--) { - _histo[i] = _histo[i - 1]; - } - } + int _pos; // position of next write public: - TrimHistory() : _num(0) {} - - void reset() { _num = 0; } + TrimHistory() : _pos(0) {} void add(const TrimResult& result) { - push_elements(); - _histo[0] = result; - if (_num < _max) { - _num++; + _histo[_pos] = result; + if (++_pos == _max) { + _pos = 0; } } + // Returns pointer to youngest result, plus iterator + const TrimResult* youngest(int& it) const { + it = 0; + return _num > 0 ? _histo : nullptr; + } + + const TrimResult* next_older(int& it) const { + it++; + return _num > it ? _histo + it : nullptr; + } + // Small heuristic to check if periodic trimming has been fruitful so far. // If this heuristic finds trimming to be harmful, we will inject one longer // trim interval (standard interval * GCTrimNativeStepDownFactor). From c765621b24b68bbb7dc0ae05e52a5545f5d2e15c Mon Sep 17 00:00:00 2001 From: tstuefe Date: Wed, 1 Feb 2023 19:43:08 +0100 Subject: [PATCH 16/27] src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp --- .../share/gc/shared/gcTrimNativeHeap.cpp | 267 +++++++++++------- 1 file changed, 163 insertions(+), 104 deletions(-) diff --git a/src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp b/src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp index bfeb8e128d321..7c03a1ab8f862 100644 --- a/src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp +++ b/src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp @@ -18,7 +18,7 @@ * * 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. + * questioSns. * */ @@ -27,6 +27,7 @@ #include "gc/shared/gc_globals.hpp" #include "gc/shared/gcTrimNativeHeap.hpp" #include "logging/log.hpp" +#include "logging/logStream.hpp" #include "runtime/atomic.hpp" #include "runtime/globals_extension.hpp" #include "runtime/mutex.hpp" @@ -40,28 +41,40 @@ class TrimResult { // time (ms) trim happened (javaTimeMillis) - const uint64_t _time; + int64_t _time; // time (ms) trim itself took. - const uint64_t _duration; - // memory relief - const os::size_change_t _size_change; + int64_t _duration; + // rss + size_t _rss_before, _rss_after; public: - TrimResult() : _time(-1) {} - TrimResult(uint64_t t, uint64_t d, os::size_change_t size_change) : - _time(t), _duration(d), _size_change(size_change) {} - TrimResult(const TrimResult& other) : - _time(other._time), _duration(other._duration), _size_change(other._size_change) {} - uint64_t time() const { return _time; } - uint64_t duration() const { return _duration; } - const os::size_change_t& size_change() const { return _size_change; } - bool is_valid() const { return _time != -1; } + TrimResult() : _time(-1), _duration(0), _rss_before(0), _rss_after(0) {} + + TrimResult(int64_t t, int64_t d, size_t rss1, size_t rss2) : + _time(t), _duration(d), _rss_before(rss1), _rss_after(rss2) + {} + + int64_t time() const { return _time; } + int64_t duration() const { return _duration; } + size_t rss_before() const { return _rss_before; } + size_t rss_after() const { return _rss_before; } + + bool is_valid() const { + return _time >= 0 && _duration >= 0 && + _rss_before != 0 && _rss_after != 0; + } // Returns size reduction; positive if memory was reduced ssize_t size_reduction() const { - return checked_cast(_size_change.before) - - checked_cast(_size_change.after); + return checked_cast(_rss_before) - + checked_cast(_rss_after); + } + + void print_on(outputStream* st) const { + st->print("time: " INT64_FORMAT ", duration " INT64_FORMAT + ", rss1: " SIZE_FORMAT ", rss2: " SIZE_FORMAT " (" SSIZE_FORMAT ")", + _time, _duration, _rss_before, _rss_after, size_reduction()); } }; @@ -69,6 +82,9 @@ class TrimResult { class TrimHistory { static const int _max = 4; + // Note: history may contain invalid results; for one, it is + // initialized with invalid results to keep iterating simple; + // also invalid results can happen if measuring rss goes wrong. TrimResult _histo[_max]; int _pos; // position of next write @@ -83,70 +99,17 @@ class TrimHistory { } } - // Returns pointer to youngest result, plus iterator - const TrimResult* youngest(int& it) const { - it = 0; - return _num > 0 ? _histo : nullptr; - } - - const TrimResult* next_older(int& it) const { - it++; - return _num > it ? _histo + it : nullptr; - } - - // Small heuristic to check if periodic trimming has been fruitful so far. - // If this heuristic finds trimming to be harmful, we will inject one longer - // trim interval (standard interval * GCTrimNativeStepDownFactor). - // - // Trimming costs are the trim itself plus the re-aquisition costs of memory should the - // released memory be malloced again. Trimming gains are the memory reduction over time. - // Lasting gains are good; gains that don't last are not. - // - // There are roughly three usage pattern: - // - rare malloc spikes interspersed with long idle periods. Trimming is beneficial - // since the relieved memory pressure holds for a long time. - // - a constant low-intensity malloc drone. Trimming does not help much here but its - // harmless too since trimming is cheap if it does not recover much. - // - frequent malloc spikes with short idle periods; trimmed memory will be re-aquired - // after only a short relief; here, trimming could be harmful since we pay a lot for - // not much relief. We want to alleviate these scenarios. - // - // Putting numbers on these things is difficult though. We cannot observe malloc - // load directly, only RSS. For every trim we know the RSS reduction (from, to). So - // for subsequent trims we also can glean from (.from) whether RSS bounced - // back. But that is quite vague since RSS may have been influenced by a ton of other - // developments, especially for longer trim intervals. - // - // Therefore this heuristic may produce false positives and negatives. We try to err on - // the side of too much trimming here and to identify only situations that are clearly - // harmful. Note that the GCTrimNativeStepDownFactor (4) is gentle enough for wrong - // heuristic results not to be too harmful. - bool recommend_pause() { - if (_num < _max / 2) { - return false; // not enough data; - } - int num_significant_trims = 0; - int num_bouncebacks = 0; - for (int i = _num - 1; i > 0; i--) { // oldest to youngest - const ssize_t sz_before = checked_cast(_histo[i].size_change().before); - const ssize_t sz_after = checked_cast(_histo[i].after); - const ssize_t gains = sz_before - sz_after; - if (gains > (ssize_t)MIN2(32 * M, _histo[i].before / 10)) { // considered significant - num_significant_trims++; - const ssize_t sz_before_next = checked_cast(_histo[i - 1].before); - const ssize_t bounceback = sz_before_next - sz_after; - // We consider it to have bounced back if RSS for the followup sample returns to - // within at least -2% of post-trim-RSS. - if (bounceback >= (gains - (gains / 50))) { - num_bouncebacks++; - } + template + void iterate_oldest_to_youngest(Functor f) const { + int idx = _pos; + do { + f(_histo + idx); + if (++idx == _max) { + idx = 0; } - } - log_trace(gc, trim)("Last %d trims yielded significant gains; %d showed bounceback.", - num_significant_trims, num_bouncebacks); - return (num_significant_trims >= (_max / 2) && - num_significant_trims == num_bouncebacks); + } while (idx != _pos); } + }; class NativeTrimmer : public ConcurrentGCThread { @@ -199,8 +162,7 @@ class NativeTrimmer : public ConcurrentGCThread { } // 2 - Trim - os::size_change_t sc; - bool have_trim_results = execute_trim_and_log(&sc); + TrimResult result = execute_trim_and_log(); // 3 - Update next trim time // Note: outside setters have preference - if we paused/unpaused/scheduled trim concurrently while the last trim @@ -212,26 +174,15 @@ class NativeTrimmer : public ConcurrentGCThread { int64_t ntt2 = _next_trim_time; if (ntt2 == ntt) { // not changed concurrently? if (_periodic_trim_enabled) { - // Feed trim data into history; then, if it recommends stepping down the trim interval, + // Feed trim data into history and examine history. // do that. - bool long_pause = false; - if (have_trim_results) { - _trim_history.add(&sc); - long_pause = _trim_history.recommend_pause(); - } else { - // Sample was invalid, we lost it and hence history is torn: reset history and start from - // scratch next time. - _trim_history.reset(); - } - - if (long_pause) { + _trim_history.add(result); + if (recommend_pause()) { log_debug(gc, trim)("NativeTrimmer: long pause (" INT64_FORMAT " ms)", _max_interval_ms); - _trim_history.reset(); _next_trim_time = tnow + _max_interval_ms; } else { _next_trim_time = tnow + _interval_ms; } - } else { // periodic trim disabled _next_trim_time = never; @@ -239,7 +190,6 @@ class NativeTrimmer : public ConcurrentGCThread { } } // Mutex scope } - } void stop_service() override { @@ -249,29 +199,139 @@ class NativeTrimmer : public ConcurrentGCThread { // Execute the native trim, log results. // Return true if trim succeeded *and* we have valid size change data. - bool execute_trim_and_log(os::size_change_t* sc) { + TrimResult execute_trim_and_log() { assert(os::can_trim_native_heap(), "Unexpected"); if (!os::should_trim_native_heap()) { log_trace(gc, trim)("Trim native heap: not necessary"); - return false; + return TrimResult(); } + const int64_t tnow = now(); + os::size_change_t sc; Ticks start = Ticks::now(); - if (os::trim_native_heap(sc)) { + 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 ? '-' : '+'; + 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), + PROPERFMTARGS(sc.before), PROPERFMTARGS(sc.after), sign, PROPERFMTARGS(delta), trim_time.seconds() * 1000); - return true; + return TrimResult(tnow, now() - tnow, sc.before, sc.after); } else { log_info(gc, trim)("Trim native heap (no details)"); } } + return TrimResult(); + } + + ////// Heuristics ///////////////////////////////////// + + // Small heuristic to check if periodic trimming has been fruitful so far. + // If this heuristic finds trimming to be harmful, we will inject one longer + // trim interval (GCTrimNativeIntervalMax). + // + // Trimming costs are the trim itself plus the re-aquisition costs of memory should the + // released memory be malloced again. Trimming gains are the memory reduction over time. + // Lasting gains are good; gains that don't last are not. + // + // There are roughly three usage pattern: + // - rare malloc spikes interspersed with long idle periods. Trimming is beneficial + // since the relieved memory pressure holds for a long time. + // - a constant low-intensity malloc drone. Trimming does not help much here but its + // harmless too since trimming is cheap if it does not recover much. + // - frequent malloc spikes with short idle periods; trimmed memory will be re-aquired + // after only a short relief; here, trimming could be harmful since we pay a lot for + // not much relief. We want to alleviate these scenarios. + // + // Putting numbers on these things is difficult though. We cannot observe malloc + // load directly, only RSS. For every trim we know the RSS reduction (from, to). So + // for subsequent trims we also can glean from (.from) whether RSS bounced + // back. But that is quite vague since RSS may have been influenced by a ton of other + // developments, especially for longer trim intervals. + // + // Therefore this heuristic may produce false positives and negatives. We try to err on + // the side of too much trimming here and to identify only situations that are clearly + // harmful. Note that the GCTrimNativeIntervalMax default (4 * GCTrimNativeInterval) + // is gentle enough for wrong heuristic results to not be too punative. + + // Given two results of subsequent trims, return the lasting gain of the + // first trim, in bytes. Negative numbers mean a loss. + static ssize_t calc_lasting_gain(const TrimResult& s1, const TrimResult& s2) { + ssize_t gain = s1.size_reduction(); + ssize_t loss = checked_cast(s2.rss_before()) - + checked_cast(s1.rss_after()); + return gain - loss; + } + + // Given two results of subsequent trims, return the interval time + // between them. This includes the trim time itself. + static int64_t interval_time(const TrimResult& s1, const TrimResult& s2) { + return s2.time() - s1.time(); + } + + // Given two results of subsequent trims, returns true if the first trim is considered + // "bad" - a trim that had been not worth the cost. + static bool is_bad_trim(const TrimResult& s1, const TrimResult& s2) { + assert(s1.is_valid() && s2.is_valid(), "Sanity"); + const int64_t tinterval = interval_time(s1, s2); + assert(tinterval >= 0, "negative interval? " INT64_FORMAT, tinterval); + if (tinterval <= 0) { + return false; + } + assert(tinterval >= s1.duration(), "trim duration cannot be larger than trim interval (" + INT64_FORMAT ", " INT64_FORMAT ")", tinterval, s1.duration()); + + // Cost: ratio of trim time to total interval time (which contains trim time) + const double ratio_trim_time_to_interval_time = + (double)s1.duration() / (double)tinterval; + assert(ratio_trim_time_to_interval_time >= 0, "Sanity"); + + // Any ratio of less than 1% trim time to interval time we regard as harmless + // (e.g. less than 10ms for 1second of interval) + if (ratio_trim_time_to_interval_time < 0.01) { + return false; + } + + // Benefit: Ratio of lasting size reduction to RSS before the first trim. + const double rss_gain_ratio = (double)calc_lasting_gain(s1, s2) / s1.rss_before(); + + // We consider paying 1% (or more) time-per-interval for + // 1% (or less, maybe even negative) rss size reduction as bad. + bool bad = ratio_trim_time_to_interval_time > rss_gain_ratio; + +tty->print_cr("%s", bad ? "BAD" : ""); + return false; } + bool recommend_pause() { + struct { int trims, bad, ignored; } counts = { 0, 0, 0 }; + const TrimResult* previous = nullptr; + auto trim_evaluater = [&counts, &previous] (const TrimResult* r) { + +tty->print("?? "); +r->print_on(tty); + + if (!r->is_valid() || previous == nullptr || !previous->is_valid()) { + // Note: we always ignore the very youngest trim, since we don't know the + // RSS bounce back to the next trim yet. + counts.ignored++; + } else { + counts.trims++; + if (is_bad_trim(*previous, *r)) { + counts.bad++; + } + } + +tty->cr(); + previous = r; + }; + _trim_history.iterate_oldest_to_youngest(trim_evaluater); + log_trace(gc, trim)("Heuristics: trims: %d, bad trims: %d, ignored: %d", + counts.trims, counts.bad, counts.ignored); + return counts.ignored <= 1 && counts.bad == counts.trims; + } + public: NativeTrimmer() : @@ -295,7 +355,6 @@ class NativeTrimmer : public ConcurrentGCThread { MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag); _next_trim_time_saved = _next_trim_time; _next_trim_time = never; - _trim_history.reset(); ml.notify_all(); } log_debug(gc, trim)("NativeTrimmer pause"); From 9811577c7741240df18c797552b990d8eb4334be Mon Sep 17 00:00:00 2001 From: tstuefe Date: Thu, 2 Feb 2023 06:43:51 +0100 Subject: [PATCH 17/27] rename --- src/hotspot/share/gc/g1/g1CollectedHeap.cpp | 2 +- src/hotspot/share/gc/g1/g1FullCollector.cpp | 1 - src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp | 2 +- src/hotspot/share/gc/parallel/psParallelCompact.cpp | 2 +- src/hotspot/share/gc/shared/genCollectedHeap.cpp | 2 +- .../gc/shared/{gcTrimNativeHeap.cpp => trimNative.cpp} | 2 +- .../gc/shared/{gcTrimNativeHeap.hpp => trimNative.hpp} | 6 +++--- src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp | 2 +- src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp | 2 +- src/hotspot/share/gc/z/zCollectedHeap.cpp | 2 +- src/hotspot/share/gc/z/zDriver.cpp | 2 +- src/hotspot/share/runtime/java.cpp | 2 +- 12 files changed, 13 insertions(+), 14 deletions(-) rename src/hotspot/share/gc/shared/{gcTrimNativeHeap.cpp => trimNative.cpp} (99%) rename src/hotspot/share/gc/shared/{gcTrimNativeHeap.hpp => trimNative.hpp} (92%) diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp index 13a98e5518635..7c0f067af9420 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp @@ -82,7 +82,6 @@ #include "gc/shared/gcLocker.inline.hpp" #include "gc/shared/gcTimer.hpp" #include "gc/shared/gcTraceTime.inline.hpp" -#include "gc/shared/gcTrimNativeHeap.hpp" #include "gc/shared/generationSpec.hpp" #include "gc/shared/isGCActiveMark.hpp" #include "gc/shared/locationPrinter.inline.hpp" @@ -93,6 +92,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" diff --git a/src/hotspot/share/gc/g1/g1FullCollector.cpp b/src/hotspot/share/gc/g1/g1FullCollector.cpp index 70c8ab5a14aa0..bb6c53070f741 100644 --- a/src/hotspot/share/gc/g1/g1FullCollector.cpp +++ b/src/hotspot/share/gc/g1/g1FullCollector.cpp @@ -39,7 +39,6 @@ #include "gc/g1/g1Policy.hpp" #include "gc/g1/g1RegionMarkStatsCache.inline.hpp" #include "gc/shared/gcTraceTime.inline.hpp" -#include "gc/shared/gcTrimNativeHeap.hpp" #include "gc/shared/preservedMarks.hpp" #include "gc/shared/referenceProcessor.hpp" #include "gc/shared/verifyOption.hpp" diff --git a/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp b/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp index 14a829e45b7ee..33d160c97f3a7 100644 --- a/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp +++ b/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp @@ -42,7 +42,7 @@ #include "gc/shared/locationPrinter.inline.hpp" #include "gc/shared/scavengableNMethods.hpp" #include "gc/shared/suspendibleThreadSet.hpp" -#include "gc/shared/gcTrimNativeHeap.hpp" +#include "gc/shared/trimNative.hpp" #include "logging/log.hpp" #include "memory/iterator.hpp" #include "memory/metaspaceCounters.hpp" diff --git a/src/hotspot/share/gc/parallel/psParallelCompact.cpp b/src/hotspot/share/gc/parallel/psParallelCompact.cpp index 5810020bd8f25..fcbc1c3e6fab1 100644 --- a/src/hotspot/share/gc/parallel/psParallelCompact.cpp +++ b/src/hotspot/share/gc/parallel/psParallelCompact.cpp @@ -49,7 +49,7 @@ #include "gc/shared/gcTimer.hpp" #include "gc/shared/gcTrace.hpp" #include "gc/shared/gcTraceTime.inline.hpp" -#include "gc/shared/gcTrimNativeHeap.hpp" +#include "gc/shared/trimNative.hpp" #include "gc/shared/isGCActiveMark.hpp" #include "gc/shared/oopStorage.inline.hpp" #include "gc/shared/oopStorageSet.inline.hpp" diff --git a/src/hotspot/share/gc/shared/genCollectedHeap.cpp b/src/hotspot/share/gc/shared/genCollectedHeap.cpp index 89750642c8f3c..790d9062dc89c 100644 --- a/src/hotspot/share/gc/shared/genCollectedHeap.cpp +++ b/src/hotspot/share/gc/shared/genCollectedHeap.cpp @@ -57,7 +57,7 @@ #include "gc/shared/scavengableNMethods.hpp" #include "gc/shared/space.hpp" #include "gc/shared/strongRootsScope.hpp" -#include "gc/shared/gcTrimNativeHeap.hpp" +#include "gc/shared/trimNative.hpp" #include "gc/shared/weakProcessor.hpp" #include "gc/shared/workerThread.hpp" #include "memory/iterator.hpp" diff --git a/src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp b/src/hotspot/share/gc/shared/trimNative.cpp similarity index 99% rename from src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp rename to src/hotspot/share/gc/shared/trimNative.cpp index 7c03a1ab8f862..c84b597dd0c48 100644 --- a/src/hotspot/share/gc/shared/gcTrimNativeHeap.cpp +++ b/src/hotspot/share/gc/shared/trimNative.cpp @@ -25,7 +25,7 @@ #include "precompiled.hpp" #include "gc/shared/concurrentGCThread.hpp" #include "gc/shared/gc_globals.hpp" -#include "gc/shared/gcTrimNativeHeap.hpp" +#include "gc/shared/trimNative.hpp" #include "logging/log.hpp" #include "logging/logStream.hpp" #include "runtime/atomic.hpp" diff --git a/src/hotspot/share/gc/shared/gcTrimNativeHeap.hpp b/src/hotspot/share/gc/shared/trimNative.hpp similarity index 92% rename from src/hotspot/share/gc/shared/gcTrimNativeHeap.hpp rename to src/hotspot/share/gc/shared/trimNative.hpp index 4f899ee51e019..436d8e3c7bad1 100644 --- a/src/hotspot/share/gc/shared/gcTrimNativeHeap.hpp +++ b/src/hotspot/share/gc/shared/trimNative.hpp @@ -23,8 +23,8 @@ * */ -#ifndef SHARE_GC_SHARED_GCTRIMNATIVEHEAP_HPP -#define SHARE_GC_SHARED_GCTRIMNATIVEHEAP_HPP +#ifndef SHARE_GC_SHARED_TRIMNATIVE_HPP +#define SHARE_GC_SHARED_TRIMNATIVE_HPP #include "memory/allStatic.hpp" @@ -54,4 +54,4 @@ class GCTrimNative : public AllStatic { }; -#endif // SHARE_GC_SHARED_GCTRIMNATIVEHEAP_HPP +#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 80bbbd86fe13c..732d80fa410eb 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp @@ -40,7 +40,7 @@ #include "gc/shenandoah/shenandoahVMOperations.hpp" #include "gc/shenandoah/shenandoahWorkerPolicy.hpp" #include "gc/shenandoah/heuristics/shenandoahHeuristics.hpp" -#include "gc/shared/gcTrimNativeHeap.hpp" +#include "gc/shared/trimNative.hpp" #include "memory/iterator.hpp" #include "memory/metaspaceUtils.hpp" #include "memory/metaspaceStats.hpp" diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp index c4609f1e06b8a..36f700301e661 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp @@ -30,11 +30,11 @@ #include "gc/shared/gcArguments.hpp" #include "gc/shared/gcTimer.hpp" #include "gc/shared/gcTraceTime.inline.hpp" -#include "gc/shared/gcTrimNativeHeap.hpp" #include "gc/shared/locationPrinter.inline.hpp" #include "gc/shared/memAllocator.hpp" #include "gc/shared/plab.hpp" #include "gc/shared/tlab_globals.hpp" +#include "gc/shared/trimNative.hpp" #include "gc/shenandoah/shenandoahBarrierSet.hpp" #include "gc/shenandoah/shenandoahClosures.inline.hpp" diff --git a/src/hotspot/share/gc/z/zCollectedHeap.cpp b/src/hotspot/share/gc/z/zCollectedHeap.cpp index 9271fd9e7a349..78b435b716b87 100644 --- a/src/hotspot/share/gc/z/zCollectedHeap.cpp +++ b/src/hotspot/share/gc/z/zCollectedHeap.cpp @@ -25,8 +25,8 @@ #include "classfile/classLoaderData.hpp" #include "gc/shared/gcHeapSummary.hpp" #include "gc/shared/gcLocker.inline.hpp" -#include "gc/shared/gcTrimNativeHeap.hpp" #include "gc/shared/suspendibleThreadSet.hpp" +#include "gc/shared/trimNative.hpp" #include "gc/z/zCollectedHeap.hpp" #include "gc/z/zDirector.hpp" #include "gc/z/zDriver.hpp" diff --git a/src/hotspot/share/gc/z/zDriver.cpp b/src/hotspot/share/gc/z/zDriver.cpp index 0c3bb519a9d1f..96592a0866757 100644 --- a/src/hotspot/share/gc/z/zDriver.cpp +++ b/src/hotspot/share/gc/z/zDriver.cpp @@ -26,7 +26,7 @@ #include "gc/shared/gcLocker.hpp" #include "gc/shared/isGCActiveMark.hpp" #include "gc/shared/gcVMOperations.hpp" -#include "gc/shared/gcTrimNativeHeap.hpp" +#include "gc/shared/trimNative.hpp" #include "gc/z/zAbort.inline.hpp" #include "gc/z/zBreakpoint.hpp" #include "gc/z/zCollectedHeap.hpp" diff --git a/src/hotspot/share/runtime/java.cpp b/src/hotspot/share/runtime/java.cpp index 54dc13b431d9a..65f52845e388b 100644 --- a/src/hotspot/share/runtime/java.cpp +++ b/src/hotspot/share/runtime/java.cpp @@ -34,7 +34,7 @@ #include "compiler/compileBroker.hpp" #include "compiler/compilerOracle.hpp" #include "gc/shared/collectedHeap.hpp" -#include "gc/shared/gcTrimNativeHeap.hpp" +#include "gc/shared/trimNative.hpp" #include "gc/shared/stringdedup/stringDedup.hpp" #include "interpreter/bytecodeHistogram.hpp" #include "jfr/jfrEvents.hpp" From f4140dfd1734b014da18f6aa18c6c39dc7fa5b2d Mon Sep 17 00:00:00 2001 From: tstuefe Date: Thu, 2 Feb 2023 06:45:25 +0100 Subject: [PATCH 18/27] rename NativeTrimmer --- src/hotspot/share/gc/shared/trimNative.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/hotspot/share/gc/shared/trimNative.cpp b/src/hotspot/share/gc/shared/trimNative.cpp index c84b597dd0c48..06b51f33fed21 100644 --- a/src/hotspot/share/gc/shared/trimNative.cpp +++ b/src/hotspot/share/gc/shared/trimNative.cpp @@ -112,7 +112,7 @@ class TrimHistory { }; -class NativeTrimmer : public ConcurrentGCThread { +class NativeTrimmerThread : public ConcurrentGCThread { Monitor* _lock; @@ -334,7 +334,7 @@ tty->cr(); public: - NativeTrimmer() : + NativeTrimmerThread() : _lock(new (std::nothrow) PaddedMonitor(Mutex::nosafepoint, "NativeTrimmer_lock")), _interval_ms(GCTrimNativeHeapInterval * 1000), _max_interval_ms(GCTrimNativeHeapIntervalMax * 1000), @@ -387,7 +387,7 @@ tty->cr(); }; // NativeTrimmer -static NativeTrimmer* g_trimmer_thread = nullptr; +static NativeTrimmerThread* g_trimmer_thread = nullptr; /// GCTrimNative outside facing methods @@ -415,7 +415,7 @@ void GCTrimNative::initialize() { log_info(gc, trim)("Periodic native trim disabled (we trim at full gc only)."); } - g_trimmer_thread = new NativeTrimmer(); + g_trimmer_thread = new NativeTrimmerThread(); } } From 0359343464e81f377dae0df9def255c03a30d22c Mon Sep 17 00:00:00 2001 From: tstuefe Date: Thu, 2 Feb 2023 06:52:30 +0100 Subject: [PATCH 19/27] rename GCTrimNative TrimNative --- src/hotspot/share/gc/g1/g1CollectedHeap.cpp | 4 +-- .../gc/parallel/parallelScavengeHeap.cpp | 2 +- .../share/gc/parallel/psParallelCompact.cpp | 2 +- .../share/gc/shared/genCollectedHeap.cpp | 4 +-- src/hotspot/share/gc/shared/trimNative.cpp | 10 +++---- src/hotspot/share/gc/shared/trimNative.hpp | 27 ++++++++++++------- .../gc/shenandoah/shenandoahControlThread.cpp | 4 +-- .../share/gc/shenandoah/shenandoahHeap.cpp | 2 +- src/hotspot/share/gc/z/zCollectedHeap.cpp | 2 +- src/hotspot/share/gc/z/zDriver.cpp | 2 +- src/hotspot/share/runtime/java.cpp | 2 +- 11 files changed, 34 insertions(+), 27 deletions(-) diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp index 7c0f067af9420..6a3169c54155c 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp @@ -1077,7 +1077,7 @@ bool G1CollectedHeap::do_full_collection(bool explicit_gc, bool do_maximal_compaction) { assert_at_safepoint_on_vm_thread(); - GCTrimNative::PauseThenTrimMark trim_pause_mark; + TrimNative::PauseThenTrimMark trim_pause_mark; if (GCLocker::check_active_before_gc()) { // Full GC was not completed. @@ -1753,7 +1753,7 @@ void G1CollectedHeap::safepoint_synchronize_end() { void G1CollectedHeap::post_initialize() { CollectedHeap::post_initialize(); ref_processing_init(); - GCTrimNative::initialize(); + TrimNative::initialize(); } void G1CollectedHeap::ref_processing_init() { diff --git a/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp b/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp index 33d160c97f3a7..ef7ff560a1fb6 100644 --- a/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp +++ b/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp @@ -192,7 +192,7 @@ void ParallelScavengeHeap::post_initialize() { ScavengableNMethods::initialize(&_is_scavengable); - GCTrimNative::initialize(); + TrimNative::initialize(); } void ParallelScavengeHeap::update_counters() { diff --git a/src/hotspot/share/gc/parallel/psParallelCompact.cpp b/src/hotspot/share/gc/parallel/psParallelCompact.cpp index fcbc1c3e6fab1..7fdb71e07e225 100644 --- a/src/hotspot/share/gc/parallel/psParallelCompact.cpp +++ b/src/hotspot/share/gc/parallel/psParallelCompact.cpp @@ -1712,7 +1712,7 @@ bool PSParallelCompact::invoke_no_policy(bool maximum_heap_compaction) { } // Pause native trimming for the duration of the GC - GCTrimNative::PauseThenTrimMark trim_native_pause; + TrimNative::PauseThenTrimMark trim_native_pause; ParallelScavengeHeap* heap = ParallelScavengeHeap::heap(); diff --git a/src/hotspot/share/gc/shared/genCollectedHeap.cpp b/src/hotspot/share/gc/shared/genCollectedHeap.cpp index 790d9062dc89c..c3176921e7762 100644 --- a/src/hotspot/share/gc/shared/genCollectedHeap.cpp +++ b/src/hotspot/share/gc/shared/genCollectedHeap.cpp @@ -203,7 +203,7 @@ void GenCollectedHeap::post_initialize() { ScavengableNMethods::initialize(&_is_scavengable); - GCTrimNative::initialize(); + TrimNative::initialize(); } void GenCollectedHeap::ref_processing_init() { @@ -509,7 +509,7 @@ void GenCollectedHeap::do_collection(bool full, "the requesting thread should have the Heap_lock"); guarantee(!is_gc_active(), "collection is not reentrant"); - GCTrimNative::PauseThenTrimMark trim_native_pause; + TrimNative::PauseThenTrimMark trim_native_pause; 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 index 06b51f33fed21..4fab59d82df16 100644 --- a/src/hotspot/share/gc/shared/trimNative.cpp +++ b/src/hotspot/share/gc/shared/trimNative.cpp @@ -391,7 +391,7 @@ static NativeTrimmerThread* g_trimmer_thread = nullptr; /// GCTrimNative outside facing methods -void GCTrimNative::initialize() { +void TrimNative::initialize() { if (GCTrimNativeHeap) { @@ -419,25 +419,25 @@ void GCTrimNative::initialize() { } } -void GCTrimNative::cleanup() { +void TrimNative::cleanup() { if (g_trimmer_thread != nullptr) { g_trimmer_thread->stop(); } } -void GCTrimNative::pause_periodic_trim() { +void TrimNative::pause_periodic_trim() { if (g_trimmer_thread != nullptr) { g_trimmer_thread->pause(); } } -void GCTrimNative::unpause_periodic_trim() { +void TrimNative::unpause_periodic_trim() { if (g_trimmer_thread != nullptr) { g_trimmer_thread->unpause(); } } -void GCTrimNative::schedule_trim() { +void TrimNative::schedule_trim() { if (g_trimmer_thread != nullptr) { g_trimmer_thread->schedule_trim(); } diff --git a/src/hotspot/share/gc/shared/trimNative.hpp b/src/hotspot/share/gc/shared/trimNative.hpp index 436d8e3c7bad1..5be4c2a368379 100644 --- a/src/hotspot/share/gc/shared/trimNative.hpp +++ b/src/hotspot/share/gc/shared/trimNative.hpp @@ -1,6 +1,6 @@ /* - * Copyright (c) 2022 SAP SE. All rights reserved. - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * 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 @@ -28,28 +28,35 @@ #include "memory/allStatic.hpp" -class GCTrimNative : public AllStatic { +class TrimNative : public AllStatic { public: static void initialize(); static void cleanup(); - // Pause/unpause periodic trim + // Pause periodic trim (if enabled). static void pause_periodic_trim(); + + // Unpause periodic trim (if enabled). static void unpause_periodic_trim(); - // Schedule an explicit trim now. If periodic trims are enabled and have been paused, - // they are unpaused. + // Schedule an explicit trim now. + // If periodic trims are enabled and had been paused, they are unpaused + // and the interval is reset. static void schedule_trim(); + // Pause periodic trimming while in scope; when leaving scope, + // resume periodic trimming. struct PauseMark { - PauseMark() { GCTrimNative::pause_periodic_trim(); } - ~PauseMark() { GCTrimNative::unpause_periodic_trim(); } + PauseMark() { pause_periodic_trim(); } + ~PauseMark() { unpause_periodic_trim(); } }; + // Pause periodic trimming while in scope; when leaving scope, + // trim immediately and resume periodic trimming with a new interval. struct PauseThenTrimMark { - PauseThenTrimMark() { GCTrimNative::pause_periodic_trim(); } - ~PauseThenTrimMark() { GCTrimNative::schedule_trim(); } + PauseThenTrimMark() { pause_periodic_trim(); } + ~PauseThenTrimMark() { schedule_trim(); } }; }; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp index 732d80fa410eb..3cc50a23942bd 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp @@ -425,7 +425,7 @@ void ShenandoahControlThread::stop_service() { } void ShenandoahControlThread::service_stw_full_cycle(GCCause::Cause cause) { - GCTrimNative::PauseThenTrimMark trim_pause_mark; + TrimNative::PauseThenTrimMark trim_pause_mark; GCIdMark gc_id_mark; ShenandoahGCSession session(cause); @@ -440,7 +440,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"); - GCTrimNative::PauseMark trim_pause_mark; + TrimNative::PauseMark trim_pause_mark; GCIdMark gc_id_mark; ShenandoahGCSession session(cause); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp index 36f700301e661..989f2e5beb360 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp @@ -627,7 +627,7 @@ void ShenandoahHeap::post_initialize() { JFR_ONLY(ShenandoahJFRSupport::register_jfr_type_serializers()); - GCTrimNative::initialize(); + TrimNative::initialize(); } size_t ShenandoahHeap::used() const { diff --git a/src/hotspot/share/gc/z/zCollectedHeap.cpp b/src/hotspot/share/gc/z/zCollectedHeap.cpp index 78b435b716b87..1cf7624e62d2a 100644 --- a/src/hotspot/share/gc/z/zCollectedHeap.cpp +++ b/src/hotspot/share/gc/z/zCollectedHeap.cpp @@ -75,7 +75,7 @@ jint ZCollectedHeap::initialize() { Universe::calculate_verify_data((HeapWord*)0, (HeapWord*)UINTPTR_MAX); - GCTrimNative::initialize(); + TrimNative::initialize(); return JNI_OK; } diff --git a/src/hotspot/share/gc/z/zDriver.cpp b/src/hotspot/share/gc/z/zDriver.cpp index 96592a0866757..dd74def312e92 100644 --- a/src/hotspot/share/gc/z/zDriver.cpp +++ b/src/hotspot/share/gc/z/zDriver.cpp @@ -266,7 +266,7 @@ void ZDriver::collect(const ZDriverRequest& request) { template bool ZDriver::pause() { - GCTrimNative::PauseMark trim_native_pause; + TrimNative::PauseMark trim_native_pause; for (;;) { T op; VMThread::execute(&op); diff --git a/src/hotspot/share/runtime/java.cpp b/src/hotspot/share/runtime/java.cpp index 65f52845e388b..d62a5ef6c4a89 100644 --- a/src/hotspot/share/runtime/java.cpp +++ b/src/hotspot/share/runtime/java.cpp @@ -451,7 +451,7 @@ void before_exit(JavaThread* thread, bool halt) { StringDedup::stop(); } - GCTrimNative::cleanup(); + TrimNative::cleanup(); // Stop concurrent GC threads Universe::heap()->stop(); From 55386d2a67c93528561afe1aeaf1fe253968dbe7 Mon Sep 17 00:00:00 2001 From: tstuefe Date: Thu, 2 Feb 2023 14:12:11 +0100 Subject: [PATCH 20/27] wip --- src/hotspot/os/aix/os_aix.inline.hpp | 1 - src/hotspot/os/bsd/os_bsd.inline.hpp | 1 - src/hotspot/os/linux/os_linux.cpp | 29 -- src/hotspot/os/windows/os_windows.inline.hpp | 1 - src/hotspot/share/gc/g1/g1CollectedHeap.cpp | 3 +- src/hotspot/share/gc/g1/g1ConcurrentMark.cpp | 5 + src/hotspot/share/gc/g1/g1VMOperations.cpp | 1 + src/hotspot/share/gc/shared/gc_globals.hpp | 29 +- .../share/gc/shared/jvmFlagConstraintsGC.cpp | 11 - .../share/gc/shared/jvmFlagConstraintsGC.hpp | 3 +- src/hotspot/share/gc/shared/trimNative.cpp | 293 ++++-------------- .../share/gc/shared/trimNativeStepDown.cpp | 107 +++++++ .../share/gc/shared/trimNativeStepDown.hpp | 170 ++++++++++ .../gc/shenandoah/shenandoahControlThread.cpp | 6 +- src/hotspot/share/gc/z/zDriver.cpp | 4 + src/hotspot/share/runtime/os.hpp | 3 - 16 files changed, 371 insertions(+), 296 deletions(-) create mode 100644 src/hotspot/share/gc/shared/trimNativeStepDown.cpp create mode 100644 src/hotspot/share/gc/shared/trimNativeStepDown.hpp diff --git a/src/hotspot/os/aix/os_aix.inline.hpp b/src/hotspot/os/aix/os_aix.inline.hpp index dd10aab76710f..5f7415e4a5181 100644 --- a/src/hotspot/os/aix/os_aix.inline.hpp +++ b/src/hotspot/os/aix/os_aix.inline.hpp @@ -54,7 +54,6 @@ inline void os::map_stack_shadow_pages(address sp) { // stubbed-out trim-native support inline bool os::can_trim_native_heap() { return false; } -inline bool os::should_trim_native_heap() { return false; } inline bool os::trim_native_heap(os::size_change_t* rss_change) { return false; } #endif // OS_AIX_OS_AIX_INLINE_HPP diff --git a/src/hotspot/os/bsd/os_bsd.inline.hpp b/src/hotspot/os/bsd/os_bsd.inline.hpp index 055a164e14495..f30ac61e463ff 100644 --- a/src/hotspot/os/bsd/os_bsd.inline.hpp +++ b/src/hotspot/os/bsd/os_bsd.inline.hpp @@ -57,7 +57,6 @@ inline void os::map_stack_shadow_pages(address sp) { // stubbed-out trim-native support inline bool os::can_trim_native_heap() { return false; } -inline bool os::should_trim_native_heap() { return false; } inline bool os::trim_native_heap(os::size_change_t* rss_change) { return false; } #endif // OS_BSD_OS_BSD_INLINE_HPP diff --git a/src/hotspot/os/linux/os_linux.cpp b/src/hotspot/os/linux/os_linux.cpp index 33909d3d4bda9..dbebaaaf8b49c 100644 --- a/src/hotspot/os/linux/os_linux.cpp +++ b/src/hotspot/os/linux/os_linux.cpp @@ -5415,32 +5415,3 @@ bool os::trim_native_heap(os::size_change_t* rss_change) { return false; // musl #endif } - -static const size_t retain_size = 2 * M; - -bool os::should_trim_native_heap() { -#ifdef __GLIBC__ - bool rc = true; - // We try, using mallinfo, to predict whether a malloc_trim(3) will be beneficial. - // - // "mallinfo::keepcost" is no help even if manpage claims this to be the projected - // trim size. In practice it is just a very small value with no relation to the actual - // effect trimming will have. - // - // Our best bet is "mallinfo::fordblks", the total chunk size of free blocks. Since - // only free blocks can be trimmed, a very low bar is to require their combined size - // to be higher than our retain size. Note, however, that "mallinfo::fordblks" includes - // already-trimmed blocks, since glibc trims by calling madvice(MADV_DONT_NEED) on free - // chunks but does not update its bookkeeping. - // - // In the end we want to prevent obvious bogus attempts to trim, and for that fordblks - // is good enough. - os::Linux::glibc_mallinfo mi; - bool possibly_wrapped; - os::Linux::get_mallinfo(&mi, &possibly_wrapped); - // If we cannot say for sure because we use an older glibc, assume trimming makes sense. - return possibly_wrapped ? true : retain_size < mi.fordblks; -#else - return false; // musl -#endif -} diff --git a/src/hotspot/os/windows/os_windows.inline.hpp b/src/hotspot/os/windows/os_windows.inline.hpp index 59065680c1462..276863e95b5f8 100644 --- a/src/hotspot/os/windows/os_windows.inline.hpp +++ b/src/hotspot/os/windows/os_windows.inline.hpp @@ -100,7 +100,6 @@ inline void PlatformMonitor::notify_all() { // stubbed-out trim-native support inline bool os::can_trim_native_heap() { return false; } -inline bool os::should_trim_native_heap() { return false; } inline bool os::trim_native_heap(os::size_change_t* rss_change) { return false; } #endif // OS_WINDOWS_OS_WINDOWS_INLINE_HPP diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp index 6a3169c54155c..1fda56d462b83 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp @@ -1077,8 +1077,6 @@ bool G1CollectedHeap::do_full_collection(bool explicit_gc, bool do_maximal_compaction) { assert_at_safepoint_on_vm_thread(); - TrimNative::PauseThenTrimMark trim_pause_mark; - if (GCLocker::check_active_before_gc()) { // Full GC was not completed. return false; @@ -2790,6 +2788,7 @@ void G1CollectedHeap::retire_tlabs() { void G1CollectedHeap::do_collection_pause_at_safepoint_helper() { ResourceMark rm; + TrimNative::PauseThenTrimMark trim_native_pause; 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 74818a90625f7..ede4b29b7fecb 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp @@ -56,6 +56,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" @@ -1229,6 +1230,8 @@ void G1ConcurrentMark::remark() { return; } + TrimNative::PauseMark trim_native_pause; + G1Policy* policy = _g1h->policy(); policy->record_concurrent_mark_remark_start(); @@ -1445,6 +1448,8 @@ void G1ConcurrentMark::cleanup() { return; } + TrimNative::PauseThenTrimMark trim_native_pause; + G1Policy* policy = _g1h->policy(); policy->record_concurrent_mark_cleanup_start(); diff --git a/src/hotspot/share/gc/g1/g1VMOperations.cpp b/src/hotspot/share/gc/g1/g1VMOperations.cpp index 8ccd7f2c9fb6b..0e78acf3c3dc3 100644 --- a/src/hotspot/share/gc/g1/g1VMOperations.cpp +++ b/src/hotspot/share/gc/g1/g1VMOperations.cpp @@ -34,6 +34,7 @@ #include "gc/shared/gcTimer.hpp" #include "gc/shared/gcTraceTime.inline.hpp" #include "gc/shared/isGCActiveMark.hpp" +#include "gc/shared/trimNative.hpp" #include "memory/universe.hpp" #include "runtime/interfaceSupport.inline.hpp" diff --git a/src/hotspot/share/gc/shared/gc_globals.hpp b/src/hotspot/share/gc/shared/gc_globals.hpp index bb49240250e51..7b965fb84eb3b 100644 --- a/src/hotspot/share/gc/shared/gc_globals.hpp +++ b/src/hotspot/share/gc/shared/gc_globals.hpp @@ -692,25 +692,22 @@ range(128, NOT_LP64(512) LP64_ONLY(1024)) \ constraint(GCCardSizeInBytesConstraintFunc,AtParse) \ \ - product(bool, GCTrimNativeHeap, false, EXPERIMENTAL, \ + product(bool, TrimNativeHeap, false, EXPERIMENTAL, \ "GC will attempt to trim the native heap periodically and " \ - "after full GCs.") \ - \ - product(uint, GCTrimNativeHeapInterval, 30, EXPERIMENTAL, \ - "If GCTrimNativeHeap is enabled: interval, in seconds, in which " \ - "the VM will attempt to trim the native heap. This is a lower " \ - "bound; the JVM may increase the interval time up to " \ - "GCTrimNativeHeapIntervalMax." \ - "A value of 0 disables periodic trimming altogether while " \ - "leaving trimming after full gc enabled.") \ + "at the end of a GC cycle.") \ + \ + product(uint, TrimNativeHeapInterval, 5, EXPERIMENTAL, \ + "If TrimNativeHeap is enabled: interval, in seconds, in which " \ + "the GC will attempt to trim the native heap. If " \ + "TrimNativeHeapAdaptiveStepDown is enabled, the JVM may lower " \ + "the frequency of trimming." \ + "A value of 0 disables periodic trimming altogether while still " \ + "trimming after a GC cycle.") \ range(0, (24 * 60 * 60)) \ \ - product(uint, GCTrimNativeHeapIntervalMax, 0, EXPERIMENTAL, \ - "If GCTrimNativeHeap is enabled and GCTrimNativeHeapInterval is " \ - "not 0: upper bound for the interval time, in seconds, in which " \ - "the VM will attempt to trim the native heap.") \ - range(0, max_juint) \ - constraint(GCTrimNativeHeapIntervalMaxFunc,AtParse) \ + product(bool, TrimNativeHeapAdaptiveStepDown, false, EXPERIMENTAL, \ + "If TrimNativeHeap and periodic trimming are enabled: if true, " \ + "the GC may step down trimming frequency if needed. ") \ \ // end of GC_FLAGS diff --git a/src/hotspot/share/gc/shared/jvmFlagConstraintsGC.cpp b/src/hotspot/share/gc/shared/jvmFlagConstraintsGC.cpp index 98cc3af4f571c..dbe4e31da8ef4 100644 --- a/src/hotspot/share/gc/shared/jvmFlagConstraintsGC.cpp +++ b/src/hotspot/share/gc/shared/jvmFlagConstraintsGC.cpp @@ -436,14 +436,3 @@ JVMFlag::Error GCCardSizeInBytesConstraintFunc(uint value, bool verbose) { } } -JVMFlag::Error GCTrimNativeHeapIntervalMaxFunc(uint value, bool verbose) { - if (GCTrimNativeHeap && value < GCTrimNativeHeapInterval) { - JVMFlag::printError(verbose, - "GCTrimNativeHeapIntervalMax ( %u ) must be " - "greater than or equal to GCTrimNativeHeapInterval ( %u )\n", - value, GCTrimNativeHeapInterval); - return JVMFlag::VIOLATES_CONSTRAINT; - } else { - return JVMFlag::SUCCESS; - } -} diff --git a/src/hotspot/share/gc/shared/jvmFlagConstraintsGC.hpp b/src/hotspot/share/gc/shared/jvmFlagConstraintsGC.hpp index 67904e278c01c..da320944b0ef2 100644 --- a/src/hotspot/share/gc/shared/jvmFlagConstraintsGC.hpp +++ b/src/hotspot/share/gc/shared/jvmFlagConstraintsGC.hpp @@ -67,8 +67,7 @@ f(uintx, SurvivorRatioConstraintFunc) \ f(size_t, MetaspaceSizeConstraintFunc) \ f(size_t, MaxMetaspaceSizeConstraintFunc) \ - f(uint, GCCardSizeInBytesConstraintFunc) \ - f(uint, GCTrimNativeHeapIntervalMaxFunc) + f(uint, GCCardSizeInBytesConstraintFunc) SHARED_GC_CONSTRAINTS(DECLARE_CONSTRAINT) diff --git a/src/hotspot/share/gc/shared/trimNative.cpp b/src/hotspot/share/gc/shared/trimNative.cpp index 4fab59d82df16..a1b9f748d5a99 100644 --- a/src/hotspot/share/gc/shared/trimNative.cpp +++ b/src/hotspot/share/gc/shared/trimNative.cpp @@ -26,9 +26,8 @@ #include "gc/shared/concurrentGCThread.hpp" #include "gc/shared/gc_globals.hpp" #include "gc/shared/trimNative.hpp" +#include "gc/shared/trimNativeStepDown.hpp" #include "logging/log.hpp" -#include "logging/logStream.hpp" -#include "runtime/atomic.hpp" #include "runtime/globals_extension.hpp" #include "runtime/mutex.hpp" #include "runtime/mutexLocker.hpp" @@ -37,94 +36,33 @@ #include "utilities/globalDefinitions.hpp" #include "utilities/ticks.hpp" -// A class holding trim results for a single trim operation. -class TrimResult { - - // time (ms) trim happened (javaTimeMillis) - int64_t _time; - // time (ms) trim itself took. - int64_t _duration; - // rss - size_t _rss_before, _rss_after; - -public: - - TrimResult() : _time(-1), _duration(0), _rss_before(0), _rss_after(0) {} - - TrimResult(int64_t t, int64_t d, size_t rss1, size_t rss2) : - _time(t), _duration(d), _rss_before(rss1), _rss_after(rss2) - {} - - int64_t time() const { return _time; } - int64_t duration() const { return _duration; } - size_t rss_before() const { return _rss_before; } - size_t rss_after() const { return _rss_before; } - - bool is_valid() const { - return _time >= 0 && _duration >= 0 && - _rss_before != 0 && _rss_after != 0; - } - - // Returns size reduction; positive if memory was reduced - ssize_t size_reduction() const { - return checked_cast(_rss_before) - - checked_cast(_rss_after); - } - - void print_on(outputStream* st) const { - st->print("time: " INT64_FORMAT ", duration " INT64_FORMAT - ", rss1: " SIZE_FORMAT ", rss2: " SIZE_FORMAT " (" SSIZE_FORMAT ")", - _time, _duration, _rss_before, _rss_after, size_reduction()); - } -}; - -// A FIFO of the last n trim results -class TrimHistory { - static const int _max = 4; - - // Note: history may contain invalid results; for one, it is - // initialized with invalid results to keep iterating simple; - // also invalid results can happen if measuring rss goes wrong. - TrimResult _histo[_max]; - int _pos; // position of next write - -public: - - TrimHistory() : _pos(0) {} - - void add(const TrimResult& result) { - _histo[_pos] = result; - if (++_pos == _max) { - _pos = 0; - } - } - - template - void iterate_oldest_to_youngest(Functor f) const { - int idx = _pos; - do { - f(_histo + idx); - if (++idx == _max) { - idx = 0; - } - } while (idx != _pos); - } - -}; - class NativeTrimmerThread : public ConcurrentGCThread { Monitor* _lock; // Periodic trimming state const int64_t _interval_ms; - const int64_t _max_interval_ms; const bool _periodic_trim_enabled; + const bool _adaptive_stepdown_enabled; int64_t _next_trim_time; int64_t _next_trim_time_saved; // for pause - TrimHistory _trim_history; + // Adaptive step-down + TrimNativeStepDownControl _stepdown_control; + static const int _min_stepdown_factor = 2; // 2 * interval length + static const int _max_stepdown_factor = 8; // 8 * interval length + static const int64_t _stepdown_factor_reset_after = 60 * 1000; + int64_t _last_stepdown_time; + int _last_stepdown_factor; + + void update_stepdown_factor(int64_t tnow) { + if (tnow > (_last_stepdown_time + _stepdown_factor_reset_after)) { + _last_stepdown_factor = _min_stepdown_factor; + } else { + _last_stepdown_factor = MIN2(_last_stepdown_factor + 1, _max_stepdown_factor); + } + } static const int64_t never = INT64_MAX; @@ -132,16 +70,13 @@ class NativeTrimmerThread : public ConcurrentGCThread { void run_service() override { - assert(GCTrimNativeHeap, "Sanity"); - assert(os::can_trim_native_heap(), "Sanity"); - log_info(gc, trim)("NativeTrimmer start."); int64_t ntt = 0; int64_t tnow = 0; for (;;) { - // 1 - Wait for the next trim point + // 1 - Wait for _next_trim_time. Handle spurious wakeups and shutdown. { MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag); do { @@ -161,28 +96,42 @@ class NativeTrimmerThread : public ConcurrentGCThread { } while (ntt > tnow); } - // 2 - Trim + // 2 - Trimming happens outside of lock protection. GC threads can issue new commands + // concurrently. TrimResult result = execute_trim_and_log(); - // 3 - Update next trim time - // Note: outside setters have preference - if we paused/unpaused/scheduled trim concurrently while the last trim - // was in progress, we do that. Note that if this causes two back-to-back trims, that is harmless since usually - // the second trim is cheap. + // 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? + if (_periodic_trim_enabled) { - // Feed trim data into history and examine history. - // do that. - _trim_history.add(result); - if (recommend_pause()) { - log_debug(gc, trim)("NativeTrimmer: long pause (" INT64_FORMAT " ms)", _max_interval_ms); - _next_trim_time = tnow + _max_interval_ms; - } else { - _next_trim_time = tnow + _interval_ms; + int64_t interval_length = _interval_ms; + + // Handle adaptive stepdown. If heuristic recommends step-down, we prolong the + // wait interval by a factor that gets progressively larger with subsequent step-downs. + // Factor is capped and gets reset after a while. + if (_adaptive_stepdown_enabled) { + _stepdown_control.feed(result); + + if (_stepdown_control.recommend_step_down()) { + _last_stepdown_factor = + // increase or reset step-down factor depending on how many step-downs we had and + // how long they are ago. + (tnow > (_last_stepdown_time + _stepdown_factor_reset_after)) ? + _min_stepdown_factor : + MIN2(_last_stepdown_factor + 1, _max_stepdown_factor); + + _last_stepdown_time = tnow; + interval_length = _interval_ms * _last_stepdown_factor; + log_debug(gc, trim)("NativeTrimmer: long pause (" INT64_FORMAT " ms)", interval_length); + } } + _next_trim_time = tnow + interval_length; + } else { // periodic trim disabled _next_trim_time = never; @@ -199,15 +148,12 @@ class NativeTrimmerThread : public ConcurrentGCThread { // Execute the native trim, log results. // Return true if trim succeeded *and* we have valid size change data. - TrimResult execute_trim_and_log() { + TrimResult execute_trim_and_log() const { assert(os::can_trim_native_heap(), "Unexpected"); - if (!os::should_trim_native_heap()) { - log_trace(gc, trim)("Trim native heap: not necessary"); - return TrimResult(); - } 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) { @@ -224,123 +170,17 @@ class NativeTrimmerThread : public ConcurrentGCThread { return TrimResult(); } - ////// Heuristics ///////////////////////////////////// - - // Small heuristic to check if periodic trimming has been fruitful so far. - // If this heuristic finds trimming to be harmful, we will inject one longer - // trim interval (GCTrimNativeIntervalMax). - // - // Trimming costs are the trim itself plus the re-aquisition costs of memory should the - // released memory be malloced again. Trimming gains are the memory reduction over time. - // Lasting gains are good; gains that don't last are not. - // - // There are roughly three usage pattern: - // - rare malloc spikes interspersed with long idle periods. Trimming is beneficial - // since the relieved memory pressure holds for a long time. - // - a constant low-intensity malloc drone. Trimming does not help much here but its - // harmless too since trimming is cheap if it does not recover much. - // - frequent malloc spikes with short idle periods; trimmed memory will be re-aquired - // after only a short relief; here, trimming could be harmful since we pay a lot for - // not much relief. We want to alleviate these scenarios. - // - // Putting numbers on these things is difficult though. We cannot observe malloc - // load directly, only RSS. For every trim we know the RSS reduction (from, to). So - // for subsequent trims we also can glean from (.from) whether RSS bounced - // back. But that is quite vague since RSS may have been influenced by a ton of other - // developments, especially for longer trim intervals. - // - // Therefore this heuristic may produce false positives and negatives. We try to err on - // the side of too much trimming here and to identify only situations that are clearly - // harmful. Note that the GCTrimNativeIntervalMax default (4 * GCTrimNativeInterval) - // is gentle enough for wrong heuristic results to not be too punative. - - // Given two results of subsequent trims, return the lasting gain of the - // first trim, in bytes. Negative numbers mean a loss. - static ssize_t calc_lasting_gain(const TrimResult& s1, const TrimResult& s2) { - ssize_t gain = s1.size_reduction(); - ssize_t loss = checked_cast(s2.rss_before()) - - checked_cast(s1.rss_after()); - return gain - loss; - } - - // Given two results of subsequent trims, return the interval time - // between them. This includes the trim time itself. - static int64_t interval_time(const TrimResult& s1, const TrimResult& s2) { - return s2.time() - s1.time(); - } - - // Given two results of subsequent trims, returns true if the first trim is considered - // "bad" - a trim that had been not worth the cost. - static bool is_bad_trim(const TrimResult& s1, const TrimResult& s2) { - assert(s1.is_valid() && s2.is_valid(), "Sanity"); - const int64_t tinterval = interval_time(s1, s2); - assert(tinterval >= 0, "negative interval? " INT64_FORMAT, tinterval); - if (tinterval <= 0) { - return false; - } - assert(tinterval >= s1.duration(), "trim duration cannot be larger than trim interval (" - INT64_FORMAT ", " INT64_FORMAT ")", tinterval, s1.duration()); - - // Cost: ratio of trim time to total interval time (which contains trim time) - const double ratio_trim_time_to_interval_time = - (double)s1.duration() / (double)tinterval; - assert(ratio_trim_time_to_interval_time >= 0, "Sanity"); - - // Any ratio of less than 1% trim time to interval time we regard as harmless - // (e.g. less than 10ms for 1second of interval) - if (ratio_trim_time_to_interval_time < 0.01) { - return false; - } - - // Benefit: Ratio of lasting size reduction to RSS before the first trim. - const double rss_gain_ratio = (double)calc_lasting_gain(s1, s2) / s1.rss_before(); - - // We consider paying 1% (or more) time-per-interval for - // 1% (or less, maybe even negative) rss size reduction as bad. - bool bad = ratio_trim_time_to_interval_time > rss_gain_ratio; - -tty->print_cr("%s", bad ? "BAD" : ""); - - return false; - } - - bool recommend_pause() { - struct { int trims, bad, ignored; } counts = { 0, 0, 0 }; - const TrimResult* previous = nullptr; - auto trim_evaluater = [&counts, &previous] (const TrimResult* r) { - -tty->print("?? "); -r->print_on(tty); - - if (!r->is_valid() || previous == nullptr || !previous->is_valid()) { - // Note: we always ignore the very youngest trim, since we don't know the - // RSS bounce back to the next trim yet. - counts.ignored++; - } else { - counts.trims++; - if (is_bad_trim(*previous, *r)) { - counts.bad++; - } - } - -tty->cr(); - previous = r; - }; - _trim_history.iterate_oldest_to_youngest(trim_evaluater); - log_trace(gc, trim)("Heuristics: trims: %d, bad trims: %d, ignored: %d", - counts.trims, counts.bad, counts.ignored); - return counts.ignored <= 1 && counts.bad == counts.trims; - } - public: NativeTrimmerThread() : _lock(new (std::nothrow) PaddedMonitor(Mutex::nosafepoint, "NativeTrimmer_lock")), - _interval_ms(GCTrimNativeHeapInterval * 1000), - _max_interval_ms(GCTrimNativeHeapIntervalMax * 1000), - _periodic_trim_enabled(GCTrimNativeHeapInterval > 0), + _interval_ms(TrimNativeHeapInterval * 1000), + _periodic_trim_enabled(TrimNativeHeapInterval > 0), + _adaptive_stepdown_enabled(TrimNativeHeapAdaptiveStepDown), _next_trim_time(0), - _next_trim_time_saved(0) + _next_trim_time_saved(0), + _last_stepdown_time(0), + _last_stepdown_factor(_min_stepdown_factor) { set_name("Native Heap Trimmer"); _next_trim_time = _periodic_trim_enabled ? (now() + _interval_ms) : never; @@ -372,7 +212,7 @@ tty->cr(); log_debug(gc, trim)("NativeTrimmer unpause"); } - void schedule_trim() { + void unpause_and_trim() { { MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag); _next_trim_time = 0; @@ -392,29 +232,24 @@ static NativeTrimmerThread* g_trimmer_thread = nullptr; /// GCTrimNative outside facing methods void TrimNative::initialize() { - - if (GCTrimNativeHeap) { - + if (TrimNativeHeap) { if (!os::can_trim_native_heap()) { - FLAG_SET_ERGO(GCTrimNativeHeap, false); - log_info(gc, trim)("GCTrimNativeHeap disabled - trim-native not supported on this platform."); + FLAG_SET_ERGO(TrimNativeHeap, false); + log_info(gc, trim)("Native trim not supported on this platform."); return; } log_info(gc, trim)("Native trim enabled."); - if (GCTrimNativeHeapInterval > 0) { // periodic trimming enabled - assert(GCTrimNativeHeapIntervalMax == 0 || - GCTrimNativeHeapIntervalMax > GCTrimNativeHeapInterval, "Sanity"); // see flag constraint - if (GCTrimNativeHeapIntervalMax == 0) { // default - FLAG_SET_ERGO(GCTrimNativeHeapIntervalMax, GCTrimNativeHeapInterval * 4); + if (TrimNativeHeapInterval == 0) { + if (TrimNativeHeapAdaptiveStepDown) { + FLAG_SET_ERGO(TrimNativeHeapAdaptiveStepDown, false); } - log_info(gc, trim)("Periodic native trim enabled (interval: %u seconds, step-down-interval: %u seconds).", - GCTrimNativeHeapInterval, GCTrimNativeHeapIntervalMax); + log_info(gc, trim)("Periodic trimming disabled."); } else { - log_info(gc, trim)("Periodic native trim disabled (we trim at full gc only)."); + log_info(gc, trim)("Periodic native trim enabled (interval: %u seconds, dynamic step-down %s", + TrimNativeHeapInterval, (TrimNativeHeapAdaptiveStepDown ? "enabled" : "disabled")); } - g_trimmer_thread = new NativeTrimmerThread(); } } @@ -439,6 +274,6 @@ void TrimNative::unpause_periodic_trim() { void TrimNative::schedule_trim() { if (g_trimmer_thread != nullptr) { - g_trimmer_thread->schedule_trim(); + g_trimmer_thread->unpause_and_trim(); } } diff --git a/src/hotspot/share/gc/shared/trimNativeStepDown.cpp b/src/hotspot/share/gc/shared/trimNativeStepDown.cpp new file mode 100644 index 0000000000000..bf04bd52deb20 --- /dev/null +++ b/src/hotspot/share/gc/shared/trimNativeStepDown.cpp @@ -0,0 +1,107 @@ +/* + * 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/trimNativeStepDown.hpp" +#include "logging/log.hpp" +#include "runtime/os.hpp" +#include "utilities/debug.hpp" +#include "utilities/globalDefinitions.hpp" +#include "utilities/ostream.hpp" + +void TrimResult::print_on(outputStream* st) const { + st->print("time: " INT64_FORMAT ", duration " INT64_FORMAT + ", rss1: " SIZE_FORMAT ", rss2: " SIZE_FORMAT " (" SSIZE_FORMAT ")", + _time, _duration, _rss_before, _rss_after, size_reduction()); +} + +// Given two results of subsequent trims, returns true if the first trim is considered +// "bad" - a trim that had been not worth the cost. +bool TrimNativeStepDownControl::is_bad_trim(const TrimResult& r, const TrimResult& r_followup) { + assert(r.is_valid() && r_followup.is_valid(), "Sanity"); + + const int64_t tinterval = r.interval_time(r_followup); + assert(tinterval >= 0, "negative interval? " INT64_FORMAT, tinterval); + if (tinterval == 0) { + return false; + } + assert(tinterval >= r.duration(), "trim duration cannot be larger than trim interval (" + INT64_FORMAT ", " INT64_FORMAT ")", tinterval, r.duration()); + + // Cost: ratio of trim time to total interval time (which contains trim time) + const double ratio_trim_time_to_interval_time = + (double)r.duration() / (double)tinterval; + assert(ratio_trim_time_to_interval_time >= 0, "Sanity"); + + // Any ratio of less than 1% trim time to interval time we regard as harmless + // (e.g. less than 10ms for 1second of interval) + if (ratio_trim_time_to_interval_time < 0.01) { + return false; + } + + // Benefit: Ratio of lasting size reduction to RSS before the first trim. + const double rss_gain_ratio = (double)r.calc_lasting_gain(r_followup) / (double)r.rss_before(); + + // We consider paying 1% (or more) time-per-interval for + // 1% (or less, maybe even negative) rss size reduction as bad. + bool bad = ratio_trim_time_to_interval_time > rss_gain_ratio; + + return false; +} + +bool TrimNativeStepDownControl::recommend_step_down() const { + struct { int trims, bad, ignored; } counts = { 0, 0, 0 }; + + const TrimResult* previous = nullptr; + auto trim_evaluater = [&counts, &previous] (const TrimResult* r) { +tty->print("?? "); +r->print_on(tty); + if (!r->is_valid() || previous == nullptr || !previous->is_valid()) { + // Note: we ignore: + // - the very youngest trim, since we don't know the + // RSS bounce back to the next trim yet. + // - invalid trim results + counts.ignored++; + } else { + counts.trims++; + if (is_bad_trim(*previous, *r)) { + counts.bad++; + } + } +tty->cr(); + previous = r; + }; + _history.iterate_oldest_to_youngest(trim_evaluater); + + log_trace(gc, trim)("Heuristic says: trims: %d, bad trims: %d, ignored: %d", + counts.trims, counts.bad, counts.ignored); + + // If all trims in the history had been bad (excluding the youngest, for which we cannot + // evaluate the lasting gains yet), step down. + return counts.ignored <= 1 && counts.bad == counts.trims; +} + +void TrimNativeStepDownControl::feed(const TrimResult& r) { + _history.add(r); +} diff --git a/src/hotspot/share/gc/shared/trimNativeStepDown.hpp b/src/hotspot/share/gc/shared/trimNativeStepDown.hpp new file mode 100644 index 0000000000000..f98c5a18a5bf7 --- /dev/null +++ b/src/hotspot/share/gc/shared/trimNativeStepDown.hpp @@ -0,0 +1,170 @@ +/* + * 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_TRIMNATIVESTEPDOWN_HPP +#define SHARE_GC_SHARED_TRIMNATIVESTEPDOWN_HPP + + +#include "memory/allStatic.hpp" +#include "utilities/globalDefinitions.hpp" + +class outputStream; + +/////// Support for TrimNativeHeapAdaptiveStepDown ////// +// +// Small heuristic to check if periodic trimming has been fruitful so far. +// If this heuristic finds trimming to be harmful, we will inject one longer +// trim interval (GCTrimNativeIntervalMax). +// +// Trimming costs are the trim itself plus the re-aquisition costs of memory should the +// released memory be malloced again. Trimming gains are the memory reduction over time. +// Lasting gains are good; gains that don't last are not. +// +// There are roughly three usage pattern: +// - rare malloc spikes interspersed with long idle periods. Trimming is beneficial +// since the relieved memory pressure holds for a long time. +// - a constant low-intensity malloc drone. Trimming does not help much here but its +// harmless too since trimming is cheap if it does not recover much. +// - frequent malloc spikes with short idle periods; trimmed memory will be re-aquired +// after only a short relief; here, trimming could be harmful since we pay a lot for +// not much relief. We want to alleviate these scenarios. +// +// Putting numbers on these things is difficult though. We cannot observe malloc +// load directly, only RSS. For every trim we know the RSS reduction (from, to). So +// for subsequent trims we also can glean from (.from) whether RSS bounced +// back. But that is quite vague since RSS may have been influenced by a ton of other +// developments, especially for longer trim intervals. +// +// Therefore this heuristic may produce false positives and negatives. We try to err on +// the side of too much trimming here and to identify only situations that are clearly +// harmful. Note that the GCTrimNativeIntervalMax default (4 * GCTrimNativeInterval) +// is gentle enough for wrong heuristic results to not be too punative. + + +// A class holding results for a single trim operation. +class TrimResult { + + // time (ms) trim happened (javaTimeMillis) + int64_t _time; + // time (ms) trim itself took. + int64_t _duration; + // rss + size_t _rss_before, _rss_after; + +public: + + TrimResult() : _time(-1), _duration(0), _rss_before(0), _rss_after(0) {} + + TrimResult(int64_t t, int64_t d, size_t rss1, size_t rss2) : + _time(t), _duration(d), _rss_before(rss1), _rss_after(rss2) + {} + + int64_t time() const { return _time; } + int64_t duration() const { return _duration; } + size_t rss_before() const { return _rss_before; } + size_t rss_after() const { return _rss_before; } + + bool is_valid() const { + return _time >= 0 && _duration >= 0 && + _rss_before != 0 && _rss_after != 0; + } + + // Returns size reduction; positive if memory was reduced + ssize_t size_reduction() const { + return checked_cast(_rss_before) - + checked_cast(_rss_after); + } + + // Return the lasting gain compared with a follow-up trim. Negative numbers mean a loss. + ssize_t calc_lasting_gain(const TrimResult& followup_trim) const { + ssize_t gain = size_reduction(); + ssize_t loss = checked_cast(followup_trim.rss_before()) - + checked_cast(rss_after()); + return gain - loss; + } + + // Return the interval time between this result and a follow-up trim. + int64_t interval_time(const TrimResult& followup_trim) const { + return followup_trim.time() - time(); + } + + void print_on(outputStream* st) const; + +}; + +class TrimNativeStepDownControl { + + static const int _trim_history_length = 4; + + // A FIFO of the last n trim results + class TrimHistory { + static const int _max = _trim_history_length; + + // Note: history may contain invalid results; for one, it is + // initialized with invalid results to keep iterating simple; + // also invalid results can happen if measuring rss goes wrong. + TrimResult _histo[_max]; + int _pos; // position of next write + + public: + + TrimHistory() : _pos(0) {} + + void add(const TrimResult& result) { + _histo[_pos] = result; + if (++_pos == _max) { + _pos = 0; + } + } + + template + void iterate_oldest_to_youngest(Functor f) const { + int idx = _pos; + do { + f(_histo + idx); + if (++idx == _max) { + idx = 0; + } + } while (idx != _pos); + } + }; + + TrimHistory _history; + + static bool is_bad_trim(const TrimResult& r, const TrimResult& r_followup); + +public: + + // Feed a new trim result into control. It will be added to the history, + // replacing the oldest result. + // Adding invalid results is allowed; they will be ignored by the heuristics. + void feed(const TrimResult& r); + + // Returns true if Heuristic recommends stepping down the trim interval + bool recommend_step_down() const; + +}; + +#endif // SHARE_GC_SHARED_TRIMNATIVESTEPDOWN_HPP diff --git a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp index 3cc50a23942bd..34d0b80df34d9 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp @@ -283,10 +283,14 @@ void ShenandoahControlThread::run_service() { // Print Metaspace change following GC (if logging is enabled). MetaspaceUtils::print_metaspace_change(meta_sizes); + // Expedite next native trim. This also trims if periodic trims are disabled. + TrimNative::schedule_trim(); + // GC is over, we are at idle now if (ShenandoahPacing) { heap->pacer()->setup_for_idle(); } + } else { // Allow allocators to know we have seen this much regions if (ShenandoahPacing && (allocs_seen > 0)) { @@ -425,7 +429,7 @@ void ShenandoahControlThread::stop_service() { } void ShenandoahControlThread::service_stw_full_cycle(GCCause::Cause cause) { - TrimNative::PauseThenTrimMark trim_pause_mark; + TrimNative::PauseMark trim_pause_mark; GCIdMark gc_id_mark; ShenandoahGCSession session(cause); diff --git a/src/hotspot/share/gc/z/zDriver.cpp b/src/hotspot/share/gc/z/zDriver.cpp index dd74def312e92..eaa89f75fb8fa 100644 --- a/src/hotspot/share/gc/z/zDriver.cpp +++ b/src/hotspot/share/gc/z/zDriver.cpp @@ -321,6 +321,7 @@ void ZDriver::concurrent_reset_relocation_set() { } void ZDriver::pause_verify() { + TrimNative::PauseMark trim_native_pause; if (VerifyBeforeGC || VerifyDuringGC || VerifyAfterGC) { // Full verification VM_Verify op; @@ -430,6 +431,9 @@ class ZDriverGCScope : public StackObj { // Signal that we have completed a visit to all live objects Universe::heap()->record_whole_heap_examined_timestamp(); + + // Expedite next native trim. This also trims if periodic trims are disabled. + TrimNative::schedule_trim(); } }; diff --git a/src/hotspot/share/runtime/os.hpp b/src/hotspot/share/runtime/os.hpp index 494ea58ada38b..e8927ac5b87d8 100644 --- a/src/hotspot/share/runtime/os.hpp +++ b/src/hotspot/share/runtime/os.hpp @@ -442,9 +442,6 @@ class os: AllStatic { // Does the platform support trimming the native heap? static bool can_trim_native_heap(); - // Does the platform recommend trimming? - static bool should_trim_native_heap(); - // Trim the C-heap. Optionally returns working set size change (RSS+Swap) in *rss_change. // Note: If trimming succeeded but no size change information could be obtained, // rss_change.after will contain SIZE_MAX upon return. From db0a8ba8fb917736d48dffb795c99dca838e5d8d Mon Sep 17 00:00:00 2001 From: tstuefe Date: Wed, 1 Mar 2023 10:09:53 +0100 Subject: [PATCH 21/27] wip --- .../jtreg/serviceability/dcmd/vm/TrimLibcHeapTest.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) 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 From d95d1f4cd06e5e9849701d20769548885b0cab3b Mon Sep 17 00:00:00 2001 From: tstuefe Date: Wed, 1 Mar 2023 21:25:58 +0100 Subject: [PATCH 22/27] wip --- src/hotspot/share/gc/shared/trimNative.cpp | 14 +- test/hotspot/jtreg/gc/TestTrimNative.java | 169 +++++++++++++-------- 2 files changed, 117 insertions(+), 66 deletions(-) diff --git a/src/hotspot/share/gc/shared/trimNative.cpp b/src/hotspot/share/gc/shared/trimNative.cpp index a1b9f748d5a99..d24d9dcd5cd1a 100644 --- a/src/hotspot/share/gc/shared/trimNative.cpp +++ b/src/hotspot/share/gc/shared/trimNative.cpp @@ -98,7 +98,8 @@ class NativeTrimmerThread : public ConcurrentGCThread { // 2 - Trimming happens outside of lock protection. GC threads can issue new commands // concurrently. - TrimResult result = execute_trim_and_log(); + const bool explicitly_scheduled = (ntt == 0); + TrimResult result = execute_trim_and_log(explicitly_scheduled); // 3 - Update _next_trim_time; but give concurrent setters preference. { @@ -148,7 +149,7 @@ class NativeTrimmerThread : public ConcurrentGCThread { // Execute the native trim, log results. // Return true if trim succeeded *and* we have valid size change data. - TrimResult execute_trim_and_log() const { + TrimResult execute_trim_and_log(bool explicitly_scheduled) const { assert(os::can_trim_native_heap(), "Unexpected"); const int64_t tnow = now(); os::size_change_t sc; @@ -159,7 +160,8 @@ class NativeTrimmerThread : public ConcurrentGCThread { 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", + log_info(gc, trim)("Trim native heap (%s): RSS+Swap: " PROPERFMT "->" PROPERFMT " (%c" PROPERFMT "), %1.3fms", + (explicitly_scheduled ? "explicit" : "periodic"), PROPERFMTARGS(sc.before), PROPERFMTARGS(sc.after), sign, PROPERFMTARGS(delta), trim_time.seconds() * 1000); return TrimResult(tnow, now() - tnow, sc.before, sc.after); @@ -219,9 +221,9 @@ class NativeTrimmerThread : public ConcurrentGCThread { ml.notify_all(); } if (_periodic_trim_enabled) { - log_debug(gc, trim)("NativeTrimmer unpause+trim"); + log_debug(gc, trim)("NativeTrimmer unpause + request explicit trim"); } else { - log_debug(gc, trim)("NativeTrimmer trim"); + log_debug(gc, trim)("NativeTrimmer request explicit trim"); } } @@ -247,7 +249,7 @@ void TrimNative::initialize() { } log_info(gc, trim)("Periodic trimming disabled."); } else { - log_info(gc, trim)("Periodic native trim enabled (interval: %u seconds, dynamic step-down %s", + log_info(gc, trim)("Periodic native trim enabled (interval: %u seconds, dynamic step-down %s)", TrimNativeHeapInterval, (TrimNativeHeapAdaptiveStepDown ? "enabled" : "disabled")); } g_trimmer_thread = new NativeTrimmerThread(); diff --git a/test/hotspot/jtreg/gc/TestTrimNative.java b/test/hotspot/jtreg/gc/TestTrimNative.java index 6a04c012ea638..62e5dee3be4e1 100644 --- a/test/hotspot/jtreg/gc/TestTrimNative.java +++ b/test/hotspot/jtreg/gc/TestTrimNative.java @@ -28,7 +28,7 @@ /* * All these tests test the trim-native feature for all GCs. * Trim-native is the ability to trim the C-heap as part of the GC cycle. - * This feature is controlled by -XX:+GCTrimNativeHeap (by default off). + * This feature is controlled by -XX:+TrimNativeHeap (by default off). * Trimming happens on full gc for all gcs. Shenandoah and G1 also support * concurrent trimming (Shenandoah supports this without any ties to java * heap occupancy). @@ -39,7 +39,7 @@ /* * @test id=fullgc-serial - * @summary Test that GCTrimNativeHeap works with Serial + * @summary Test that TrimNativeHeap works with Serial * @requires vm.gc.Serial * @requires (os.family=="linux") & !vm.musl * @modules java.base/jdk.internal.misc @@ -49,7 +49,7 @@ /* * @test id=fullgc-parallel - * @summary Test that GCTrimNativeHeap works with Parallel + * @summary Test that TrimNativeHeap works with Parallel * @requires vm.gc.Parallel * @requires (os.family=="linux") & !vm.musl * @modules java.base/jdk.internal.misc @@ -59,7 +59,7 @@ /* * @test id=fullgc-shenandoah - * @summary Test that GCTrimNativeHeap works with Shenandoah + * @summary Test that TrimNativeHeap works with Shenandoah * @requires vm.gc.Shenandoah * @requires (os.family=="linux") & !vm.musl * @modules java.base/jdk.internal.misc @@ -69,7 +69,7 @@ /* * @test id=fullgc-g1 - * @summary Test that GCTrimNativeHeap works with G1 + * @summary Test that TrimNativeHeap works with G1 * @requires vm.gc.G1 * @requires (os.family=="linux") & !vm.musl * @modules java.base/jdk.internal.misc @@ -79,7 +79,7 @@ /* * @test id=fullgc-z - * @summary Test that GCTrimNativeHeap works with Z + * @summary Test that TrimNativeHeap works with Z * @requires vm.gc.Z * @requires (os.family=="linux") & !vm.musl * @modules java.base/jdk.internal.misc @@ -93,7 +93,7 @@ /* * @test id=auto-parallel - * @summary Test that GCTrimNativeHeap works with Parallel + * @summary Test that TrimNativeHeap works with Parallel * @requires vm.gc.Parallel * @requires (os.family=="linux") & !vm.musl * @modules java.base/jdk.internal.misc @@ -103,7 +103,7 @@ /* * @test id=auto-shenandoah - * @summary Test that GCTrimNativeHeap works with Shenandoah + * @summary Test that TrimNativeHeap works with Shenandoah * @requires vm.gc.Shenandoah * @requires (os.family=="linux") & !vm.musl * @modules java.base/jdk.internal.misc @@ -113,7 +113,7 @@ /* * @test id=auto-g1 - * @summary Test that GCTrimNativeHeap works with G1 + * @summary Test that TrimNativeHeap works with G1 * @requires vm.gc.G1 * @requires (os.family=="linux") & !vm.musl * @modules java.base/jdk.internal.misc @@ -123,7 +123,7 @@ /* * @test id=auto-z - * @summary Test that GCTrimNativeHeap works with Z + * @summary Test that TrimNativeHeap works with Z * @requires vm.gc.Z * @requires (os.family=="linux") & !vm.musl * @modules java.base/jdk.internal.misc @@ -137,7 +137,7 @@ /* * @test id=auto-high-interval-parallel - * @summary Test that a high GCTrimNativeHeapInterval effectively disables automatic trimming + * @summary Test that a high TrimNativeHeapInterval effectively disables automatic trimming * @requires vm.gc.Parallel * @requires (os.family=="linux") & !vm.musl * @modules java.base/jdk.internal.misc @@ -147,7 +147,7 @@ /* * @test id=auto-high-interval-g1 - * @summary Test that a high GCTrimNativeHeapInterval effectively disables automatic trimming + * @summary Test that a high TrimNativeHeapInterval effectively disables automatic trimming * @requires vm.gc.G1 * @requires (os.family=="linux") & !vm.musl * @modules java.base/jdk.internal.misc @@ -157,7 +157,7 @@ /* * @test id=auto-high-interval-shenandoah - * @summary Test that a high GCTrimNativeHeapInterval effectively disables automatic trimming + * @summary Test that a high TrimNativeHeapInterval effectively disables automatic trimming * @requires vm.gc.Shenandoah * @requires (os.family=="linux") & !vm.musl * @modules java.base/jdk.internal.misc @@ -167,7 +167,7 @@ /* * @test id=auto-high-interval-z - * @summary Test that a high GCTrimNativeHeapInterval effectively disables automatic trimming + * @summary Test that a high TrimNativeHeapInterval effectively disables automatic trimming * @requires vm.gc.Z * @requires (os.family=="linux") & !vm.musl * @modules java.base/jdk.internal.misc @@ -181,7 +181,7 @@ /* * @test id=auto-zero-interval-parallel - * @summary Test that a GCTrimNativeHeapInterval=0 disables periodic trimming + * @summary Test that a TrimNativeHeapInterval=0 disables periodic trimming * @requires vm.gc.Parallel * @requires (os.family=="linux") & !vm.musl * @modules java.base/jdk.internal.misc @@ -191,7 +191,7 @@ /* * @test id=auto-zero-interval-g1 - * @summary Test that a GCTrimNativeHeapInterval=0 disables periodic trimming + * @summary Test that a TrimNativeHeapInterval=0 disables periodic trimming * @requires vm.gc.G1 * @requires (os.family=="linux") & !vm.musl * @modules java.base/jdk.internal.misc @@ -201,7 +201,7 @@ /* * @test id=auto-zero-interval-shenandoah - * @summary Test that a GCTrimNativeHeapInterval=0 disables periodic trimming + * @summary Test that a TrimNativeHeapInterval=0 disables periodic trimming * @requires vm.gc.Shenandoah * @requires (os.family=="linux") & !vm.musl * @modules java.base/jdk.internal.misc @@ -211,7 +211,7 @@ /* * @test id=auto-zero-interval-z - * @summary Test that a GCTrimNativeHeapInterval=0 disables periodic trimming + * @summary Test that a TrimNativeHeapInterval=0 disables periodic trimming * @requires vm.gc.Z * @requires (os.family=="linux") & !vm.musl * @modules java.base/jdk.internal.misc @@ -288,7 +288,7 @@ String getSwitchName() { boolean isShenandoah() { return this == GC.shenandoah; } } - static private final OutputAnalyzer runTestWithOptions(String[] extraOptions, String[] testArgs) throws IOException { + private static OutputAnalyzer runTestWithOptions(String[] extraOptions, String[] testArgs) throws IOException { List allOptions = new ArrayList(); allOptions.add("-XX:+UnlockExperimentalVMOptions"); @@ -309,45 +309,89 @@ static private final OutputAnalyzer runTestWithOptions(String[] extraOptions, St } + private static void checkExpectedLogMessages(OutputAnalyzer output, boolean expectEnabled, + int expectedInterval, boolean expectAutoStepDown) { + if (expectEnabled) { + output.shouldContain("Native trim enabled"); + if (expectedInterval > 0) { + output.shouldContain("Periodic native trim enabled (interval: " + expectedInterval + + " seconds, dynamic step-down " + (expectAutoStepDown ? "enabled" : "disabled") + ")"); + output.shouldContain("NativeTrimmer start"); + output.shouldContain("NativeTrimmer stop"); + } else { + output.shouldContain("Periodic native trim disabled"); + } + + } else { + output.shouldContain("Native trim disabled"); + } + } + /** - * Given JVM output, look for a log line that describes a successful negative trim in the megabyte range + * Given JVM output, look for a log line that describes a successful negative trim. The total amount of trims + * should be matching about what the test program allocated. * like this: * "[2.053s][debug][gc,trim] Trim native heap (retain size: 5120K): RSS+Swap: 271M->223M (-49112K), 2.834ms" * (Note: we use the "properXXX" print routines, therefore units can differ) * Check that the sum of all trim log lines comes to a total RSS reduction in the MB range * @param output - * @param minExpected min number of trim lines expected in UL log - * @param maxExpected max number of trim lines expected in UL log + * @param minPeriodicTrimsExpected min number of periodic trim lines expected in UL log + * @param maxPeriodicTrimsExpected min number of periodic trim lines expected in UL log + * @param minExplicitTrimsExpected min number of explicit trim lines expected in UL log + * @param maxExplicitTrimsExpected min number of explicit trim lines expected in UL log */ - private final static void parseOutputAndLookForNegativeTrim(OutputAnalyzer output, int minExpected, int maxExpected) { + private static void parseOutputAndLookForNegativeTrim(OutputAnalyzer output, int minPeriodicTrimsExpected, + int maxPeriodicTrimsExpected, int minExplicitTrimsExpected, + int maxExplicitTrimsExpected) { output.reportDiagnosticSummary(); List lines = output.asLines(); - Pattern pat = Pattern.compile(".*\\[gc,trim\\] Trim native heap.*RSS\\+Swap: (\\d+)([KMB])->(\\d+)([KMB]).*"); - int numTrimsFound = 0; + Pattern pat = Pattern.compile(".*\\[gc,trim\\] Trim native heap (explicit|periodic)" + + ".*RSS\\+Swap: (\\d+)([BKMG])->(\\d+)([BKMG]).*"); + int numExplicitTrimsFound = 0; + int numPeriodicTrimsFound = 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); + String explicitOrPeriodic = mat.group(1); + boolean periodic = false; + switch (explicitOrPeriodic) { + case "explicit": periodic = false; break; + case "periodic": periodic = true; break; + default: throw new RuntimeException("Invalid line \"" + line + "\""); + } + long rss1 = Long.parseLong(mat.group(2)) * Unit.valueOf(mat.group(3)).size; + long rss2 = Long.parseLong(mat.group(4)) * Unit.valueOf(mat.group(5)).size; + System.out.println("Parsed Trim Line. Periodic: " + periodic + ", rss1: " + rss1 + " rss2: " + rss2); if (rss1 > rss2) { rssReductionTotal += (rss1 - rss2); } - numTrimsFound ++; + if (periodic) { + numPeriodicTrimsFound ++; + } else { + numExplicitTrimsFound ++; + } + } + if (numPeriodicTrimsFound > maxPeriodicTrimsExpected) { + throw new RuntimeException("Abnormal high number of periodic trim attempts found (more than " + maxPeriodicTrimsExpected + + "). Does the interval setting not work?"); } - if (numTrimsFound > maxExpected) { - throw new RuntimeException("Abnormal high number of trim attempts found (more than " + maxExpected + + if (numExplicitTrimsFound > maxExplicitTrimsExpected) { + throw new RuntimeException("Abnormal high number of explicit trim attempts found (more than " + maxExplicitTrimsExpected + "). Does the interval setting not work?"); } } - if (numTrimsFound < minExpected) { - throw new RuntimeException("We found fewer trim lines in UL log than expected (expected " + minExpected + - ", found " + numTrimsFound + "."); + if (numPeriodicTrimsFound < minPeriodicTrimsExpected) { + throw new RuntimeException("We found fewer trim lines in UL log than expected (expected " + numPeriodicTrimsFound + + ", found " + minPeriodicTrimsExpected + "."); } - // This is very fuzzy. We malloced X, free'd X, trimmed, measured the combined effect of all reductions. - // This does not take into effect mallocs or frees that may happen concurrently. But we expect to see *some* - // reduction somewhere. Test with a fudge factor. + if (numExplicitTrimsFound < minExplicitTrimsExpected) { + throw new RuntimeException("We found fewer trim lines in UL log than expected (expected " + numExplicitTrimsFound + + ", found " + minExplicitTrimsExpected + "."); + } + // 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.7f; // 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 @@ -362,40 +406,45 @@ private final static void parseOutputAndLookForNegativeTrim(OutputAnalyzer outpu } } - // Test that we trim on full gc + final static int DEFAULT_TRIM_INTERVAL = 30; + final static boolean DEFAULT_AUTOSTEP = false; + + // Test explicit trim on full gc static private final void testWithFullGC(GC gc) throws IOException { System.out.println("testWithFullGC"); int sleeptime_secs = 2; OutputAnalyzer output = runTestWithOptions ( - new String[] { gc.getSwitchName(), "-XX:+GCTrimNativeHeap" }, + new String[] { gc.getSwitchName(), "-XX:+TrimNativeHeap" }, new String[] { "true" /* full gc */, String.valueOf(sleeptime_secs * 1000) /* ms after peak */ } ); - output.shouldContain("Native trim enabled."); - output.shouldContain("Periodic native trim enabled (interval: 30 seconds, step-down-interval: 120 seconds)."); - output.shouldContain("NativeTrimmer start"); + + checkExpectedLogMessages(output, true, DEFAULT_TRIM_INTERVAL, DEFAULT_AUTOSTEP); + + // We expect to see at least one pause (because we pause during STW gc cycles) followed by an reqested immediate trim output.shouldContain("NativeTrimmer pause"); - output.shouldContain("NativeTrimmer unpause+trim"); - output.shouldContain("NativeTrimmer stop"); - // With default interval time of 30 seconds, auto trimming should never kick in, so the only - // log line we expect to see is the one from the full-gc induced trim. - parseOutputAndLookForNegativeTrim(output, 1, 1); + output.shouldContain("NativeTrimmer unpause + request explicit trim"); + + parseOutputAndLookForNegativeTrim(output, true, 1, 1); } - // Test that GCTrimNativeHeap=1 causes a trim-native automatically, without GC (for now, shenandoah only) + // Test periodic trim with very short trim interval. We explicitly don't do a GC to not get an explicite trim + // "stealing" our gains. static private final void testAuto(GC gc) throws IOException { System.out.println("testAuto"); long t1 = System.currentTimeMillis(); int sleeptime_secs = 4; OutputAnalyzer output = runTestWithOptions ( - new String[] { gc.getSwitchName(), "-XX:+GCTrimNativeHeap", "-XX:GCTrimNativeHeapInterval=1" }, - new String[] { "false" /* full gc */, String.valueOf(sleeptime_secs * 1000) /* ms after peak */ } + new String[] { gc.getSwitchName(), "-XX:+TrimNativeHeap", "-XX:TrimNativeHeapInterval=1" }, + new String[] { "false" /* no full gc */, String.valueOf(sleeptime_secs * 1000) /* ms after peak */ } ); long t2 = System.currentTimeMillis(); int runtime_s = (int)((t2 - t1) / 1000); - output.shouldContain("Native trim enabled."); - output.shouldContain("Periodic native trim enabled (interval: 1 seconds, step-down-interval: 4 seconds)."); - // With an interval time of 1 second and a runtime of 6..x seconds we expect to see x log lines (+- fudge factor). - parseOutputAndLookForNegativeTrim(output, runtime_s - 4, runtime_s); + + checkExpectedLogMessages(output, true, 1, DEFAULT_AUTOSTEP); + + // With an interval time of 1 second and a runtime of 6..x seconds we expect to see x periodic trim + // log lines (+- fudge factor). + parseOutputAndLookForNegativeTrim(output, false,runtime_s - 4, runtime_s); } // Test that trim-native correctly honors interval @@ -403,7 +452,7 @@ static private final void testAutoWithHighInterval(GC gc) throws IOException { // We pass a high interval than the expected test runtime. We expect no trims. System.out.println("testAutoWithHighInterval"); OutputAnalyzer output = runTestWithOptions ( - new String[] { gc.getSwitchName(), "-XX:+GCTrimNativeHeap", "-XX:GCTrimNativeHeapInterval=12" }, + new String[] { gc.getSwitchName(), "-XX:+TrimNativeHeap", "-XX:TrimNativeHeapInterval=12" }, new String[] { "false" /* full gc */, "6000" /* ms after peak */ } ); output.shouldContain("Native trim enabled."); @@ -414,7 +463,7 @@ static private final void testAutoWithHighInterval(GC gc) throws IOException { static private final void testAutoWithZeroInterval(GC gc) throws IOException { System.out.println("testAutoWithZeroInterval"); OutputAnalyzer output = runTestWithOptions ( - new String[] { gc.getSwitchName(), "-XX:+GCTrimNativeHeap", "-XX:GCTrimNativeHeapInterval=0" }, + new String[] { gc.getSwitchName(), "-XX:+TrimNativeHeap", "-XX:TrimNativeHeapInterval=0" }, new String[] { "false" /* full gc */, "4000" /* ms after peak */ } ); output.shouldContain("Native trim enabled."); @@ -427,20 +476,20 @@ static private final void testOffOnNonCompliantPlatforms() throws IOException { // Logic is shared, so no need to test with every GC. Just use the default GC. System.out.println("testOffOnNonCompliantPlatforms"); OutputAnalyzer output = runTestWithOptions ( - new String[] { "-XX:+GCTrimNativeHeap" }, + new String[] { "-XX:+TrimNativeHeap" }, new String[] { "true" /* full gc */, "2000" /* ms after peak */ } ); - output.shouldContain("GCTrimNativeHeap disabled"); + output.shouldContain("TrimNativeHeap disabled"); output.shouldNotContain("Native trim enabled."); output.shouldNotContain("Trim native heap"); } - // Test that GCTrimNativeHeap=0 switches trim-native off + // Test that TrimNativeHeap=0 switches trim-native off static private final void testOffExplicit() throws IOException { // Logic is shared, so no need to test with every GC. Just use the default GC. System.out.println("testOffExplicit"); OutputAnalyzer output = runTestWithOptions ( - new String[] { "-XX:-GCTrimNativeHeap" }, + new String[] { "-XX:-TrimNativeHeap" }, new String[] { "true" /* full gc */, "2000" /* ms after peak */ } ); output.shouldNotContain("Native trim enabled."); From 5d41312e081dc5655673efd6271a4b36cec1e87b Mon Sep 17 00:00:00 2001 From: tstuefe Date: Thu, 2 Mar 2023 17:33:57 +0100 Subject: [PATCH 23/27] wip --- src/hotspot/share/gc/shared/gc_globals.hpp | 2 +- src/hotspot/share/gc/shared/trimNative.cpp | 2 +- test/hotspot/jtreg/gc/TestTrimNative.java | 236 ++++++++++----------- 3 files changed, 109 insertions(+), 131 deletions(-) diff --git a/src/hotspot/share/gc/shared/gc_globals.hpp b/src/hotspot/share/gc/shared/gc_globals.hpp index 7b965fb84eb3b..e5b9604de0df0 100644 --- a/src/hotspot/share/gc/shared/gc_globals.hpp +++ b/src/hotspot/share/gc/shared/gc_globals.hpp @@ -696,7 +696,7 @@ "GC will attempt to trim the native heap periodically and " \ "at the end of a GC cycle.") \ \ - product(uint, TrimNativeHeapInterval, 5, EXPERIMENTAL, \ + product(uint, TrimNativeHeapInterval, 30, EXPERIMENTAL, \ "If TrimNativeHeap is enabled: interval, in seconds, in which " \ "the GC will attempt to trim the native heap. If " \ "TrimNativeHeapAdaptiveStepDown is enabled, the JVM may lower " \ diff --git a/src/hotspot/share/gc/shared/trimNative.cpp b/src/hotspot/share/gc/shared/trimNative.cpp index d24d9dcd5cd1a..740ff3edba7e8 100644 --- a/src/hotspot/share/gc/shared/trimNative.cpp +++ b/src/hotspot/share/gc/shared/trimNative.cpp @@ -247,7 +247,7 @@ void TrimNative::initialize() { if (TrimNativeHeapAdaptiveStepDown) { FLAG_SET_ERGO(TrimNativeHeapAdaptiveStepDown, false); } - log_info(gc, trim)("Periodic trimming disabled."); + log_info(gc, trim)("Periodic native trim disabled."); } else { log_info(gc, trim)("Periodic native trim enabled (interval: %u seconds, dynamic step-down %s)", TrimNativeHeapInterval, (TrimNativeHeapAdaptiveStepDown ? "enabled" : "disabled")); diff --git a/test/hotspot/jtreg/gc/TestTrimNative.java b/test/hotspot/jtreg/gc/TestTrimNative.java index 62e5dee3be4e1..77bb98261d296 100644 --- a/test/hotspot/jtreg/gc/TestTrimNative.java +++ b/test/hotspot/jtreg/gc/TestTrimNative.java @@ -38,214 +38,186 @@ //// full gc tests ///// /* - * @test id=fullgc-serial + * @test id=testExplicitTrimOnFullGC-serial * @summary Test that TrimNativeHeap works with 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-fullgc serial + * @run driver gc.TestTrimNative testExplicitTrimOnFullGC serial */ /* - * @test id=fullgc-parallel + * @test id=testExplicitTrimOnFullGC-parallel * @summary Test that TrimNativeHeap works with Parallel * @requires vm.gc.Parallel * @requires (os.family=="linux") & !vm.musl * @modules java.base/jdk.internal.misc * @library /test/lib - * @run driver gc.TestTrimNative test-fullgc parallel + * @run driver gc.TestTrimNative testExplicitTrimOnFullGC parallel */ /* - * @test id=fullgc-shenandoah + * @test id=testExplicitTrimOnFullGC-shenandoah * @summary Test that TrimNativeHeap works with Shenandoah * @requires vm.gc.Shenandoah * @requires (os.family=="linux") & !vm.musl * @modules java.base/jdk.internal.misc * @library /test/lib - * @run driver gc.TestTrimNative test-fullgc shenandoah + * @run driver gc.TestTrimNative testExplicitTrimOnFullGC shenandoah */ /* - * @test id=fullgc-g1 + * @test id=testExplicitTrimOnFullGC-g1 * @summary Test that TrimNativeHeap works with G1 * @requires vm.gc.G1 * @requires (os.family=="linux") & !vm.musl * @modules java.base/jdk.internal.misc * @library /test/lib - * @run driver gc.TestTrimNative test-fullgc g1 + * @run driver gc.TestTrimNative testExplicitTrimOnFullGC g1 */ /* - * @test id=fullgc-z + * @test id=testExplicitTrimOnFullGC-z * @summary Test that TrimNativeHeap works with Z * @requires vm.gc.Z * @requires (os.family=="linux") & !vm.musl * @modules java.base/jdk.internal.misc * @library /test/lib - * @run driver gc.TestTrimNative test-fullgc z + * @run driver gc.TestTrimNative testExplicitTrimOnFullGC z */ //// auto mode tests ///// -// Note: not serial, since it does not do periodic trimming, only trimming on full gc +/* + * @test id=testPeriodicTrim-serial + * @summary Test that TrimNativeHeap works with Parallel + * @requires vm.gc.Parallel + * @requires (os.family=="linux") & !vm.musl + * @modules java.base/jdk.internal.misc + * @library /test/lib + * @run driver gc.TestTrimNative testPeriodicTrim serial + */ /* - * @test id=auto-parallel + * @test id=testPeriodicTrim-parallel * @summary Test that TrimNativeHeap works with Parallel * @requires vm.gc.Parallel * @requires (os.family=="linux") & !vm.musl * @modules java.base/jdk.internal.misc * @library /test/lib - * @run driver gc.TestTrimNative test-auto parallel + * @run driver gc.TestTrimNative testPeriodicTrim parallel */ /* - * @test id=auto-shenandoah + * @test id=testPeriodicTrim-shenandoah * @summary Test that TrimNativeHeap works with Shenandoah * @requires vm.gc.Shenandoah * @requires (os.family=="linux") & !vm.musl * @modules java.base/jdk.internal.misc * @library /test/lib - * @run driver gc.TestTrimNative test-auto shenandoah + * @run driver gc.TestTrimNative testPeriodicTrim shenandoah */ /* - * @test id=auto-g1 + * @test id=testPeriodicTrim-g1 * @summary Test that TrimNativeHeap works with G1 * @requires vm.gc.G1 * @requires (os.family=="linux") & !vm.musl * @modules java.base/jdk.internal.misc * @library /test/lib - * @run driver gc.TestTrimNative test-auto g1 + * @run driver gc.TestTrimNative testPeriodicTrim g1 */ /* - * @test id=auto-z + * @test id=testPeriodicTrim-z * @summary Test that TrimNativeHeap works with Z * @requires vm.gc.Z * @requires (os.family=="linux") & !vm.musl * @modules java.base/jdk.internal.misc * @library /test/lib - * @run driver gc.TestTrimNative test-auto z + * @run driver gc.TestTrimNative testPeriodicTrim z */ -//// test-auto-high-interval interval test ///// - -// Note: not serial, since it does not do periodic trimming, only trimming on full gc +//// testPeriodicTrimDisabled test ///// /* - * @test id=auto-high-interval-parallel - * @summary Test that a high TrimNativeHeapInterval effectively disables automatic trimming + * @test id=testPeriodicTrimDisabled-serial + * @summary Test that TrimNativeHeap works with Parallel * @requires vm.gc.Parallel * @requires (os.family=="linux") & !vm.musl * @modules java.base/jdk.internal.misc * @library /test/lib - * @run driver gc.TestTrimNative test-auto-high-interval parallel + * @run driver gc.TestTrimNative testPeriodicTrimDisabled serial */ /* - * @test id=auto-high-interval-g1 - * @summary Test that a high TrimNativeHeapInterval effectively disables automatic trimming - * @requires vm.gc.G1 + * @test id=testPeriodicTrimDisabled-parallel + * @summary Test that TrimNativeHeap works with Parallel + * @requires vm.gc.Parallel * @requires (os.family=="linux") & !vm.musl * @modules java.base/jdk.internal.misc * @library /test/lib - * @run driver gc.TestTrimNative test-auto-high-interval g1 + * @run driver gc.TestTrimNative testPeriodicTrimDisabled parallel */ /* - * @test id=auto-high-interval-shenandoah - * @summary Test that a high TrimNativeHeapInterval effectively disables automatic trimming + * @test id=testPeriodicTrimDisabled-shenandoah + * @summary Test that TrimNativeHeap works with Shenandoah * @requires vm.gc.Shenandoah * @requires (os.family=="linux") & !vm.musl * @modules java.base/jdk.internal.misc * @library /test/lib - * @run driver gc.TestTrimNative test-auto-high-interval shenandoah + * @run driver gc.TestTrimNative testPeriodicTrimDisabled shenandoah */ /* - * @test id=auto-high-interval-z - * @summary Test that a high TrimNativeHeapInterval effectively disables automatic trimming - * @requires vm.gc.Z - * @requires (os.family=="linux") & !vm.musl - * @modules java.base/jdk.internal.misc - * @library /test/lib - * @run driver gc.TestTrimNative test-auto-high-interval z - */ - -//// test-auto-interval-0 test ///// - -// Note: not serial, since it does not do periodic trimming, only trimming on full gc - -/* - * @test id=auto-zero-interval-parallel - * @summary Test that a TrimNativeHeapInterval=0 disables periodic trimming - * @requires vm.gc.Parallel - * @requires (os.family=="linux") & !vm.musl - * @modules java.base/jdk.internal.misc - * @library /test/lib - * @run driver gc.TestTrimNative test-auto-zero-interval parallel - */ - -/* - * @test id=auto-zero-interval-g1 - * @summary Test that a TrimNativeHeapInterval=0 disables periodic trimming + * @test id=testPeriodicTrimDisabled-g1 + * @summary Test that TrimNativeHeap works with G1 * @requires vm.gc.G1 * @requires (os.family=="linux") & !vm.musl * @modules java.base/jdk.internal.misc * @library /test/lib - * @run driver gc.TestTrimNative test-auto-zero-interval g1 - */ - -/* - * @test id=auto-zero-interval-shenandoah - * @summary Test that a TrimNativeHeapInterval=0 disables periodic trimming - * @requires vm.gc.Shenandoah - * @requires (os.family=="linux") & !vm.musl - * @modules java.base/jdk.internal.misc - * @library /test/lib - * @run driver gc.TestTrimNative test-auto-zero-interval shenandoah + * @run driver gc.TestTrimNative testPeriodicTrimDisabled g1 */ /* - * @test id=auto-zero-interval-z - * @summary Test that a TrimNativeHeapInterval=0 disables periodic trimming + * @test id=testPeriodicTrimDisabled-z + * @summary Test that TrimNativeHeap works with Z * @requires vm.gc.Z * @requires (os.family=="linux") & !vm.musl * @modules java.base/jdk.internal.misc * @library /test/lib - * @run driver gc.TestTrimNative test-auto-zero-interval z + * @run driver gc.TestTrimNative testPeriodicTrimDisabled z */ // Other tests /* - * @test id=off-explicit + * @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 test-off-explicit + * @run driver gc.TestTrimNative testOffByDefault */ /* - * @test id=off-by-default + * @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 test-off-by-default + * @run driver gc.TestTrimNative testOffExplicit */ /* - * @test id=off-on-other-platforms + * @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 test-off-on-other-platforms + * @run driver gc.TestTrimNative testOffOnNonCompliantPlatforms */ import jdk.internal.misc.Unsafe; @@ -254,6 +226,7 @@ import jdk.test.lib.process.ProcessTools; import java.io.IOException; +import java.rmi.RemoteException; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -323,7 +296,7 @@ private static void checkExpectedLogMessages(OutputAnalyzer output, boolean expe } } else { - output.shouldContain("Native trim disabled"); + output.shouldNotContain("Native trim"); } } @@ -345,7 +318,7 @@ private static void parseOutputAndLookForNegativeTrim(OutputAnalyzer output, int int maxExplicitTrimsExpected) { output.reportDiagnosticSummary(); List lines = output.asLines(); - Pattern pat = Pattern.compile(".*\\[gc,trim\\] Trim native heap (explicit|periodic)" + + Pattern pat = Pattern.compile(".*\\[gc,trim\\] Trim native heap \\((explicit|periodic)\\)" + ".*RSS\\+Swap: (\\d+)([BKMG])->(\\d+)([BKMG]).*"); int numExplicitTrimsFound = 0; int numPeriodicTrimsFound = 0; @@ -382,12 +355,12 @@ private static void parseOutputAndLookForNegativeTrim(OutputAnalyzer output, int } } if (numPeriodicTrimsFound < minPeriodicTrimsExpected) { - throw new RuntimeException("We found fewer trim lines in UL log than expected (expected " + numPeriodicTrimsFound + - ", found " + minPeriodicTrimsExpected + "."); + throw new RuntimeException("We found fewer (periodic) trim lines in UL log than expected (expected at least " + minPeriodicTrimsExpected + + ", found " + numPeriodicTrimsFound + ")."); } if (numExplicitTrimsFound < minExplicitTrimsExpected) { - throw new RuntimeException("We found fewer trim lines in UL log than expected (expected " + numExplicitTrimsFound + - ", found " + minExplicitTrimsExpected + "."); + throw new RuntimeException("We found fewer (explicit) trim lines in UL log than expected (expected at least " + minExplicitTrimsExpected + + ", found " + numExplicitTrimsFound + ")."); } // 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 @@ -410,8 +383,8 @@ private static void parseOutputAndLookForNegativeTrim(OutputAnalyzer output, int final static boolean DEFAULT_AUTOSTEP = false; // Test explicit trim on full gc - static private final void testWithFullGC(GC gc) throws IOException { - System.out.println("testWithFullGC"); + static private final void testExplicitTrimOnFullGC(GC gc) throws IOException { + System.out.println("testExplicitTrimOnFullGC"); int sleeptime_secs = 2; OutputAnalyzer output = runTestWithOptions ( new String[] { gc.getSwitchName(), "-XX:+TrimNativeHeap" }, @@ -424,13 +397,23 @@ static private final void testWithFullGC(GC gc) throws IOException { output.shouldContain("NativeTrimmer pause"); output.shouldContain("NativeTrimmer unpause + request explicit trim"); - parseOutputAndLookForNegativeTrim(output, true, 1, 1); + int minPeriodicTrimsExpected = 0; + int maxPeriodicTrimsExpected = 10; + int minExplicitTrimsExpected = 1; + int maxExplicitTrimsExpected = 10; + + parseOutputAndLookForNegativeTrim(output, + 0, /* minPeriodicTrimsExpected */ + 10, /* maxPeriodicTrimsExpected */ + 1, /* minExplicitTrimsExpected */ + 10 /* maxExplicitTrimsExpected */ + ); } // Test periodic trim with very short trim interval. We explicitly don't do a GC to not get an explicite trim // "stealing" our gains. - static private final void testAuto(GC gc) throws IOException { - System.out.println("testAuto"); + static private final void testPeriodicTrim(GC gc) throws IOException { + System.out.println("testPeriodicTrim"); long t1 = System.currentTimeMillis(); int sleeptime_secs = 4; OutputAnalyzer output = runTestWithOptions ( @@ -444,47 +427,47 @@ static private final void testAuto(GC gc) throws IOException { // With an interval time of 1 second and a runtime of 6..x seconds we expect to see x periodic trim // log lines (+- fudge factor). - parseOutputAndLookForNegativeTrim(output, false,runtime_s - 4, runtime_s); - } - - // Test that trim-native correctly honors interval - static private final void testAutoWithHighInterval(GC gc) throws IOException { - // We pass a high interval than the expected test runtime. We expect no trims. - System.out.println("testAutoWithHighInterval"); - OutputAnalyzer output = runTestWithOptions ( - new String[] { gc.getSwitchName(), "-XX:+TrimNativeHeap", "-XX:TrimNativeHeapInterval=12" }, - new String[] { "false" /* full gc */, "6000" /* ms after peak */ } + parseOutputAndLookForNegativeTrim(output, + runtime_s - 4, /* minPeriodicTrimsExpected */ + runtime_s, /* maxPeriodicTrimsExpected */ + 0, /* minExplicitTrimsExpected */ + 10 /* maxExplicitTrimsExpected */ ); - output.shouldContain("Native trim enabled."); - output.shouldContain("Periodic native trim enabled (interval: 12 seconds, step-down-interval: 48 seconds)."); - output.shouldNotContain("Trim native heap"); + } - static private final void testAutoWithZeroInterval(GC gc) throws IOException { - System.out.println("testAutoWithZeroInterval"); + static private final void testPeriodicTrimDisabled(GC gc) throws IOException { + System.out.println("testPeriodicTrimDisabled"); OutputAnalyzer output = runTestWithOptions ( new String[] { gc.getSwitchName(), "-XX:+TrimNativeHeap", "-XX:TrimNativeHeapInterval=0" }, - new String[] { "false" /* full gc */, "4000" /* ms after peak */ } + new String[] { "true" /* full gc */, "4000" /* ms after peak */ } + ); + checkExpectedLogMessages(output, true, 0, DEFAULT_AUTOSTEP); + + // We expect only explicit trims, no periodic trims + parseOutputAndLookForNegativeTrim(output, + 0, /* minPeriodicTrimsExpected */ + 0, /* maxPeriodicTrimsExpected */ + 1, /* minExplicitTrimsExpected */ + 10 /* maxExplicitTrimsExpected */ ); - output.shouldContain("Native trim enabled."); - output.shouldContain("Periodic native trim disabled"); - output.shouldNotContain("Trim native heap"); } // Test that trim-native gets disabled on platforms that don't support it. static private final void testOffOnNonCompliantPlatforms() throws IOException { + if (Platform.isLinux() && !Platform.isMusl()) { + throw new RemoteException("Don't call me for Linux glibc"); + } // Logic is shared, so no need to test with every GC. Just use the default GC. System.out.println("testOffOnNonCompliantPlatforms"); OutputAnalyzer output = runTestWithOptions ( new String[] { "-XX:+TrimNativeHeap" }, new String[] { "true" /* full gc */, "2000" /* ms after peak */ } ); - output.shouldContain("TrimNativeHeap disabled"); - output.shouldNotContain("Native trim enabled."); - output.shouldNotContain("Trim native heap"); + checkExpectedLogMessages(output, false, 0, false); } - // Test that TrimNativeHeap=0 switches trim-native off + // Test trim native is disabled if explicitly switched off static private final void testOffExplicit() throws IOException { // Logic is shared, so no need to test with every GC. Just use the default GC. System.out.println("testOffExplicit"); @@ -492,8 +475,7 @@ static private final void testOffExplicit() throws IOException { new String[] { "-XX:-TrimNativeHeap" }, new String[] { "true" /* full gc */, "2000" /* ms after peak */ } ); - output.shouldNotContain("Native trim enabled."); - output.shouldNotContain("Trim native heap"); + checkExpectedLogMessages(output, false, 0, false); } // Test that trim-native is disabled by default @@ -504,8 +486,7 @@ static private final void testOffByDefault() throws IOException { new String[] { }, new String[] { "true" /* full gc */, "2000" /* ms after peak */ } ); - output.shouldNotContain("Native trim enabled."); - output.shouldNotContain("Trim native heap"); + checkExpectedLogMessages(output, false, 0, false); } public static void main(String[] args) throws Exception { @@ -541,24 +522,21 @@ public static void main(String[] args) throws Exception { return; - } else if (args[0].equals("test-fullgc")) { + } else if (args[0].equals("testExplicitTrimOnFullGC")) { final GC gc = GC.valueOf(args[1]); - testWithFullGC(gc); - } else if (args[0].equals("test-auto")) { + testExplicitTrimOnFullGC(gc); + } else if (args[0].equals("testPeriodicTrim")) { final GC gc = GC.valueOf(args[1]); - testAuto(gc); - } else if (args[0].equals("test-auto-high-interval")) { + testPeriodicTrim(gc); + } else if (args[0].equals("testPeriodicTrimDisabled")) { final GC gc = GC.valueOf(args[1]); - testAutoWithHighInterval(gc); - } else if (args[0].equals("test-auto-zero-interval")) { - final GC gc = GC.valueOf(args[1]); - testAutoWithZeroInterval(gc); - } else if (args[0].equals("test-off-explicit")) { + testPeriodicTrimDisabled(gc); + } else if (args[0].equals("testOffOnNonCompliantPlatforms")) { + testOffOnNonCompliantPlatforms(); + } else if (args[0].equals("testOffExplicit")) { testOffExplicit(); - } else if (args[0].equals("test-off-by-default")) { + } else if (args[0].equals("testOffByDefault")) { testOffByDefault(); - } else if (args[0].equals("test-off-on-other-platforms")) { - testOffOnNonCompliantPlatforms(); } else { throw new RuntimeException("Invalid test " + args[0]); } From aa9b2a782e21bac7170660eca1c46a3de11341ed Mon Sep 17 00:00:00 2001 From: tstuefe Date: Tue, 4 Jul 2023 10:15:18 +0200 Subject: [PATCH 24/27] Remove adaptive stepdown coding --- src/hotspot/share/gc/shared/gc_globals.hpp | 8 +--- src/hotspot/share/gc/shared/trimNative.cpp | 50 +--------------------- test/hotspot/jtreg/gc/TestTrimNative.java | 28 ++++++------ 3 files changed, 16 insertions(+), 70 deletions(-) diff --git a/src/hotspot/share/gc/shared/gc_globals.hpp b/src/hotspot/share/gc/shared/gc_globals.hpp index b90a5267799e4..627863d211311 100644 --- a/src/hotspot/share/gc/shared/gc_globals.hpp +++ b/src/hotspot/share/gc/shared/gc_globals.hpp @@ -698,17 +698,11 @@ \ product(uint, TrimNativeHeapInterval, 30, EXPERIMENTAL, \ "If TrimNativeHeap is enabled: interval, in seconds, in which " \ - "the GC will attempt to trim the native heap. If " \ - "TrimNativeHeapAdaptiveStepDown is enabled, the JVM may lower " \ - "the frequency of trimming." \ + "the GC will attempt to trim the native heap. " \ "A value of 0 disables periodic trimming altogether while still " \ "trimming after a GC cycle.") \ range(0, (24 * 60 * 60)) \ \ - product(bool, TrimNativeHeapAdaptiveStepDown, false, EXPERIMENTAL, \ - "If TrimNativeHeap and periodic trimming are enabled: if true, " \ - "the GC may step down trimming frequency if needed. ") \ - \ // end of GC_FLAGS DECLARE_FLAGS(GC_FLAGS) diff --git a/src/hotspot/share/gc/shared/trimNative.cpp b/src/hotspot/share/gc/shared/trimNative.cpp index 740ff3edba7e8..fc67f80eb2100 100644 --- a/src/hotspot/share/gc/shared/trimNative.cpp +++ b/src/hotspot/share/gc/shared/trimNative.cpp @@ -43,27 +43,10 @@ class NativeTrimmerThread : public ConcurrentGCThread { // Periodic trimming state const int64_t _interval_ms; const bool _periodic_trim_enabled; - const bool _adaptive_stepdown_enabled; int64_t _next_trim_time; int64_t _next_trim_time_saved; // for pause - // Adaptive step-down - TrimNativeStepDownControl _stepdown_control; - static const int _min_stepdown_factor = 2; // 2 * interval length - static const int _max_stepdown_factor = 8; // 8 * interval length - static const int64_t _stepdown_factor_reset_after = 60 * 1000; - int64_t _last_stepdown_time; - int _last_stepdown_factor; - - void update_stepdown_factor(int64_t tnow) { - if (tnow > (_last_stepdown_time + _stepdown_factor_reset_after)) { - _last_stepdown_factor = _min_stepdown_factor; - } else { - _last_stepdown_factor = MIN2(_last_stepdown_factor + 1, _max_stepdown_factor); - } - } - static const int64_t never = INT64_MAX; static int64_t now() { return os::javaTimeMillis(); } @@ -111,30 +94,8 @@ class NativeTrimmerThread : public ConcurrentGCThread { if (_periodic_trim_enabled) { int64_t interval_length = _interval_ms; - - // Handle adaptive stepdown. If heuristic recommends step-down, we prolong the - // wait interval by a factor that gets progressively larger with subsequent step-downs. - // Factor is capped and gets reset after a while. - if (_adaptive_stepdown_enabled) { - _stepdown_control.feed(result); - - if (_stepdown_control.recommend_step_down()) { - _last_stepdown_factor = - // increase or reset step-down factor depending on how many step-downs we had and - // how long they are ago. - (tnow > (_last_stepdown_time + _stepdown_factor_reset_after)) ? - _min_stepdown_factor : - MIN2(_last_stepdown_factor + 1, _max_stepdown_factor); - - _last_stepdown_time = tnow; - interval_length = _interval_ms * _last_stepdown_factor; - log_debug(gc, trim)("NativeTrimmer: long pause (" INT64_FORMAT " ms)", interval_length); - } - } _next_trim_time = tnow + interval_length; - } else { - // periodic trim disabled _next_trim_time = never; } } @@ -178,11 +139,8 @@ class NativeTrimmerThread : public ConcurrentGCThread { _lock(new (std::nothrow) PaddedMonitor(Mutex::nosafepoint, "NativeTrimmer_lock")), _interval_ms(TrimNativeHeapInterval * 1000), _periodic_trim_enabled(TrimNativeHeapInterval > 0), - _adaptive_stepdown_enabled(TrimNativeHeapAdaptiveStepDown), _next_trim_time(0), - _next_trim_time_saved(0), - _last_stepdown_time(0), - _last_stepdown_factor(_min_stepdown_factor) + _next_trim_time_saved(0) { set_name("Native Heap Trimmer"); _next_trim_time = _periodic_trim_enabled ? (now() + _interval_ms) : never; @@ -244,13 +202,9 @@ void TrimNative::initialize() { log_info(gc, trim)("Native trim enabled."); if (TrimNativeHeapInterval == 0) { - if (TrimNativeHeapAdaptiveStepDown) { - FLAG_SET_ERGO(TrimNativeHeapAdaptiveStepDown, false); - } log_info(gc, trim)("Periodic native trim disabled."); } else { - log_info(gc, trim)("Periodic native trim enabled (interval: %u seconds, dynamic step-down %s)", - TrimNativeHeapInterval, (TrimNativeHeapAdaptiveStepDown ? "enabled" : "disabled")); + log_info(gc, trim)("Periodic native trim enabled (interval: %u seconds)", TrimNativeHeapInterval); } g_trimmer_thread = new NativeTrimmerThread(); } diff --git a/test/hotspot/jtreg/gc/TestTrimNative.java b/test/hotspot/jtreg/gc/TestTrimNative.java index 77bb98261d296..4a2aed595f79d 100644 --- a/test/hotspot/jtreg/gc/TestTrimNative.java +++ b/test/hotspot/jtreg/gc/TestTrimNative.java @@ -254,11 +254,11 @@ String getSwitchName() { String s = name(); return "-XX:+Use" + s.substring(0, 1).toUpperCase() + s.substring(1) + "GC"; } - boolean isZ() { return this == GC.z; } - boolean isSerial() { return this == GC.serial; } - boolean isParallel() { return this == GC.parallel; } - boolean isG1() { return this == GC.g1; } - boolean isShenandoah() { return this == GC.shenandoah; } + boolean isZ() { return this == z; } + boolean isSerial() { return this == serial; } + boolean isParallel() { return this == parallel; } + boolean isG1() { return this == g1; } + boolean isShenandoah() { return this == shenandoah; } } private static OutputAnalyzer runTestWithOptions(String[] extraOptions, String[] testArgs) throws IOException { @@ -283,12 +283,11 @@ private static OutputAnalyzer runTestWithOptions(String[] extraOptions, String[] } private static void checkExpectedLogMessages(OutputAnalyzer output, boolean expectEnabled, - int expectedInterval, boolean expectAutoStepDown) { + int expectedInterval) { if (expectEnabled) { output.shouldContain("Native trim enabled"); if (expectedInterval > 0) { - output.shouldContain("Periodic native trim enabled (interval: " + expectedInterval + - " seconds, dynamic step-down " + (expectAutoStepDown ? "enabled" : "disabled") + ")"); + output.shouldContain("Periodic native trim enabled (interval: " + expectedInterval + " seconds"); output.shouldContain("NativeTrimmer start"); output.shouldContain("NativeTrimmer stop"); } else { @@ -380,7 +379,6 @@ private static void parseOutputAndLookForNegativeTrim(OutputAnalyzer output, int } final static int DEFAULT_TRIM_INTERVAL = 30; - final static boolean DEFAULT_AUTOSTEP = false; // Test explicit trim on full gc static private final void testExplicitTrimOnFullGC(GC gc) throws IOException { @@ -391,7 +389,7 @@ static private final void testExplicitTrimOnFullGC(GC gc) throws IOException { new String[] { "true" /* full gc */, String.valueOf(sleeptime_secs * 1000) /* ms after peak */ } ); - checkExpectedLogMessages(output, true, DEFAULT_TRIM_INTERVAL, DEFAULT_AUTOSTEP); + checkExpectedLogMessages(output, true, DEFAULT_TRIM_INTERVAL); // We expect to see at least one pause (because we pause during STW gc cycles) followed by an reqested immediate trim output.shouldContain("NativeTrimmer pause"); @@ -423,7 +421,7 @@ static private final void testPeriodicTrim(GC gc) throws IOException { long t2 = System.currentTimeMillis(); int runtime_s = (int)((t2 - t1) / 1000); - checkExpectedLogMessages(output, true, 1, DEFAULT_AUTOSTEP); + checkExpectedLogMessages(output, true, 1); // With an interval time of 1 second and a runtime of 6..x seconds we expect to see x periodic trim // log lines (+- fudge factor). @@ -442,7 +440,7 @@ static private final void testPeriodicTrimDisabled(GC gc) throws IOException { new String[] { gc.getSwitchName(), "-XX:+TrimNativeHeap", "-XX:TrimNativeHeapInterval=0" }, new String[] { "true" /* full gc */, "4000" /* ms after peak */ } ); - checkExpectedLogMessages(output, true, 0, DEFAULT_AUTOSTEP); + checkExpectedLogMessages(output, true, 0); // We expect only explicit trims, no periodic trims parseOutputAndLookForNegativeTrim(output, @@ -464,7 +462,7 @@ static private final void testOffOnNonCompliantPlatforms() throws IOException { new String[] { "-XX:+TrimNativeHeap" }, new String[] { "true" /* full gc */, "2000" /* ms after peak */ } ); - checkExpectedLogMessages(output, false, 0, false); + checkExpectedLogMessages(output, false, 0); } // Test trim native is disabled if explicitly switched off @@ -475,7 +473,7 @@ static private final void testOffExplicit() throws IOException { new String[] { "-XX:-TrimNativeHeap" }, new String[] { "true" /* full gc */, "2000" /* ms after peak */ } ); - checkExpectedLogMessages(output, false, 0, false); + checkExpectedLogMessages(output, false, 0); } // Test that trim-native is disabled by default @@ -486,7 +484,7 @@ static private final void testOffByDefault() throws IOException { new String[] { }, new String[] { "true" /* full gc */, "2000" /* ms after peak */ } ); - checkExpectedLogMessages(output, false, 0, false); + checkExpectedLogMessages(output, false, 0); } public static void main(String[] args) throws Exception { From a451dc5747400beda4b43625bec5c5187091efd7 Mon Sep 17 00:00:00 2001 From: tstuefe Date: Tue, 4 Jul 2023 14:37:07 +0200 Subject: [PATCH 25/27] wip --- src/hotspot/share/classfile/stringTable.cpp | 2 + src/hotspot/share/classfile/symbolTable.cpp | 2 + src/hotspot/share/gc/g1/g1ConcurrentMark.cpp | 2 +- src/hotspot/share/gc/shared/trimNative.cpp | 25 +-- src/hotspot/share/gc/shared/trimNative.hpp | 12 +- .../share/gc/shared/trimNativeStepDown.cpp | 107 ----------- .../share/gc/shared/trimNativeStepDown.hpp | 170 ------------------ src/hotspot/share/runtime/synchronizer.cpp | 2 + 8 files changed, 19 insertions(+), 303 deletions(-) delete mode 100644 src/hotspot/share/gc/shared/trimNativeStepDown.cpp delete mode 100644 src/hotspot/share/gc/shared/trimNativeStepDown.hpp diff --git a/src/hotspot/share/classfile/stringTable.cpp b/src/hotspot/share/classfile/stringTable.cpp index 89381b1a785f1..3558d2e1cdebf 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; { 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..535b570b96842 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; { TraceTime timer("Clean", TRACETIME_LOG(Debug, symboltable, perf)); while (bdt.do_task(jt, stdc, stdd)) { diff --git a/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp b/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp index eae7fb3e78375..cead7dac85c2f 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp @@ -1451,7 +1451,7 @@ void G1ConcurrentMark::cleanup() { return; } - TrimNative::PauseThenTrimMark trim_native_pause; + TrimNative::PauseMark trim_native_pause; G1Policy* policy = _g1h->policy(); policy->record_concurrent_mark_cleanup_start(); diff --git a/src/hotspot/share/gc/shared/trimNative.cpp b/src/hotspot/share/gc/shared/trimNative.cpp index fc67f80eb2100..6cc5e96b67e25 100644 --- a/src/hotspot/share/gc/shared/trimNative.cpp +++ b/src/hotspot/share/gc/shared/trimNative.cpp @@ -26,7 +26,6 @@ #include "gc/shared/concurrentGCThread.hpp" #include "gc/shared/gc_globals.hpp" #include "gc/shared/trimNative.hpp" -#include "gc/shared/trimNativeStepDown.hpp" #include "logging/log.hpp" #include "runtime/globals_extension.hpp" #include "runtime/mutex.hpp" @@ -82,7 +81,7 @@ class NativeTrimmerThread : public ConcurrentGCThread { // 2 - Trimming happens outside of lock protection. GC threads can issue new commands // concurrently. const bool explicitly_scheduled = (ntt == 0); - TrimResult result = execute_trim_and_log(explicitly_scheduled); + execute_trim_and_log(explicitly_scheduled); // 3 - Update _next_trim_time; but give concurrent setters preference. { @@ -110,7 +109,7 @@ class NativeTrimmerThread : public ConcurrentGCThread { // Execute the native trim, log results. // Return true if trim succeeded *and* we have valid size change data. - TrimResult execute_trim_and_log(bool explicitly_scheduled) const { + void execute_trim_and_log(bool explicitly_scheduled) const { assert(os::can_trim_native_heap(), "Unexpected"); const int64_t tnow = now(); os::size_change_t sc; @@ -125,12 +124,10 @@ class NativeTrimmerThread : public ConcurrentGCThread { (explicitly_scheduled ? "explicit" : "periodic"), PROPERFMTARGS(sc.before), PROPERFMTARGS(sc.after), sign, PROPERFMTARGS(delta), trim_time.seconds() * 1000); - return TrimResult(tnow, now() - tnow, sc.before, sc.after); } else { log_info(gc, trim)("Trim native heap (no details)"); } } - return TrimResult(); } public: @@ -172,19 +169,6 @@ class NativeTrimmerThread : public ConcurrentGCThread { log_debug(gc, trim)("NativeTrimmer unpause"); } - void unpause_and_trim() { - { - MonitorLocker ml(_lock, Mutex::_no_safepoint_check_flag); - _next_trim_time = 0; - ml.notify_all(); - } - if (_periodic_trim_enabled) { - log_debug(gc, trim)("NativeTrimmer unpause + request explicit trim"); - } else { - log_debug(gc, trim)("NativeTrimmer request explicit trim"); - } - } - }; // NativeTrimmer static NativeTrimmerThread* g_trimmer_thread = nullptr; @@ -228,8 +212,3 @@ void TrimNative::unpause_periodic_trim() { } } -void TrimNative::schedule_trim() { - if (g_trimmer_thread != nullptr) { - g_trimmer_thread->unpause_and_trim(); - } -} diff --git a/src/hotspot/share/gc/shared/trimNative.hpp b/src/hotspot/share/gc/shared/trimNative.hpp index 5be4c2a368379..7dd57fc0ec0c3 100644 --- a/src/hotspot/share/gc/shared/trimNative.hpp +++ b/src/hotspot/share/gc/shared/trimNative.hpp @@ -48,8 +48,16 @@ class TrimNative : public AllStatic { // Pause periodic trimming while in scope; when leaving scope, // resume periodic trimming. struct PauseMark { - PauseMark() { pause_periodic_trim(); } - ~PauseMark() { unpause_periodic_trim(); } + PauseMark() { + if (TrimNativeHeap) { + pause_periodic_trim(); + } + } + ~PauseMark() { + if (TrimNativeHeap) { + unpause_periodic_trim(); + } + } }; // Pause periodic trimming while in scope; when leaving scope, diff --git a/src/hotspot/share/gc/shared/trimNativeStepDown.cpp b/src/hotspot/share/gc/shared/trimNativeStepDown.cpp deleted file mode 100644 index bf04bd52deb20..0000000000000 --- a/src/hotspot/share/gc/shared/trimNativeStepDown.cpp +++ /dev/null @@ -1,107 +0,0 @@ -/* - * 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/trimNativeStepDown.hpp" -#include "logging/log.hpp" -#include "runtime/os.hpp" -#include "utilities/debug.hpp" -#include "utilities/globalDefinitions.hpp" -#include "utilities/ostream.hpp" - -void TrimResult::print_on(outputStream* st) const { - st->print("time: " INT64_FORMAT ", duration " INT64_FORMAT - ", rss1: " SIZE_FORMAT ", rss2: " SIZE_FORMAT " (" SSIZE_FORMAT ")", - _time, _duration, _rss_before, _rss_after, size_reduction()); -} - -// Given two results of subsequent trims, returns true if the first trim is considered -// "bad" - a trim that had been not worth the cost. -bool TrimNativeStepDownControl::is_bad_trim(const TrimResult& r, const TrimResult& r_followup) { - assert(r.is_valid() && r_followup.is_valid(), "Sanity"); - - const int64_t tinterval = r.interval_time(r_followup); - assert(tinterval >= 0, "negative interval? " INT64_FORMAT, tinterval); - if (tinterval == 0) { - return false; - } - assert(tinterval >= r.duration(), "trim duration cannot be larger than trim interval (" - INT64_FORMAT ", " INT64_FORMAT ")", tinterval, r.duration()); - - // Cost: ratio of trim time to total interval time (which contains trim time) - const double ratio_trim_time_to_interval_time = - (double)r.duration() / (double)tinterval; - assert(ratio_trim_time_to_interval_time >= 0, "Sanity"); - - // Any ratio of less than 1% trim time to interval time we regard as harmless - // (e.g. less than 10ms for 1second of interval) - if (ratio_trim_time_to_interval_time < 0.01) { - return false; - } - - // Benefit: Ratio of lasting size reduction to RSS before the first trim. - const double rss_gain_ratio = (double)r.calc_lasting_gain(r_followup) / (double)r.rss_before(); - - // We consider paying 1% (or more) time-per-interval for - // 1% (or less, maybe even negative) rss size reduction as bad. - bool bad = ratio_trim_time_to_interval_time > rss_gain_ratio; - - return false; -} - -bool TrimNativeStepDownControl::recommend_step_down() const { - struct { int trims, bad, ignored; } counts = { 0, 0, 0 }; - - const TrimResult* previous = nullptr; - auto trim_evaluater = [&counts, &previous] (const TrimResult* r) { -tty->print("?? "); -r->print_on(tty); - if (!r->is_valid() || previous == nullptr || !previous->is_valid()) { - // Note: we ignore: - // - the very youngest trim, since we don't know the - // RSS bounce back to the next trim yet. - // - invalid trim results - counts.ignored++; - } else { - counts.trims++; - if (is_bad_trim(*previous, *r)) { - counts.bad++; - } - } -tty->cr(); - previous = r; - }; - _history.iterate_oldest_to_youngest(trim_evaluater); - - log_trace(gc, trim)("Heuristic says: trims: %d, bad trims: %d, ignored: %d", - counts.trims, counts.bad, counts.ignored); - - // If all trims in the history had been bad (excluding the youngest, for which we cannot - // evaluate the lasting gains yet), step down. - return counts.ignored <= 1 && counts.bad == counts.trims; -} - -void TrimNativeStepDownControl::feed(const TrimResult& r) { - _history.add(r); -} diff --git a/src/hotspot/share/gc/shared/trimNativeStepDown.hpp b/src/hotspot/share/gc/shared/trimNativeStepDown.hpp deleted file mode 100644 index f98c5a18a5bf7..0000000000000 --- a/src/hotspot/share/gc/shared/trimNativeStepDown.hpp +++ /dev/null @@ -1,170 +0,0 @@ -/* - * 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_TRIMNATIVESTEPDOWN_HPP -#define SHARE_GC_SHARED_TRIMNATIVESTEPDOWN_HPP - - -#include "memory/allStatic.hpp" -#include "utilities/globalDefinitions.hpp" - -class outputStream; - -/////// Support for TrimNativeHeapAdaptiveStepDown ////// -// -// Small heuristic to check if periodic trimming has been fruitful so far. -// If this heuristic finds trimming to be harmful, we will inject one longer -// trim interval (GCTrimNativeIntervalMax). -// -// Trimming costs are the trim itself plus the re-aquisition costs of memory should the -// released memory be malloced again. Trimming gains are the memory reduction over time. -// Lasting gains are good; gains that don't last are not. -// -// There are roughly three usage pattern: -// - rare malloc spikes interspersed with long idle periods. Trimming is beneficial -// since the relieved memory pressure holds for a long time. -// - a constant low-intensity malloc drone. Trimming does not help much here but its -// harmless too since trimming is cheap if it does not recover much. -// - frequent malloc spikes with short idle periods; trimmed memory will be re-aquired -// after only a short relief; here, trimming could be harmful since we pay a lot for -// not much relief. We want to alleviate these scenarios. -// -// Putting numbers on these things is difficult though. We cannot observe malloc -// load directly, only RSS. For every trim we know the RSS reduction (from, to). So -// for subsequent trims we also can glean from (.from) whether RSS bounced -// back. But that is quite vague since RSS may have been influenced by a ton of other -// developments, especially for longer trim intervals. -// -// Therefore this heuristic may produce false positives and negatives. We try to err on -// the side of too much trimming here and to identify only situations that are clearly -// harmful. Note that the GCTrimNativeIntervalMax default (4 * GCTrimNativeInterval) -// is gentle enough for wrong heuristic results to not be too punative. - - -// A class holding results for a single trim operation. -class TrimResult { - - // time (ms) trim happened (javaTimeMillis) - int64_t _time; - // time (ms) trim itself took. - int64_t _duration; - // rss - size_t _rss_before, _rss_after; - -public: - - TrimResult() : _time(-1), _duration(0), _rss_before(0), _rss_after(0) {} - - TrimResult(int64_t t, int64_t d, size_t rss1, size_t rss2) : - _time(t), _duration(d), _rss_before(rss1), _rss_after(rss2) - {} - - int64_t time() const { return _time; } - int64_t duration() const { return _duration; } - size_t rss_before() const { return _rss_before; } - size_t rss_after() const { return _rss_before; } - - bool is_valid() const { - return _time >= 0 && _duration >= 0 && - _rss_before != 0 && _rss_after != 0; - } - - // Returns size reduction; positive if memory was reduced - ssize_t size_reduction() const { - return checked_cast(_rss_before) - - checked_cast(_rss_after); - } - - // Return the lasting gain compared with a follow-up trim. Negative numbers mean a loss. - ssize_t calc_lasting_gain(const TrimResult& followup_trim) const { - ssize_t gain = size_reduction(); - ssize_t loss = checked_cast(followup_trim.rss_before()) - - checked_cast(rss_after()); - return gain - loss; - } - - // Return the interval time between this result and a follow-up trim. - int64_t interval_time(const TrimResult& followup_trim) const { - return followup_trim.time() - time(); - } - - void print_on(outputStream* st) const; - -}; - -class TrimNativeStepDownControl { - - static const int _trim_history_length = 4; - - // A FIFO of the last n trim results - class TrimHistory { - static const int _max = _trim_history_length; - - // Note: history may contain invalid results; for one, it is - // initialized with invalid results to keep iterating simple; - // also invalid results can happen if measuring rss goes wrong. - TrimResult _histo[_max]; - int _pos; // position of next write - - public: - - TrimHistory() : _pos(0) {} - - void add(const TrimResult& result) { - _histo[_pos] = result; - if (++_pos == _max) { - _pos = 0; - } - } - - template - void iterate_oldest_to_youngest(Functor f) const { - int idx = _pos; - do { - f(_histo + idx); - if (++idx == _max) { - idx = 0; - } - } while (idx != _pos); - } - }; - - TrimHistory _history; - - static bool is_bad_trim(const TrimResult& r, const TrimResult& r_followup); - -public: - - // Feed a new trim result into control. It will be added to the history, - // replacing the oldest result. - // Adding invalid results is allowed; they will be ignored by the heuristics. - void feed(const TrimResult& r); - - // Returns true if Heuristic recommends stepping down the trim interval - bool recommend_step_down() const; - -}; - -#endif // SHARE_GC_SHARED_TRIMNATIVESTEPDOWN_HPP diff --git a/src/hotspot/share/runtime/synchronizer.cpp b/src/hotspot/share/runtime/synchronizer.cpp index 09ed8d1a7f811..dbd0f6b710b11 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; size_t count = 0; for (ObjectMonitor* monitor: *delete_list) { delete monitor; From a4837e173093b3745862709d258db1378b5306fb Mon Sep 17 00:00:00 2001 From: tstuefe Date: Wed, 5 Jul 2023 14:40:53 +0200 Subject: [PATCH 26/27] wip --- src/hotspot/share/classfile/stringTable.cpp | 2 +- src/hotspot/share/classfile/symbolTable.cpp | 2 +- src/hotspot/share/gc/g1/g1CollectedHeap.cpp | 5 +- src/hotspot/share/gc/g1/g1ConcurrentMark.cpp | 4 +- .../gc/parallel/parallelScavengeHeap.cpp | 3 - .../share/gc/parallel/psParallelCompact.cpp | 2 +- src/hotspot/share/gc/shared/gc_globals.hpp | 13 +- .../share/gc/shared/genCollectedHeap.cpp | 4 +- src/hotspot/share/gc/shared/trimNative.cpp | 88 ++-- src/hotspot/share/gc/shared/trimNative.hpp | 25 +- .../gc/shenandoah/shenandoahControlThread.cpp | 7 +- .../share/gc/shenandoah/shenandoahHeap.cpp | 4 - src/hotspot/share/gc/z/zGeneration.cpp | 3 + src/hotspot/share/memory/arena.cpp | 22 +- src/hotspot/share/memory/universe.cpp | 6 + src/hotspot/share/runtime/synchronizer.cpp | 2 +- test/hotspot/jtreg/gc/TestTrimNative.java | 381 ++++-------------- 17 files changed, 162 insertions(+), 411 deletions(-) diff --git a/src/hotspot/share/classfile/stringTable.cpp b/src/hotspot/share/classfile/stringTable.cpp index 3558d2e1cdebf..942506b270eb8 100644 --- a/src/hotspot/share/classfile/stringTable.cpp +++ b/src/hotspot/share/classfile/stringTable.cpp @@ -457,7 +457,7 @@ void StringTable::clean_dead_entries(JavaThread* jt) { StringTableDeleteCheck stdc; StringTableDoDelete stdd; - TrimNative::PauseMark trim_native_pause; + 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 535b570b96842..3842e67680a4e 100644 --- a/src/hotspot/share/classfile/symbolTable.cpp +++ b/src/hotspot/share/classfile/symbolTable.cpp @@ -738,7 +738,7 @@ void SymbolTable::clean_dead_entries(JavaThread* jt) { SymbolTableDeleteCheck stdc; SymbolTableDoDelete stdd; - TrimNative::PauseMark trim_native_pause; + 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 e2c9fea24a87e..9e27c77327a21 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp @@ -915,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(); @@ -1545,7 +1547,6 @@ void G1CollectedHeap::safepoint_synchronize_end() { void G1CollectedHeap::post_initialize() { CollectedHeap::post_initialize(); ref_processing_init(); - TrimNative::initialize(); } void G1CollectedHeap::ref_processing_init() { @@ -2551,7 +2552,7 @@ void G1CollectedHeap::retire_tlabs() { void G1CollectedHeap::do_collection_pause_at_safepoint_helper() { ResourceMark rm; - TrimNative::PauseThenTrimMark trim_native_pause; + 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 cead7dac85c2f..fd8feff57f8e8 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp @@ -1235,7 +1235,7 @@ void G1ConcurrentMark::remark() { return; } - TrimNative::PauseMark trim_native_pause; + TrimNative::PauseMark trim_native_pause("gc"); G1Policy* policy = _g1h->policy(); policy->record_concurrent_mark_remark_start(); @@ -1451,7 +1451,7 @@ void G1ConcurrentMark::cleanup() { return; } - TrimNative::PauseMark trim_native_pause; + TrimNative::PauseMark trim_native_pause("gc"); G1Policy* policy = _g1h->policy(); policy->record_concurrent_mark_cleanup_start(); diff --git a/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp b/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp index 6879957724641..57452cf07e5ae 100644 --- a/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp +++ b/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp @@ -42,7 +42,6 @@ #include "gc/shared/locationPrinter.inline.hpp" #include "gc/shared/scavengableNMethods.hpp" #include "gc/shared/suspendibleThreadSet.hpp" -#include "gc/shared/trimNative.hpp" #include "logging/log.hpp" #include "memory/iterator.hpp" #include "memory/metaspaceCounters.hpp" @@ -187,8 +186,6 @@ void ParallelScavengeHeap::post_initialize() { PSPromotionManager::initialize(); ScavengableNMethods::initialize(&_is_scavengable); - - TrimNative::initialize(); } void ParallelScavengeHeap::update_counters() { diff --git a/src/hotspot/share/gc/parallel/psParallelCompact.cpp b/src/hotspot/share/gc/parallel/psParallelCompact.cpp index ed4a203ab0943..740a5de221796 100644 --- a/src/hotspot/share/gc/parallel/psParallelCompact.cpp +++ b/src/hotspot/share/gc/parallel/psParallelCompact.cpp @@ -1711,7 +1711,7 @@ bool PSParallelCompact::invoke_no_policy(bool maximum_heap_compaction) { } // Pause native trimming for the duration of the GC - TrimNative::PauseThenTrimMark trim_native_pause; + TrimNative::PauseMark trim_native_pause("gc"); ParallelScavengeHeap* heap = ParallelScavengeHeap::heap(); diff --git a/src/hotspot/share/gc/shared/gc_globals.hpp b/src/hotspot/share/gc/shared/gc_globals.hpp index 627863d211311..e698a75b86fbc 100644 --- a/src/hotspot/share/gc/shared/gc_globals.hpp +++ b/src/hotspot/share/gc/shared/gc_globals.hpp @@ -693,15 +693,14 @@ constraint(GCCardSizeInBytesConstraintFunc,AtParse) \ \ product(bool, TrimNativeHeap, false, EXPERIMENTAL, \ - "GC will attempt to trim the native heap periodically and " \ - "at the end of a GC cycle.") \ + "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, 30, EXPERIMENTAL, \ + product(uint, TrimNativeHeapInterval, 60, EXPERIMENTAL, \ "If TrimNativeHeap is enabled: interval, in seconds, in which " \ - "the GC will attempt to trim the native heap. " \ - "A value of 0 disables periodic trimming altogether while still " \ - "trimming after a GC cycle.") \ - range(0, (24 * 60 * 60)) \ + "the GC will attempt to trim the native heap.") \ + range(1, UINT_MAX) \ \ // end of GC_FLAGS diff --git a/src/hotspot/share/gc/shared/genCollectedHeap.cpp b/src/hotspot/share/gc/shared/genCollectedHeap.cpp index 045828428e73d..4b858c77f537b 100644 --- a/src/hotspot/share/gc/shared/genCollectedHeap.cpp +++ b/src/hotspot/share/gc/shared/genCollectedHeap.cpp @@ -186,8 +186,6 @@ void GenCollectedHeap::post_initialize() { MarkSweep::initialize(); ScavengableNMethods::initialize(&_is_scavengable); - - TrimNative::initialize(); } PreGenGCValues GenCollectedHeap::get_pre_gc_values() const { @@ -445,7 +443,7 @@ void GenCollectedHeap::do_collection(bool full, "the requesting thread should have the Heap_lock"); guarantee(!is_gc_active(), "collection is not reentrant"); - TrimNative::PauseThenTrimMark trim_native_pause; + 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 index 6cc5e96b67e25..317b31f6cc387 100644 --- a/src/hotspot/share/gc/shared/trimNative.cpp +++ b/src/hotspot/share/gc/shared/trimNative.cpp @@ -39,12 +39,11 @@ class NativeTrimmerThread : public ConcurrentGCThread { Monitor* _lock; - // Periodic trimming state - const int64_t _interval_ms; - const bool _periodic_trim_enabled; - int64_t _next_trim_time; - int64_t _next_trim_time_saved; // for pause + + // Pausing + int _pausers; + int64_t _next_trim_time_saved; static const int64_t never = INT64_MAX; @@ -78,10 +77,8 @@ class NativeTrimmerThread : public ConcurrentGCThread { } while (ntt > tnow); } - // 2 - Trimming happens outside of lock protection. GC threads can issue new commands - // concurrently. - const bool explicitly_scheduled = (ntt == 0); - execute_trim_and_log(explicitly_scheduled); + // 2 - Trim outside of lock protection. + execute_trim_and_log(); // 3 - Update _next_trim_time; but give concurrent setters preference. { @@ -90,13 +87,7 @@ class NativeTrimmerThread : public ConcurrentGCThread { int64_t ntt2 = _next_trim_time; if (ntt2 == ntt) { // not changed concurrently? - - if (_periodic_trim_enabled) { - int64_t interval_length = _interval_ms; - _next_trim_time = tnow + interval_length; - } else { - _next_trim_time = never; - } + _next_trim_time = tnow + (TrimNativeHeapInterval * 1000); } } // Mutex scope } @@ -108,8 +99,7 @@ class NativeTrimmerThread : public ConcurrentGCThread { } // Execute the native trim, log results. - // Return true if trim succeeded *and* we have valid size change data. - void execute_trim_and_log(bool explicitly_scheduled) const { + void execute_trim_and_log() const { assert(os::can_trim_native_heap(), "Unexpected"); const int64_t tnow = now(); os::size_change_t sc; @@ -120,8 +110,7 @@ class NativeTrimmerThread : public ConcurrentGCThread { 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 (%s): RSS+Swap: " PROPERFMT "->" PROPERFMT " (%c" PROPERFMT "), %1.3fms", - (explicitly_scheduled ? "explicit" : "periodic"), + 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 { @@ -134,39 +123,45 @@ class NativeTrimmerThread : public ConcurrentGCThread { NativeTrimmerThread() : _lock(new (std::nothrow) PaddedMonitor(Mutex::nosafepoint, "NativeTrimmer_lock")), - _interval_ms(TrimNativeHeapInterval * 1000), - _periodic_trim_enabled(TrimNativeHeapInterval > 0), _next_trim_time(0), + _pausers(0), _next_trim_time_saved(0) { set_name("Native Heap Trimmer"); - _next_trim_time = _periodic_trim_enabled ? (now() + _interval_ms) : never; + _next_trim_time = now() + TrimNativeHeapInterval * 1000; create_and_start(); } - void pause() { - if (!_periodic_trim_enabled) { - return; - } + void pause(const char* reason) { + assert(TrimNativeHeap > 0, "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); - _next_trim_time_saved = _next_trim_time; - _next_trim_time = never; - ml.notify_all(); + 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"); + log_debug(gc, trim)("NativeTrimmer pause (%s) (%d)", reason, lvl); } - void unpause() { - if (!_periodic_trim_enabled) { - return; - } + void unpause(const char* reason) { + assert(TrimNativeHeap > 0, "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); - _next_trim_time = _next_trim_time_saved; - ml.notify_all(); + 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"); + log_debug(gc, trim)("NativeTrimmer unpause (%s) (%d)", reason, lvl); } }; // NativeTrimmer @@ -182,15 +177,8 @@ void TrimNative::initialize() { log_info(gc, trim)("Native trim not supported on this platform."); return; } - - log_info(gc, trim)("Native trim enabled."); - - if (TrimNativeHeapInterval == 0) { - log_info(gc, trim)("Periodic native trim disabled."); - } else { - log_info(gc, trim)("Periodic native trim enabled (interval: %u seconds)", TrimNativeHeapInterval); - } g_trimmer_thread = new NativeTrimmerThread(); + log_info(gc, trim)("Periodic native trim enabled (interval: %u seconds)", TrimNativeHeapInterval); } } @@ -200,15 +188,15 @@ void TrimNative::cleanup() { } } -void TrimNative::pause_periodic_trim() { +void TrimNative::pause_periodic_trim(const char* reason) { if (g_trimmer_thread != nullptr) { - g_trimmer_thread->pause(); + g_trimmer_thread->pause(reason); } } -void TrimNative::unpause_periodic_trim() { +void TrimNative::unpause_periodic_trim(const char* reason) { if (g_trimmer_thread != nullptr) { - g_trimmer_thread->unpause(); + g_trimmer_thread->unpause(reason); } } diff --git a/src/hotspot/share/gc/shared/trimNative.hpp b/src/hotspot/share/gc/shared/trimNative.hpp index 7dd57fc0ec0c3..440872e2d45d8 100644 --- a/src/hotspot/share/gc/shared/trimNative.hpp +++ b/src/hotspot/share/gc/shared/trimNative.hpp @@ -27,6 +27,7 @@ #define SHARE_GC_SHARED_TRIMNATIVE_HPP #include "memory/allStatic.hpp" +#include "gc/shared/gc_globals.hpp" class TrimNative : public AllStatic { public: @@ -35,38 +36,26 @@ class TrimNative : public AllStatic { static void cleanup(); // Pause periodic trim (if enabled). - static void pause_periodic_trim(); + static void pause_periodic_trim(const char* reason); // Unpause periodic trim (if enabled). - static void unpause_periodic_trim(); - - // Schedule an explicit trim now. - // If periodic trims are enabled and had been paused, they are unpaused - // and the interval is reset. - static void schedule_trim(); + static void unpause_periodic_trim(const char* reason); // Pause periodic trimming while in scope; when leaving scope, // resume periodic trimming. struct PauseMark { - PauseMark() { + const char* const _reason; + PauseMark(const char* reason = "unknown") : _reason(reason) { if (TrimNativeHeap) { - pause_periodic_trim(); + pause_periodic_trim(_reason); } } ~PauseMark() { if (TrimNativeHeap) { - unpause_periodic_trim(); + unpause_periodic_trim(_reason); } } }; - - // Pause periodic trimming while in scope; when leaving scope, - // trim immediately and resume periodic trimming with a new interval. - struct PauseThenTrimMark { - PauseThenTrimMark() { pause_periodic_trim(); } - ~PauseThenTrimMark() { schedule_trim(); } - }; - }; #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 34d0b80df34d9..f26b452706764 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp @@ -283,9 +283,6 @@ void ShenandoahControlThread::run_service() { // Print Metaspace change following GC (if logging is enabled). MetaspaceUtils::print_metaspace_change(meta_sizes); - // Expedite next native trim. This also trims if periodic trims are disabled. - TrimNative::schedule_trim(); - // GC is over, we are at idle now if (ShenandoahPacing) { heap->pacer()->setup_for_idle(); @@ -429,7 +426,7 @@ void ShenandoahControlThread::stop_service() { } void ShenandoahControlThread::service_stw_full_cycle(GCCause::Cause cause) { - TrimNative::PauseMark trim_pause_mark; + TrimNative::PauseMark trim_native_pause("gc"); GCIdMark gc_id_mark; ShenandoahGCSession session(cause); @@ -444,7 +441,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_pause_mark; + TrimNative::PauseMark trim_native_pause("gc"); GCIdMark gc_id_mark; ShenandoahGCSession session(cause); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp index 15cc5923a3d7c..03d90fd79bdf0 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp @@ -34,8 +34,6 @@ #include "gc/shared/memAllocator.hpp" #include "gc/shared/plab.hpp" #include "gc/shared/tlab_globals.hpp" -#include "gc/shared/trimNative.hpp" - #include "gc/shenandoah/shenandoahBarrierSet.hpp" #include "gc/shenandoah/shenandoahClosures.inline.hpp" #include "gc/shenandoah/shenandoahCollectionSet.hpp" @@ -653,8 +651,6 @@ void ShenandoahHeap::post_initialize() { _heuristics->initialize(); JFR_ONLY(ShenandoahJFRSupport::register_jfr_type_serializers()); - - TrimNative::initialize(); } size_t ShenandoahHeap::used() const { diff --git a/src/hotspot/share/gc/z/zGeneration.cpp b/src/hotspot/share/gc/z/zGeneration.cpp index 0ee5ce7a6c89e..1c5d6e96da2c5 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"); +log_info(gc)("jallo"); // Setup GC id and active marker GCIdMark gc_id_mark(_gc_id); IsGCActiveMark gc_active_mark; 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/synchronizer.cpp b/src/hotspot/share/runtime/synchronizer.cpp index dbd0f6b710b11..026dc1e2d707e 100644 --- a/src/hotspot/share/runtime/synchronizer.cpp +++ b/src/hotspot/share/runtime/synchronizer.cpp @@ -1647,7 +1647,7 @@ class VM_RendezvousGCThreads : public VM_Operation { }; static size_t delete_monitors(GrowableArray* delete_list) { - TrimNative::PauseMark trim_native_pause; + 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 index 4a2aed595f79d..84ed53d1f5723 100644 --- a/test/hotspot/jtreg/gc/TestTrimNative.java +++ b/test/hotspot/jtreg/gc/TestTrimNative.java @@ -1,6 +1,7 @@ /* - * Copyright (c) 2022 SAP SE. All rights reserved. - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * 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 @@ -26,169 +27,48 @@ package gc; /* - * All these tests test the trim-native feature for all GCs. - * Trim-native is the ability to trim the C-heap as part of the GC cycle. - * This feature is controlled by -XX:+TrimNativeHeap (by default off). - * Trimming happens on full gc for all gcs. Shenandoah and G1 also support - * concurrent trimming (Shenandoah supports this without any ties to java - * heap occupancy). - * - */ - -//// full gc tests ///// - -/* - * @test id=testExplicitTrimOnFullGC-serial - * @summary Test that TrimNativeHeap works with Serial + * @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 testExplicitTrimOnFullGC serial - */ - -/* - * @test id=testExplicitTrimOnFullGC-parallel - * @summary Test that TrimNativeHeap works with Parallel - * @requires vm.gc.Parallel - * @requires (os.family=="linux") & !vm.musl - * @modules java.base/jdk.internal.misc - * @library /test/lib - * @run driver gc.TestTrimNative testExplicitTrimOnFullGC parallel + * @run driver gc.TestTrimNative test -XX:+UseSerialGC */ /* - * @test id=testExplicitTrimOnFullGC-shenandoah - * @summary Test that TrimNativeHeap works with Shenandoah - * @requires vm.gc.Shenandoah - * @requires (os.family=="linux") & !vm.musl - * @modules java.base/jdk.internal.misc - * @library /test/lib - * @run driver gc.TestTrimNative testExplicitTrimOnFullGC shenandoah - */ - -/* - * @test id=testExplicitTrimOnFullGC-g1 - * @summary Test that TrimNativeHeap works with G1 - * @requires vm.gc.G1 - * @requires (os.family=="linux") & !vm.musl - * @modules java.base/jdk.internal.misc - * @library /test/lib - * @run driver gc.TestTrimNative testExplicitTrimOnFullGC g1 - */ - -/* - * @test id=testExplicitTrimOnFullGC-z - * @summary Test that TrimNativeHeap works with Z - * @requires vm.gc.Z - * @requires (os.family=="linux") & !vm.musl - * @modules java.base/jdk.internal.misc - * @library /test/lib - * @run driver gc.TestTrimNative testExplicitTrimOnFullGC z - */ - -//// auto mode tests ///// - -/* - * @test id=testPeriodicTrim-serial - * @summary Test that TrimNativeHeap works with Parallel - * @requires vm.gc.Parallel - * @requires (os.family=="linux") & !vm.musl - * @modules java.base/jdk.internal.misc - * @library /test/lib - * @run driver gc.TestTrimNative testPeriodicTrim serial - */ - -/* - * @test id=testPeriodicTrim-parallel - * @summary Test that TrimNativeHeap works with Parallel - * @requires vm.gc.Parallel - * @requires (os.family=="linux") & !vm.musl - * @modules java.base/jdk.internal.misc - * @library /test/lib - * @run driver gc.TestTrimNative testPeriodicTrim parallel - */ - -/* - * @test id=testPeriodicTrim-shenandoah - * @summary Test that TrimNativeHeap works with Shenandoah - * @requires vm.gc.Shenandoah - * @requires (os.family=="linux") & !vm.musl - * @modules java.base/jdk.internal.misc - * @library /test/lib - * @run driver gc.TestTrimNative testPeriodicTrim shenandoah - */ - -/* - * @test id=testPeriodicTrim-g1 - * @summary Test that TrimNativeHeap works with G1 - * @requires vm.gc.G1 - * @requires (os.family=="linux") & !vm.musl - * @modules java.base/jdk.internal.misc - * @library /test/lib - * @run driver gc.TestTrimNative testPeriodicTrim g1 - */ - -/* - * @test id=testPeriodicTrim-z - * @summary Test that TrimNativeHeap works with Z - * @requires vm.gc.Z - * @requires (os.family=="linux") & !vm.musl - * @modules java.base/jdk.internal.misc - * @library /test/lib - * @run driver gc.TestTrimNative testPeriodicTrim z - */ - -//// testPeriodicTrimDisabled test ///// - -/* - * @test id=testPeriodicTrimDisabled-serial - * @summary Test that TrimNativeHeap works with Parallel - * @requires vm.gc.Parallel - * @requires (os.family=="linux") & !vm.musl - * @modules java.base/jdk.internal.misc - * @library /test/lib - * @run driver gc.TestTrimNative testPeriodicTrimDisabled serial - */ - -/* - * @test id=testPeriodicTrimDisabled-parallel - * @summary Test that TrimNativeHeap works with Parallel - * @requires vm.gc.Parallel + * @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 testPeriodicTrimDisabled parallel + * @run driver gc.TestTrimNative test -XX:+UseParallelGC */ /* - * @test id=testPeriodicTrimDisabled-shenandoah - * @summary Test that TrimNativeHeap works with Shenandoah - * @requires vm.gc.Shenandoah + * @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 testPeriodicTrimDisabled shenandoah + * @run driver gc.TestTrimNative test -XX:+UseShenandoahGC */ /* - * @test id=testPeriodicTrimDisabled-g1 - * @summary Test that TrimNativeHeap works with G1 - * @requires vm.gc.G1 + * @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 testPeriodicTrimDisabled g1 + * @run driver gc.TestTrimNative test -XX:+UseZGC -XX:-ZGenerational */ /* - * @test id=testPeriodicTrimDisabled-z - * @summary Test that TrimNativeHeap works with Z - * @requires vm.gc.Z + * @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 testPeriodicTrimDisabled z + * @run driver gc.TestTrimNative test -XX:+UseZGC -XX:+ZGenerational */ // Other tests @@ -226,6 +106,7 @@ 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; @@ -248,123 +129,74 @@ enum Unit { Unit(long size) { this.size = size; } } - enum GC { - serial, parallel, g1, shenandoah, z; - String getSwitchName() { - String s = name(); - return "-XX:+Use" + s.substring(0, 1).toUpperCase() + s.substring(1) + "GC"; - } - boolean isZ() { return this == z; } - boolean isSerial() { return this == serial; } - boolean isParallel() { return this == parallel; } - boolean isG1() { return this == g1; } - boolean isShenandoah() { return this == shenandoah; } - } - - private static OutputAnalyzer runTestWithOptions(String[] extraOptions, String[] testArgs) throws IOException { + private static OutputAnalyzer runTestWithOptions(String[] extraOptions) throws IOException { List allOptions = new ArrayList(); - allOptions.add("-XX:+UnlockExperimentalVMOptions"); 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"); - allOptions.addAll(Arrays.asList(testArgs)); 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("Native trim enabled"); - if (expectedInterval > 0) { - output.shouldContain("Periodic native trim enabled (interval: " + expectedInterval + " seconds"); - output.shouldContain("NativeTrimmer start"); - output.shouldContain("NativeTrimmer stop"); - } else { - output.shouldContain("Periodic native trim disabled"); - } - + 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 a log line that describes a successful negative trim. The total amount of trims - * should be matching about what the test program allocated. - * like this: - * "[2.053s][debug][gc,trim] Trim native heap (retain size: 5120K): RSS+Swap: 271M->223M (-49112K), 2.834ms" - * (Note: we use the "properXXX" print routines, therefore units can differ) - * Check that the sum of all trim log lines comes to a total RSS reduction in the MB range + * 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 minPeriodicTrimsExpected min number of periodic trim lines expected in UL log - * @param maxPeriodicTrimsExpected min number of periodic trim lines expected in UL log - * @param minExplicitTrimsExpected min number of explicit trim lines expected in UL log - * @param maxExplicitTrimsExpected min number of explicit trim lines expected in UL log + * @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 minPeriodicTrimsExpected, - int maxPeriodicTrimsExpected, int minExplicitTrimsExpected, - int maxExplicitTrimsExpected) { + 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 \\((explicit|periodic)\\)" + - ".*RSS\\+Swap: (\\d+)([BKMG])->(\\d+)([BKMG]).*"); - int numExplicitTrimsFound = 0; - int numPeriodicTrimsFound = 0; + 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()) { - String explicitOrPeriodic = mat.group(1); - boolean periodic = false; - switch (explicitOrPeriodic) { - case "explicit": periodic = false; break; - case "periodic": periodic = true; break; - default: throw new RuntimeException("Invalid line \"" + line + "\""); - } - long rss1 = Long.parseLong(mat.group(2)) * Unit.valueOf(mat.group(3)).size; - long rss2 = Long.parseLong(mat.group(4)) * Unit.valueOf(mat.group(5)).size; - System.out.println("Parsed Trim Line. Periodic: " + periodic + ", rss1: " + rss1 + " rss2: " + rss2); + 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); } - if (periodic) { - numPeriodicTrimsFound ++; - } else { - numExplicitTrimsFound ++; - } + numTrimsFound ++; } - if (numPeriodicTrimsFound > maxPeriodicTrimsExpected) { - throw new RuntimeException("Abnormal high number of periodic trim attempts found (more than " + maxPeriodicTrimsExpected + - "). Does the interval setting not work?"); - } - if (numExplicitTrimsFound > maxExplicitTrimsExpected) { - throw new RuntimeException("Abnormal high number of explicit trim attempts found (more than " + maxExplicitTrimsExpected + + if (numTrimsFound > maxTrimsExpected) { + throw new RuntimeException("Abnormal high number of periodic trim attempts found (more than " + maxTrimsExpected + "). Does the interval setting not work?"); } } - if (numPeriodicTrimsFound < minPeriodicTrimsExpected) { - throw new RuntimeException("We found fewer (periodic) trim lines in UL log than expected (expected at least " + minPeriodicTrimsExpected + - ", found " + numPeriodicTrimsFound + ")."); - } - if (numExplicitTrimsFound < minExplicitTrimsExpected) { - throw new RuntimeException("We found fewer (explicit) trim lines in UL log than expected (expected at least " + minExplicitTrimsExpected + - ", found " + numExplicitTrimsFound + ")."); + 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.7f; + 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. @@ -378,112 +210,55 @@ private static void parseOutputAndLookForNegativeTrim(OutputAnalyzer output, int } } - final static int DEFAULT_TRIM_INTERVAL = 30; - - // Test explicit trim on full gc - static private final void testExplicitTrimOnFullGC(GC gc) throws IOException { - System.out.println("testExplicitTrimOnFullGC"); - int sleeptime_secs = 2; - OutputAnalyzer output = runTestWithOptions ( - new String[] { gc.getSwitchName(), "-XX:+TrimNativeHeap" }, - new String[] { "true" /* full gc */, String.valueOf(sleeptime_secs * 1000) /* ms after peak */ } - ); - - checkExpectedLogMessages(output, true, DEFAULT_TRIM_INTERVAL); - - // We expect to see at least one pause (because we pause during STW gc cycles) followed by an reqested immediate trim - output.shouldContain("NativeTrimmer pause"); - output.shouldContain("NativeTrimmer unpause + request explicit trim"); - - int minPeriodicTrimsExpected = 0; - int maxPeriodicTrimsExpected = 10; - int minExplicitTrimsExpected = 1; - int maxExplicitTrimsExpected = 10; - - parseOutputAndLookForNegativeTrim(output, - 0, /* minPeriodicTrimsExpected */ - 10, /* maxPeriodicTrimsExpected */ - 1, /* minExplicitTrimsExpected */ - 10 /* maxExplicitTrimsExpected */ - ); - } - - // Test periodic trim with very short trim interval. We explicitly don't do a GC to not get an explicite trim - // "stealing" our gains. - static private final void testPeriodicTrim(GC gc) throws IOException { - System.out.println("testPeriodicTrim"); - long t1 = System.currentTimeMillis(); - int sleeptime_secs = 4; + static private final void runTest(String[] VMargs) throws IOException { OutputAnalyzer output = runTestWithOptions ( - new String[] { gc.getSwitchName(), "-XX:+TrimNativeHeap", "-XX:TrimNativeHeapInterval=1" }, - new String[] { "false" /* no full gc */, String.valueOf(sleeptime_secs * 1000) /* ms after peak */ } + new String[] { "-XX:+UnlockExperimentalVMOptions", + "-XX:+TrimNativeHeap", + "-XX:TrimNativeHeapInterval=1" + } ); - long t2 = System.currentTimeMillis(); - int runtime_s = (int)((t2 - t1) / 1000); checkExpectedLogMessages(output, true, 1); - // With an interval time of 1 second and a runtime of 6..x seconds we expect to see x periodic trim - // log lines (+- fudge factor). - parseOutputAndLookForNegativeTrim(output, - runtime_s - 4, /* minPeriodicTrimsExpected */ - runtime_s, /* maxPeriodicTrimsExpected */ - 0, /* minExplicitTrimsExpected */ - 10 /* maxExplicitTrimsExpected */ - ); + // 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 */); } - static private final void testPeriodicTrimDisabled(GC gc) throws IOException { - System.out.println("testPeriodicTrimDisabled"); + // Test that a high trim interval effectively disables trimming + static private final void testHighTrimInterval() throws IOException { OutputAnalyzer output = runTestWithOptions ( - new String[] { gc.getSwitchName(), "-XX:+TrimNativeHeap", "-XX:TrimNativeHeapInterval=0" }, - new String[] { "true" /* full gc */, "4000" /* ms after peak */ } - ); - checkExpectedLogMessages(output, true, 0); - - // We expect only explicit trims, no periodic trims - parseOutputAndLookForNegativeTrim(output, - 0, /* minPeriodicTrimsExpected */ - 0, /* maxPeriodicTrimsExpected */ - 1, /* minExplicitTrimsExpected */ - 10 /* maxExplicitTrimsExpected */ - ); + 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 { - if (Platform.isLinux() && !Platform.isMusl()) { - throw new RemoteException("Don't call me for Linux glibc"); - } - // Logic is shared, so no need to test with every GC. Just use the default GC. - System.out.println("testOffOnNonCompliantPlatforms"); OutputAnalyzer output = runTestWithOptions ( - new String[] { "-XX:+TrimNativeHeap" }, - new String[] { "true" /* full gc */, "2000" /* ms after peak */ } - ); + 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 { - // Logic is shared, so no need to test with every GC. Just use the default GC. - System.out.println("testOffExplicit"); OutputAnalyzer output = runTestWithOptions ( - new String[] { "-XX:-TrimNativeHeap" }, - new String[] { "true" /* full gc */, "2000" /* ms after peak */ } - ); + new String[] { "-XX:+UnlockExperimentalVMOptions", + "-XX:-TrimNativeHeap" + }); checkExpectedLogMessages(output, false, 0); } - // Test that trim-native is disabled by default + // Test trim native is disabled if explicitly switched off static private final void testOffByDefault() throws IOException { - // Logic is shared, so no need to test with every GC. Just use the default GC. - System.out.println("testOffByDefault"); - OutputAnalyzer output = runTestWithOptions ( - new String[] { }, - new String[] { "true" /* full gc */, "2000" /* ms after peak */ } - ); + OutputAnalyzer output = runTestWithOptions (new String[] { }); checkExpectedLogMessages(output, false, 0); } @@ -494,7 +269,6 @@ public static void main(String[] args) throws Exception { } if (args[0].equals("RUN")) { - boolean doFullGC = Boolean.parseBoolean(args[1]); System.out.println("Will spike now..."); for (int i = 0; i < numAllocations; i++) { @@ -507,28 +281,19 @@ public static void main(String[] args) throws Exception { } System.out.println("Done spiking."); - if (doFullGC) { - System.out.println("GC..."); - System.gc(); - } + // Do a system GC. Native trimming should be paused in that time. + System.out.println("GC..."); + System.gc(); // give GC time to react - int time = Integer.parseInt(args[2]); System.out.println("Sleeping..."); - Thread.sleep(time); + Thread.sleep(3000); System.out.println("Done."); return; - } else if (args[0].equals("testExplicitTrimOnFullGC")) { - final GC gc = GC.valueOf(args[1]); - testExplicitTrimOnFullGC(gc); - } else if (args[0].equals("testPeriodicTrim")) { - final GC gc = GC.valueOf(args[1]); - testPeriodicTrim(gc); - } else if (args[0].equals("testPeriodicTrimDisabled")) { - final GC gc = GC.valueOf(args[1]); - testPeriodicTrimDisabled(gc); + } 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")) { @@ -538,7 +303,5 @@ public static void main(String[] args) throws Exception { } else { throw new RuntimeException("Invalid test " + args[0]); } - } - } From 162b880a98053f7d6c7bfb07fa0eaebd0efa7e4a Mon Sep 17 00:00:00 2001 From: tstuefe Date: Wed, 5 Jul 2023 17:14:28 +0200 Subject: [PATCH 27/27] fix windows build --- src/hotspot/share/gc/shared/trimNative.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hotspot/share/gc/shared/trimNative.cpp b/src/hotspot/share/gc/shared/trimNative.cpp index 317b31f6cc387..1b67eb4fd83ce 100644 --- a/src/hotspot/share/gc/shared/trimNative.cpp +++ b/src/hotspot/share/gc/shared/trimNative.cpp @@ -133,7 +133,7 @@ class NativeTrimmerThread : public ConcurrentGCThread { } void pause(const char* reason) { - assert(TrimNativeHeap > 0, "Only call if enabled"); + assert(TrimNativeHeap, "Only call if enabled"); assert(TrimNativeHeapInterval > 0, "Only call if periodic trimming is enabled"); int lvl = 0; { @@ -149,7 +149,7 @@ class NativeTrimmerThread : public ConcurrentGCThread { } void unpause(const char* reason) { - assert(TrimNativeHeap > 0, "Only call if enabled"); + assert(TrimNativeHeap, "Only call if enabled"); assert(TrimNativeHeapInterval > 0, "Only call if periodic trimming is enabled"); int lvl = 0; {