8305994: Guarantee eventual async monitor deflation

Reviewed-by: simonis, stuefe, dcubed
This commit is contained in:
Aleksey Shipilev 2023-04-24 17:02:59 +00:00
parent 41d6be4d80
commit 6b81342c22
4 changed files with 272 additions and 5 deletions

View File

@ -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).") \

View File

@ -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);
}
}

View File

@ -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++;
}

View File

@ -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;
}
}
}
}