diff --git a/src/hotspot/share/runtime/globals.hpp b/src/hotspot/share/runtime/globals.hpp index c67b17ae3fb..3422185e3fa 100644 --- a/src/hotspot/share/runtime/globals.hpp +++ b/src/hotspot/share/runtime/globals.hpp @@ -713,6 +713,13 @@ const int ObjectAlignmentInBytes = 8; "MonitorUsedDeflationThreshold is exceeded (0 is off).") \ range(0, max_jint) \ \ + /* notice: the max range value here is max_jint, not max_intx */ \ + /* because of overflow issue */ \ + product(intx, GuaranteedAsyncDeflationInterval, 60000, DIAGNOSTIC, \ + "Async deflate idle monitors every so many milliseconds even " \ + "when MonitorUsedDeflationThreshold is NOT exceeded (0 is off).") \ + range(0, max_jint) \ + \ product(size_t, AvgMonitorsPerThreadEstimate, 1024, DIAGNOSTIC, \ "Used to estimate a variable ceiling based on number of threads " \ "for use with MonitorUsedDeflationThreshold (0 is off).") \ diff --git a/src/hotspot/share/runtime/monitorDeflationThread.cpp b/src/hotspot/share/runtime/monitorDeflationThread.cpp index 2f800f296b1..25a7a82ca2f 100644 --- a/src/hotspot/share/runtime/monitorDeflationThread.cpp +++ b/src/hotspot/share/runtime/monitorDeflationThread.cpp @@ -47,6 +47,11 @@ void MonitorDeflationThread::initialize() { } void MonitorDeflationThread::monitor_deflation_thread_entry(JavaThread* jt, TRAPS) { + + // We wait for GuaranteedSafepointInterval so that is_async_deflation_needed() is checked + // at the same interval, unless GuaranteedAsyncDeflationInterval is lower. + const intx wait_time = MIN2(GuaranteedSafepointInterval, GuaranteedAsyncDeflationInterval); + while (true) { { // Need state transition ThreadBlockInVM so that this thread @@ -58,9 +63,7 @@ void MonitorDeflationThread::monitor_deflation_thread_entry(JavaThread* jt, TRAP MonitorLocker ml(MonitorDeflation_lock, Mutex::_no_safepoint_check_flag); while (!ObjectSynchronizer::is_async_deflation_needed()) { // Wait until notified that there is some work to do. - // We wait for GuaranteedSafepointInterval so that - // is_async_deflation_needed() is checked at the same interval. - ml.wait(GuaranteedSafepointInterval); + ml.wait(wait_time); } } diff --git a/src/hotspot/share/runtime/synchronizer.cpp b/src/hotspot/share/runtime/synchronizer.cpp index 196892c65dc..8aca915ff78 100644 --- a/src/hotspot/share/runtime/synchronizer.cpp +++ b/src/hotspot/share/runtime/synchronizer.cpp @@ -262,6 +262,9 @@ void ObjectSynchronizer::initialize() { } // Start the ceiling with the estimate for one thread. set_in_use_list_ceiling(AvgMonitorsPerThreadEstimate); + + // Start the timer for deflations, so it does not trigger immediately. + _last_async_deflation_time_ns = os::javaTimeNanos(); } MonitorList ObjectSynchronizer::_in_use_list; @@ -290,6 +293,7 @@ bool volatile ObjectSynchronizer::_is_async_deflation_requested = false; bool volatile ObjectSynchronizer::_is_final_audit = false; jlong ObjectSynchronizer::_last_async_deflation_time_ns = 0; static uintx _no_progress_cnt = 0; +static bool _no_progress_skip_increment = false; // =====================> Quick functions @@ -1080,7 +1084,14 @@ static bool monitors_used_above_threshold(MonitorList* list) { // Check if our monitor usage is above the threshold: size_t monitor_usage = (monitors_used * 100LL) / ceiling; - return int(monitor_usage) > MonitorUsedDeflationThreshold; + if (int(monitor_usage) > MonitorUsedDeflationThreshold) { + log_info(monitorinflation)("monitors_used=" SIZE_FORMAT ", ceiling=" SIZE_FORMAT + ", monitor_usage=" SIZE_FORMAT ", threshold=" INTX_FORMAT, + monitors_used, ceiling, monitor_usage, MonitorUsedDeflationThreshold); + return true; + } + + return false; } size_t ObjectSynchronizer::in_use_list_ceiling() { @@ -1102,17 +1113,49 @@ void ObjectSynchronizer::set_in_use_list_ceiling(size_t new_value) { bool ObjectSynchronizer::is_async_deflation_needed() { if (is_async_deflation_requested()) { // Async deflation request. + log_info(monitorinflation)("Async deflation needed: explicit request"); return true; } + + jlong time_since_last = time_since_last_async_deflation_ms(); + if (AsyncDeflationInterval > 0 && - time_since_last_async_deflation_ms() > AsyncDeflationInterval && + time_since_last > AsyncDeflationInterval && monitors_used_above_threshold(&_in_use_list)) { // It's been longer than our specified deflate interval and there // are too many monitors in use. We don't deflate more frequently // than AsyncDeflationInterval (unless is_async_deflation_requested) // in order to not swamp the MonitorDeflationThread. + log_info(monitorinflation)("Async deflation needed: monitors used are above the threshold"); return true; } + + if (GuaranteedAsyncDeflationInterval > 0 && + time_since_last > GuaranteedAsyncDeflationInterval) { + // It's been longer than our specified guaranteed deflate interval. + // We need to clean up the used monitors even if the threshold is + // not reached, to keep the memory utilization at bay when many threads + // touched many monitors. + log_info(monitorinflation)("Async deflation needed: guaranteed interval (" INTX_FORMAT " ms) " + "is greater than time since last deflation (" JLONG_FORMAT " ms)", + GuaranteedAsyncDeflationInterval, time_since_last); + + // If this deflation has no progress, then it should not affect the no-progress + // tracking, otherwise threshold heuristics would think it was triggered, experienced + // no progress, and needs to backoff more aggressively. In this "no progress" case, + // the generic code would bump the no-progress counter, and we compensate for that + // by telling it to skip the update. + // + // If this deflation has progress, then it should let non-progress tracking + // know about this, otherwise the threshold heuristics would kick in, potentially + // experience no-progress due to aggressive cleanup by this deflation, and think + // it is still in no-progress stride. In this "progress" case, the generic code would + // zero the counter, and we allow it to happen. + _no_progress_skip_increment = true; + + return true; + } + return false; } @@ -1530,6 +1573,8 @@ size_t ObjectSynchronizer::deflate_idle_monitors(ObjectMonitorsHashtable* table) if (deflated_count != 0) { _no_progress_cnt = 0; + } else if (_no_progress_skip_increment) { + _no_progress_skip_increment = false; } else { _no_progress_cnt++; } diff --git a/test/hotspot/jtreg/runtime/Monitor/GuaranteedAsyncDeflationIntervalTest.java b/test/hotspot/jtreg/runtime/Monitor/GuaranteedAsyncDeflationIntervalTest.java new file mode 100644 index 00000000000..59957005789 --- /dev/null +++ b/test/hotspot/jtreg/runtime/Monitor/GuaranteedAsyncDeflationIntervalTest.java @@ -0,0 +1,212 @@ +/* + * Copyright Amazon.com Inc. 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. + */ + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +/* + * @test id=allDisabled + * @bug 8305994 + * @summary Test the GuaranteedAsyncDeflationInterval option + * @requires vm.flagless + * @library /test/lib + * @run driver GuaranteedAsyncDeflationIntervalTest allDisabled + */ + +/* + * @test id=guaranteedNoMUDT + * @requires vm.flagless + * @library /test/lib + * @run driver GuaranteedAsyncDeflationIntervalTest guaranteedNoMUDT + */ + +/* + * @test id=guaranteedNoADI + * @requires vm.flagless + * @library /test/lib + * @run driver GuaranteedAsyncDeflationIntervalTest guaranteedNoADI + */ + +/* + * @test id=allEnabled + * @requires vm.flagless + * @library /test/lib + * @run driver GuaranteedAsyncDeflationIntervalTest allEnabled + */ + +public class GuaranteedAsyncDeflationIntervalTest { + + public static class Test { + // Inflate a lot of monitors, so that threshold heuristics definitely fires + public static final int MONITORS = 10_000; + + public static Object[] monitors; + + public static void main(String... args) throws Exception { + monitors = new Object[MONITORS]; + for (int i = 0; i < MONITORS; i++) { + Object o = new Object(); + synchronized (o) { + try { + o.wait(1); // Inflate! + } catch (InterruptedException ie) { + } + } + monitors[i] = o; + } + + try { + Thread.sleep(10_000); + } catch (InterruptedException ie) { + } + } + } + + public static void main(String[] args) throws Exception { + if (args.length < 1) { + throw new IllegalArgumentException("Expect the test label"); + } + + String test = args[0]; + switch (test) { + case "allDisabled": + testAllDisabled(); + break; + case "guaranteedNoMUDT": + testGuaranteedNoMUDT(); + break; + case "guaranteedNoADI": + testGuaranteedNoADI(); + break; + case "allEnabled": + testAllEnabled(); + break; + default: + throw new IllegalArgumentException("Unknown test: " + test); + } + } + + static final String MSG_THRESHOLD = "Async deflation needed: monitors used are above the threshold"; + static final String MSG_GUARANTEED = "Async deflation needed: guaranteed interval"; + + // Try with all heuristics disabled + public static void testAllDisabled() throws Exception { + ProcessBuilder pb = ProcessTools.createJavaProcessBuilder( + "-Xmx100M", + "-XX:+UnlockDiagnosticVMOptions", + "-XX:GuaranteedAsyncDeflationInterval=0", + "-XX:AsyncDeflationInterval=0", + "-XX:MonitorUsedDeflationThreshold=0", + "-Xlog:monitorinflation=info", + "GuaranteedAsyncDeflationIntervalTest$Test"); + + OutputAnalyzer oa = new OutputAnalyzer(pb.start()); + oa.shouldHaveExitValue(0); + + oa.shouldNotContain(MSG_THRESHOLD); + oa.shouldNotContain(MSG_GUARANTEED); + assertNoDeflations(oa); + } + + // Try with guaranteed interval only enabled, threshold heuristics disabled via MUDT + public static void testGuaranteedNoMUDT() throws Exception { + ProcessBuilder pb = ProcessTools.createJavaProcessBuilder( + "-Xmx100M", + "-XX:+UnlockDiagnosticVMOptions", + "-XX:GuaranteedAsyncDeflationInterval=100", + "-XX:MonitorUsedDeflationThreshold=0", + "-Xlog:monitorinflation=info", + "GuaranteedAsyncDeflationIntervalTest$Test"); + + OutputAnalyzer oa = new OutputAnalyzer(pb.start()); + oa.shouldHaveExitValue(0); + + oa.shouldNotContain(MSG_THRESHOLD); + oa.shouldContain(MSG_GUARANTEED); + assertDeflations(oa); + } + + // Try with guaranteed interval only enabled, threshold heuristics disabled via ADI + public static void testGuaranteedNoADI() throws Exception { + ProcessBuilder pb = ProcessTools.createJavaProcessBuilder( + "-Xmx100M", + "-XX:+UnlockDiagnosticVMOptions", + "-XX:GuaranteedAsyncDeflationInterval=100", + "-XX:AsyncDeflationInterval=0", + "-Xlog:monitorinflation=info", + "GuaranteedAsyncDeflationIntervalTest$Test"); + + OutputAnalyzer oa = new OutputAnalyzer(pb.start()); + oa.shouldHaveExitValue(0); + + oa.shouldNotContain(MSG_THRESHOLD); + oa.shouldContain(MSG_GUARANTEED); + assertDeflations(oa); + } + + // Try with both threshold heuristics and guaranteed interval enabled + public static void testAllEnabled() throws Exception { + ProcessBuilder pb = ProcessTools.createJavaProcessBuilder( + "-Xmx100M", + "-XX:+UnlockDiagnosticVMOptions", + "-XX:GuaranteedAsyncDeflationInterval=5000", + "-XX:MonitorUsedDeflationThreshold=10", + "-Xlog:monitorinflation=info", + "GuaranteedAsyncDeflationIntervalTest$Test"); + + OutputAnalyzer oa = new OutputAnalyzer(pb.start()); + oa.shouldHaveExitValue(0); + + oa.shouldContain(MSG_THRESHOLD); + oa.shouldContain(MSG_GUARANTEED); + assertDeflations(oa); + } + + private static void assertNoDeflations(OutputAnalyzer oa) { + for (String line : oa.asLines()) { + if (line.contains("Starting the final audit")) { + // Final deflations started, with no prior deflations, good. + return; + } + if (line.contains("begin deflating")) { + // Deflations detected before final ones, bad + oa.reportDiagnosticSummary(); + throw new IllegalStateException("FAILED"); + } + } + } + + private static void assertDeflations(OutputAnalyzer oa) { + for (String line : oa.asLines()) { + if (line.contains("Starting the final audit")) { + // Final deflations started, with no prior deflations, bad. + oa.reportDiagnosticSummary(); + throw new IllegalStateException("FAILED"); + } + if (line.contains("begin deflating")) { + // Deflations detected before final ones, good + return; + } + } + } +}