d31fd78d96
Reviewed-by: dholmes, mbaesken
349 lines
15 KiB
Java
349 lines
15 KiB
Java
/*
|
|
* Copyright (c) 2023 SAP SE. All rights reserved.
|
|
* Copyright (c) 2023, 2024, Red Hat, Inc. All rights reserved.
|
|
* Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* @test id=trimNative
|
|
* @requires vm.flagless
|
|
* @requires (os.family=="linux") & !vm.musl
|
|
* @modules java.base/jdk.internal.misc
|
|
* @library /test/lib
|
|
* @build jdk.test.whitebox.WhiteBox
|
|
* @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
|
|
* @run driver TestTrimNative trimNative
|
|
*/
|
|
|
|
/*
|
|
* @test id=trimNativeStrict
|
|
* @requires vm.flagless
|
|
* @requires (os.family=="linux") & !vm.musl
|
|
* @modules java.base/jdk.internal.misc
|
|
* @library /test/lib
|
|
* @build jdk.test.whitebox.WhiteBox
|
|
* @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
|
|
* @run main/manual TestTrimNative trimNativeStrict
|
|
*/
|
|
|
|
/*
|
|
* @test id=trimNativeHighInterval
|
|
* @summary High interval trimming should not even kick in for short program runtimes
|
|
* @requires vm.flagless
|
|
* @requires (os.family=="linux") & !vm.musl
|
|
* @modules java.base/jdk.internal.misc
|
|
* @library /test/lib
|
|
* @build jdk.test.whitebox.WhiteBox
|
|
* @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
|
|
* @run driver TestTrimNative trimNativeHighInterval
|
|
*/
|
|
|
|
/*
|
|
* @test id=trimNativeLowInterval
|
|
* @summary Very low (sub-second) interval, nothing should explode
|
|
* @requires vm.flagless
|
|
* @requires (os.family=="linux") & !vm.musl
|
|
* @modules java.base/jdk.internal.misc
|
|
* @library /test/lib
|
|
* @build jdk.test.whitebox.WhiteBox
|
|
* @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
|
|
* @run driver TestTrimNative trimNativeLowInterval
|
|
*/
|
|
|
|
/*
|
|
* @test id=trimNativeLowIntervalStrict
|
|
* @summary Very low (sub-second) interval, nothing should explode (stricter test, manual mode)
|
|
* @requires vm.flagless
|
|
* @requires (os.family=="linux") & !vm.musl
|
|
* @modules java.base/jdk.internal.misc
|
|
* @library /test/lib
|
|
* @build jdk.test.whitebox.WhiteBox
|
|
* @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
|
|
* @run main/manual TestTrimNative trimNativeLowIntervalStrict
|
|
*/
|
|
|
|
/*
|
|
* @test id=testOffByDefault
|
|
* @summary Test that trimming is disabled by default
|
|
* @requires vm.flagless
|
|
* @requires (os.family=="linux") & !vm.musl
|
|
* @modules java.base/jdk.internal.misc
|
|
* @library /test/lib
|
|
* @build jdk.test.whitebox.WhiteBox
|
|
* @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
|
|
* @run driver TestTrimNative testOffByDefault
|
|
*/
|
|
|
|
/*
|
|
* @test id=testOffExplicit
|
|
* @summary Test that trimming can be disabled explicitly
|
|
* @requires vm.flagless
|
|
* @requires (os.family=="linux") & !vm.musl
|
|
* @modules java.base/jdk.internal.misc
|
|
* @library /test/lib
|
|
* @build jdk.test.whitebox.WhiteBox
|
|
* @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
|
|
* @run driver TestTrimNative testOffExplicit
|
|
*/
|
|
|
|
/*
|
|
* @test id=testOffOnNonCompliantPlatforms
|
|
* @summary Test that trimming is correctly reported as unavailable if unavailable
|
|
* @requires vm.flagless
|
|
* @requires (os.family!="linux") | vm.musl
|
|
* @modules java.base/jdk.internal.misc
|
|
* @library /test/lib
|
|
* @build jdk.test.whitebox.WhiteBox
|
|
* @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
|
|
* @run driver TestTrimNative testOffOnNonCompliantPlatforms
|
|
*/
|
|
|
|
import jdk.test.lib.Platform;
|
|
import jdk.test.lib.process.OutputAnalyzer;
|
|
import jdk.test.lib.process.ProcessTools;
|
|
|
|
import java.io.IOException;
|
|
import java.util.*;
|
|
import java.util.regex.Matcher;
|
|
import java.util.regex.Pattern;
|
|
|
|
import jdk.test.whitebox.WhiteBox;
|
|
|
|
public class TestTrimNative {
|
|
|
|
// Actual RSS increase is a lot larger than 4 MB. Depends on glibc overhead, and NMT malloc headers in debug VMs.
|
|
// We need small-grained allocations to make sure they actually increase RSS (all touched) and to see the
|
|
// glibc-retaining-memory effect.
|
|
static final int szAllocations = 128;
|
|
static final int totalAllocationsSize = 128 * 1024 * 1024; // 128 MB total
|
|
static final int numAllocations = totalAllocationsSize / szAllocations;
|
|
|
|
static long[] ptrs = new long[numAllocations];
|
|
|
|
enum Unit {
|
|
B(1), K(1024), M(1024*1024), G(1024*1024*1024);
|
|
public final long size;
|
|
Unit(long size) { this.size = size; }
|
|
}
|
|
|
|
private static String[] prepareOptions(String[] extraVMOptions, String[] programOptions) {
|
|
List<String> allOptions = new ArrayList<String>();
|
|
if (extraVMOptions != null) {
|
|
allOptions.addAll(Arrays.asList(extraVMOptions));
|
|
}
|
|
allOptions.add("-Xmx128m");
|
|
allOptions.add("-Xms128m"); // Stabilize RSS
|
|
allOptions.add("-XX:+AlwaysPreTouch"); // Stabilize RSS
|
|
allOptions.add("-XX:+UnlockDiagnosticVMOptions"); // For whitebox
|
|
allOptions.add("-XX:+WhiteBoxAPI");
|
|
allOptions.add("-Xbootclasspath/a:.");
|
|
allOptions.add("-XX:-ExplicitGCInvokesConcurrent"); // Invoke explicit GC on System.gc
|
|
allOptions.add("-Xlog:trimnative=debug");
|
|
allOptions.add("--add-exports=java.base/jdk.internal.misc=ALL-UNNAMED");
|
|
if (programOptions != null) {
|
|
allOptions.addAll(Arrays.asList(programOptions));
|
|
}
|
|
return allOptions.toArray(new String[0]);
|
|
}
|
|
|
|
private static OutputAnalyzer runTestWithOptions(String[] extraOptions, String[] programOptions) throws IOException {
|
|
ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(prepareOptions(extraOptions, programOptions));
|
|
OutputAnalyzer output = new OutputAnalyzer(pb.start());
|
|
output.shouldHaveExitValue(0);
|
|
return output;
|
|
}
|
|
|
|
private static void checkExpectedLogMessages(OutputAnalyzer output, boolean expectEnabled,
|
|
int expectedInterval) {
|
|
if (expectEnabled) {
|
|
output.shouldContain("Periodic native trim enabled (interval: " + expectedInterval + " ms");
|
|
output.shouldContain("Native heap trimmer start");
|
|
} else {
|
|
output.shouldNotContain("Periodic native trim enabled");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Given JVM output, look for one or more log lines that describes a successful negative trim. The total amount
|
|
* of trims should be matching about what the test program allocated.
|
|
* @param output
|
|
* @param minTrimsExpected min number of periodic trim lines expected in UL log
|
|
* @param maxTrimsExpected min number of periodic trim lines expected in UL log
|
|
* @param strict: if true, expect RSS to go down; if false, just look for trims without looking at RSS.
|
|
*/
|
|
private static void parseOutputAndLookForNegativeTrim(OutputAnalyzer output, int minTrimsExpected,
|
|
int maxTrimsExpected, boolean strict) {
|
|
output.reportDiagnosticSummary();
|
|
List<String> lines = output.asLines();
|
|
Pattern pat = Pattern.compile(".*\\[trimnative\\] Periodic Trim \\(\\d+\\): (\\d+)([BKMG])->(\\d+)([BKMG]).*");
|
|
int numTrimsFound = 0;
|
|
long rssReductionTotal = 0;
|
|
for (String line : lines) {
|
|
Matcher mat = pat.matcher(line);
|
|
if (mat.matches()) {
|
|
long rss1 = Long.parseLong(mat.group(1)) * Unit.valueOf(mat.group(2)).size;
|
|
long rss2 = Long.parseLong(mat.group(3)) * Unit.valueOf(mat.group(4)).size;
|
|
if (rss1 > rss2) {
|
|
rssReductionTotal += (rss1 - rss2);
|
|
}
|
|
numTrimsFound ++;
|
|
}
|
|
if (numTrimsFound > maxTrimsExpected) {
|
|
throw new RuntimeException("Abnormal high number of periodic trim attempts found (more than " + maxTrimsExpected +
|
|
"). Does the interval setting not work?");
|
|
}
|
|
}
|
|
if (numTrimsFound < minTrimsExpected) {
|
|
throw new RuntimeException("We found fewer (periodic) trim lines in UL log than expected (expected at least " + minTrimsExpected +
|
|
", found " + numTrimsFound + ").");
|
|
}
|
|
System.out.println("Found " + numTrimsFound + " trims. Ok.");
|
|
if (strict && maxTrimsExpected > 0) {
|
|
// This is very fuzzy. Test program malloced X bytes, then freed them again and trimmed. But the log line prints change in RSS.
|
|
// Which, of course, is influenced by a lot of other factors. But we expect to see *some* reasonable reduction in RSS
|
|
// due to trimming.
|
|
float fudge = 0.5f;
|
|
// On ppc, we see a vastly diminished return (~3M reduction instead of ~200), I suspect because of the underlying
|
|
// 64k pages lead to a different geometry. Manual tests with larger reclaim sizes show that autotrim works. For
|
|
// this test, we just reduce the fudge factor.
|
|
if (Platform.isPPC()) { // le and be both
|
|
fudge = 0.01f;
|
|
}
|
|
long expectedMinimalReduction = (long) (totalAllocationsSize * fudge);
|
|
if (rssReductionTotal < expectedMinimalReduction) {
|
|
throw new RuntimeException("We did not see the expected RSS reduction in the UL log. Expected (with fudge)" +
|
|
" to see at least a combined reduction of " + expectedMinimalReduction + ".");
|
|
} else {
|
|
System.out.println("Found high enough RSS reduction from trims: " + rssReductionTotal);
|
|
}
|
|
}
|
|
}
|
|
|
|
static class Tester {
|
|
public static void main(String[] args) throws Exception {
|
|
long sleeptime = Long.parseLong(args[0]);
|
|
|
|
System.out.println("Will spike now...");
|
|
WhiteBox wb = WhiteBox.getWhiteBox();
|
|
for (int i = 0; i < numAllocations; i++) {
|
|
ptrs[i] = wb.NMTMalloc(szAllocations);
|
|
wb.preTouchMemory(ptrs[i], szAllocations);
|
|
}
|
|
for (int i = 0; i < numAllocations; i++) {
|
|
wb.NMTFree(ptrs[i]);
|
|
}
|
|
System.out.println("Done spiking.");
|
|
|
|
System.out.println("GC...");
|
|
System.gc();
|
|
|
|
// give GC time to react
|
|
System.out.println("Sleeping for " + sleeptime + " ms...");
|
|
Thread.sleep(sleeptime);
|
|
System.out.println("Done.");
|
|
}
|
|
}
|
|
|
|
public static void main(String[] args) throws Exception {
|
|
|
|
if (args.length == 0) {
|
|
throw new RuntimeException("Argument error");
|
|
}
|
|
|
|
boolean strictTesting = args[0].endsWith("Strict");
|
|
|
|
switch (args[0]) {
|
|
case "trimNative":
|
|
case "trimNativeStrict": {
|
|
long trimInterval = 500; // twice per second
|
|
long ms1 = System.currentTimeMillis();
|
|
OutputAnalyzer output = runTestWithOptions(
|
|
new String[] { "-XX:TrimNativeHeapInterval=" + trimInterval },
|
|
new String[] { TestTrimNative.Tester.class.getName(), "5000" }
|
|
);
|
|
long ms2 = System.currentTimeMillis();
|
|
long runtime_ms = ms2 - ms1;
|
|
|
|
checkExpectedLogMessages(output, true, 500);
|
|
|
|
long maxTrimsExpected = runtime_ms / trimInterval;
|
|
long minTrimsExpected = maxTrimsExpected / 2;
|
|
parseOutputAndLookForNegativeTrim(output, (int) minTrimsExpected, (int) maxTrimsExpected, strictTesting);
|
|
} break;
|
|
|
|
case "trimNativeHighInterval": {
|
|
OutputAnalyzer output = runTestWithOptions(
|
|
new String[] { "-XX:TrimNativeHeapInterval=" + Integer.MAX_VALUE },
|
|
new String[] { TestTrimNative.Tester.class.getName(), "5000" }
|
|
);
|
|
checkExpectedLogMessages(output, true, Integer.MAX_VALUE);
|
|
// We should not see any trims since the interval would prevent them
|
|
parseOutputAndLookForNegativeTrim(output, 0, 0, strictTesting);
|
|
} break;
|
|
|
|
case "trimNativeLowInterval":
|
|
case "trimNativeLowIntervalStrict": {
|
|
long ms1 = System.currentTimeMillis();
|
|
OutputAnalyzer output = runTestWithOptions(
|
|
new String[] { "-XX:TrimNativeHeapInterval=1" },
|
|
new String[] { TestTrimNative.Tester.class.getName(), "0" }
|
|
);
|
|
long ms2 = System.currentTimeMillis();
|
|
int maxTrimsExpected = (int)(ms2 - ms1); // 1ms trim interval
|
|
checkExpectedLogMessages(output, true, 1);
|
|
parseOutputAndLookForNegativeTrim(output, 1, (int)maxTrimsExpected, strictTesting);
|
|
} break;
|
|
|
|
case "testOffOnNonCompliantPlatforms": {
|
|
OutputAnalyzer output = runTestWithOptions(
|
|
new String[] { "-XX:TrimNativeHeapInterval=1" },
|
|
new String[] { "-version" }
|
|
);
|
|
checkExpectedLogMessages(output, false, 0);
|
|
parseOutputAndLookForNegativeTrim(output, 0, 0, strictTesting);
|
|
// The following output is expected to be printed with warning level, so it should not need -Xlog
|
|
output.shouldContain("[warning][trimnative] Native heap trim is not supported on this platform");
|
|
} break;
|
|
|
|
case "testOffExplicit": {
|
|
OutputAnalyzer output = runTestWithOptions(
|
|
new String[] { "-XX:TrimNativeHeapInterval=0" },
|
|
new String[] { "-version" }
|
|
);
|
|
checkExpectedLogMessages(output, false, 0);
|
|
parseOutputAndLookForNegativeTrim(output, 0, 0, strictTesting);
|
|
} break;
|
|
|
|
case "testOffByDefault": {
|
|
OutputAnalyzer output = runTestWithOptions(null, new String[] { "-version" } );
|
|
checkExpectedLogMessages(output, false, 0);
|
|
parseOutputAndLookForNegativeTrim(output, 0, 0, strictTesting);
|
|
} break;
|
|
|
|
default:
|
|
throw new RuntimeException("Invalid test " + args[0]);
|
|
|
|
}
|
|
}
|
|
}
|