jdk-24/test/hotspot/jtreg/runtime/stringtable/StringTableCleaningTest.java
2024-01-03 08:53:01 +00:00

292 lines
11 KiB
Java

/*
* Copyright (c) 2020, 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.
*/
/*
* @test
* @requires vm.gc != "Epsilon"
* @summary Stress the string table and cleaning.
* Test argument is the approximate number of seconds to run.
* @library /test/lib
* @modules java.base/jdk.internal.misc
* @build jdk.test.whitebox.WhiteBox
* @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
* @run main/othervm
* -Xbootclasspath/a:.
* -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI
* runtime.stringtable.StringTableCleaningTest 30
*/
package runtime.stringtable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.process.ProcessTools;
import jdk.test.whitebox.gc.GC;
public class StringTableCleaningTest {
public static void main(String[] args) throws Exception {
List<String> subargs = new ArrayList<String>();
subargs.addAll(List.of("-Xlog:gc,gc+start,stringtable*=trace", "-Xmx1g"));
subargs.add(Tester.class.getName());
subargs.addAll(Arrays.asList(args));
OutputAnalyzer output = ProcessTools.executeTestJava(subargs);
output.shouldHaveExitValue(0);
checkOutput(output);
}
private static int fail(String msg) throws Exception {
throw new RuntimeException(msg);
}
// Recognizing GC start and end log lines.
private static final String gcPrefix = "\\[info\\s*\\]\\[gc";
private static final String gcMiddle = "\\s*\\] GC\\(\\p{Digit}+\\) ";
private static final String gcStartPrefix = gcPrefix + ",start" + gcMiddle;
private static final String gcEndPrefix = gcPrefix + gcMiddle;
// Suffix for SerialGC and ParallelGC.
private static final String spSuffix = "Pause";
// All G1 pauses except Cleanup do weak reference clearing.
private static final String g1Suffix = "Pause(?! Cleanup)";
// For ZGC only major collections clean the string table. ZGC prints the
// start message without using the start tag, hence the special prefix.
private static final String zStartPrefix = gcPrefix + gcMiddle;
private static final String zStartSuffix = "Major Collection \\(.*\\)$";
private static final String zEndSuffix = "Major Collection \\(.*\\) .*->.*$";
// Suffix for ZGC (non generational).
private static final String xStartSuffix = "Garbage Collection (.*)$";
private static final String xEndSuffix = "Garbage Collection (.*) .*->.*$";
// Suffix for Shenandoah.
private static final String shenSuffix = "Concurrent weak roots";
private static String getGcStartString() {
if (GC.Serial.isSelected() || GC.Parallel.isSelected()) {
return gcStartPrefix + spSuffix;
} else if (GC.G1.isSelected()) {
return gcStartPrefix + g1Suffix;
} else if (GC.Z.isSelected()) {
return "(" + zStartPrefix + zStartSuffix + ")|(" + gcStartPrefix + xStartSuffix + ")";
} else if (GC.Shenandoah.isSelected()) {
return gcStartPrefix + shenSuffix;
} else {
return "unsupported GC";
}
}
private static String getGcEndString() {
if (GC.Serial.isSelected() || GC.Parallel.isSelected()) {
return gcEndPrefix + spSuffix;
} else if (GC.G1.isSelected()) {
return gcEndPrefix + g1Suffix;
} else if (GC.Z.isSelected()) {
return gcEndPrefix + "(" + zEndSuffix + ")|(" + xEndSuffix + ")";
} else if (GC.Shenandoah.isSelected()) {
return gcEndPrefix + shenSuffix;
} else {
return "unsupported GC";
}
}
private static Pattern getGcStartPattern() {
return Pattern.compile(getGcStartString());
}
private static Pattern getGcEndPattern() {
return Pattern.compile(getGcEndString());
}
private static final Pattern pGcStart = getGcStartPattern();
private static final Pattern pGcEnd = getGcEndPattern();
// Recognizing StringTable GC callback log lines.
private static final Pattern pCallback =
Pattern.compile("\\[trace\\s*\\]\\[stringtable\\s*\\] Uncleaned items:");
private static boolean matchesPattern(String line, Pattern regex) {
return regex.matcher(line).find();
}
private static boolean matchesStart(String line) {
return matchesPattern(line, pGcStart);
}
private static boolean matchesEnd(String line) {
return matchesPattern(line, pGcEnd);
}
private static boolean matchesCallback(String line) {
return matchesPattern(line, pCallback);
}
// Search the lines for the first GC start log line in lines, starting
// from fromIndex. Returns the index of that line, or -1 if no GC start
// line found. Throws if a callback or GC end line is found first.
private static int findStart(List<String> lines, int fromIndex)
throws Exception
{
for (int i = fromIndex; i < lines.size(); ++i) {
String line = lines.get(i);
if (matchesStart(line)) {
return i;
} else if (matchesEnd(line)) {
fail("End without Start: " + i);
} else if (matchesCallback(line)) {
fail("Callback without Start: " + i);
}
}
return -1;
}
// Search the lines for the first callback log line in lines, starting
// after gcStart. Returns the index of that line, or -1 if no callback
// line is found (concurrent GC could start but not complete). Throws
// if a GC start or GC end log line is found first.
private static int findCallback(List<String> lines, int gcStart)
throws Exception
{
for (int i = gcStart + 1; i < lines.size(); ++i) {
String line = lines.get(i);
if (matchesCallback(line)) {
return i;
} else if (matchesEnd(line)) {
fail("Missing Callback in [" + gcStart + ", " + i + "]");
} else if (matchesStart(line)) {
fail("Two Starts: " + gcStart + ", " + i);
}
}
return -1;
}
// Search the lines for the first GC end log line in lines, starting
// after callback. Returns the index of that line, or -1 if no GC end
// line is found (concurrent GC could start but not complete). Throws
// if a GC start or a callback log line is found first.
private static int findEnd(List<String> lines, int gcStart, int callback)
throws Exception
{
for (int i = callback + 1; i < lines.size(); ++i) {
String line = lines.get(i);
if (matchesEnd(line)) {
return i;
} else if (matchesStart(line)) {
fail("Missing End for Start: " + gcStart + " at " + i);
} else if (matchesCallback(line)) {
fail("Multiple Callbacks for Start: " + gcStart + " at " + i);
}
}
return -1;
}
private static int check(List<String> lines, int fromIndex) throws Exception {
int gcStart = findStart(lines, fromIndex);
if (gcStart < 0) return -1;
int callback = findCallback(lines, gcStart);
if (callback < 0) return -1;
int gcEnd = findEnd(lines, gcStart, callback);
if (gcEnd < 0) return -1;
return gcEnd + 1;
}
private static void checkOutput(OutputAnalyzer output) throws Exception {
List<String> lines = output.asLines();
int count = -1;
int i = 0;
try {
for ( ; i >= 0; i = check(lines, i)) { ++count; }
} finally {
if (i < 0) {
System.out.println("Output check passed with " + count + " GCs");
} else {
System.out.println("--- Output check failed: " + count + " -----");
System.out.println(output.getOutput());
}
}
}
static class Tester {
private static volatile boolean stopRequested = false;
private static final TimeUnit durationUnits = TimeUnit.SECONDS;
public static void main(String[] args) throws Exception {
long duration = Long.parseLong(args[0]);
runTest(duration);
}
public static void runTest(long duration) throws Exception {
ScheduledExecutorService scheduler =
Executors.newScheduledThreadPool(1);
try {
ScheduledFuture<?> stopper =
scheduler.schedule(() -> stopRequested = true,
duration,
durationUnits);
try {
stringMaker(10000000, 100000, 50000);
} finally {
stopper.cancel(false);
}
} finally {
scheduler.shutdownNow();
}
}
private static void stringMaker(int maxSize, int growStep, int shrinkStep)
throws Exception
{
long stringNum = 0;
while (true) {
LinkedList<String> list = new LinkedList<String>();
for (int i = 0; i < maxSize; ++i, ++stringNum) {
if (stopRequested) {
return;
}
if ((i != 0) && ((i % growStep) == 0)) {
list.subList(0, shrinkStep).clear();
}
list.push(Long.toString(stringNum).intern());
}
// For generational collectors, try to move current list
// contents to old-gen before dropping the list.
System.gc();
}
}
}
}