| /* |
| * 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(); |
| } |
| } |
| } |
| } |