cbe329b90a
Reviewed-by: lkorinth, lmesnik
292 lines
11 KiB
Java
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();
|
|
}
|
|
}
|
|
}
|
|
}
|