/* * 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 subargs = new ArrayList(); 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 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 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 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 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 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 list = new LinkedList(); 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(); } } } }