diff --git a/test/jdk/jdk/jfr/jcmd/TestJcmdDumpPathToGCRootsDFSArrayChunking.java b/test/jdk/jdk/jfr/jcmd/TestJcmdDumpPathToGCRootsDFSArrayChunking.java new file mode 100644 index 00000000000..0872071c5ad --- /dev/null +++ b/test/jdk/jdk/jfr/jcmd/TestJcmdDumpPathToGCRootsDFSArrayChunking.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2025, 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. + */ + +package jdk.jfr.jcmd; + +import java.util.LinkedList; +import java.util.Random; + +/** + * @test + * @summary Test dumping with path-to-gc-roots and DFS only, excercise the array chunking path + * @requires vm.hasJFR & vm.flagless + * @modules jdk.jfr/jdk.jfr.internal.test + * @library /test/lib /test/jdk + * + * @run main/othervm -Xmx1g jdk.jfr.jcmd.TestJcmdDumpPathToGCRootsDFSArrayChunking + */ +public class TestJcmdDumpPathToGCRootsDFSArrayChunking extends TestJcmdDumpPathToGCRootsDFSBase { + + // Tests that array chunking works correctly. We create an object array; link a second object array + // into it at its middle; link a third object array into the second at its end; fill the third array with + // many objects. GC root search should walk successfully through the middle of the first and the end of + // the second to the third array and sample a good portion of its objects. + + private static final int TOTAL_OBJECTS = 10_000_000; + private static final int arrayChunkSize = 64; // keep in sync with dfsClosure.cpp + private Object[] leak; + + @Override + protected final void buildLeak() { + final int arraySize = (arrayChunkSize * 10) + arrayChunkSize / 2; + Object[] first = new Object[arraySize]; + Object[] second = new Object[arraySize]; + Object[] third = new Object[TOTAL_OBJECTS]; + for (int i = 0; i < third.length; i++) { + third[i] = new Object(); + } + second[second.length - 1] = third; + first[first.length / 2] = second; + leak = first; + } + + protected final void clearLeak() { + leak = null; + System.gc(); + } + + public static void main(String[] args) throws Exception { + new TestJcmdDumpPathToGCRootsDFSArrayChunking().testDump("TestJcmdDumpPathToGCRootsDFSArrayChunking", 30); + } + +} diff --git a/test/jdk/jdk/jfr/jcmd/TestJcmdDumpPathToGCRootsDFSBase.java b/test/jdk/jdk/jfr/jcmd/TestJcmdDumpPathToGCRootsDFSBase.java new file mode 100644 index 00000000000..7fbb3520404 --- /dev/null +++ b/test/jdk/jdk/jfr/jcmd/TestJcmdDumpPathToGCRootsDFSBase.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2025, 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. + */ + +package jdk.jfr.jcmd; + +import jdk.jfr.Enabled; +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordedObject; +import jdk.jfr.consumer.RecordingFile; +import jdk.jfr.internal.test.WhiteBox; +import jdk.test.lib.jfr.EventNames; + +import java.io.File; +import java.io.IOException; +import java.util.*; + +public abstract class TestJcmdDumpPathToGCRootsDFSBase { + + protected abstract void buildLeak(); + protected abstract void clearLeak(); + + protected void testDump(String jfrFileName, int minChainsExpected) throws Exception { + WhiteBox.setWriteAllObjectSamples(true); + WhiteBox.setSkipBFS(true); + final String settingName = EventNames.OldObjectSample + "#" + "cutoff"; + Map settings = Collections.singletonMap(settingName, "infinity"); + final String pathToGcRoots = "path-to-gc-roots=true"; + int numTries = 10; + while (--numTries >= 0) { + try (Recording r = new Recording()) { + Map p = new HashMap<>(settings); + p.put(EventNames.OldObjectSample + "#" + Enabled.NAME, "true"); + r.setName("dodo"); + r.setSettings(p); + r.setToDisk(true); + r.start(); + clearLeak(); + System.out.println("Recording id: " + r.getId()); + System.out.println("Settings: " + settings.toString()); + System.out.println("Command: JFR.dump " + pathToGcRoots); + buildLeak(); + System.gc(); + System.gc(); + File recording = new File(jfrFileName + r.getId() + ".jfr"); + recording.delete(); + JcmdHelper.jcmd("JFR.dump", "name=dodo", pathToGcRoots, "filename=" + recording.getAbsolutePath()); + r.setSettings(Collections.emptyMap()); + List events = RecordingFile.readAllEvents(recording.toPath()); + if (events.isEmpty()) { + System.out.println("No events found in recording. Retrying."); + continue; + } + int chains = countChains(events); + if (chains < minChainsExpected) { + System.out.println(events); + System.out.println("Not enough chains found (" + chains + "), retrying."); + continue; + } + return; // Success + } + } + throw new RuntimeException("Failed"); + } + + private static int countChains(List events) throws IOException { + int found = 0; + for (RecordedEvent e : events) { + RecordedObject ro = e.getValue("object"); + if (ro.getValue("referrer") != null) { + found++; + } + } + System.out.println("Found chains: " + found); + return found; + } + +} diff --git a/test/jdk/jdk/jfr/jcmd/TestJcmdDumpPathToGCRootsDFSWithSmallStack.java b/test/jdk/jdk/jfr/jcmd/TestJcmdDumpPathToGCRootsDFSWithSmallStack.java index 64d00039eb2..1040b3f7cb5 100644 --- a/test/jdk/jdk/jfr/jcmd/TestJcmdDumpPathToGCRootsDFSWithSmallStack.java +++ b/test/jdk/jdk/jfr/jcmd/TestJcmdDumpPathToGCRootsDFSWithSmallStack.java @@ -22,19 +22,8 @@ */ package jdk.jfr.jcmd; - -import java.io.File; -import java.io.IOException; import java.util.*; -import jdk.jfr.Enabled; -import jdk.jfr.Recording; -import jdk.jfr.consumer.RecordedEvent; -import jdk.jfr.consumer.RecordedObject; -import jdk.jfr.consumer.RecordingFile; -import jdk.jfr.internal.test.WhiteBox; -import jdk.test.lib.jfr.EventNames; - /** * @test * @summary Test dumping with path-to-gc-roots and DFS only with a very small stacksize for the VM thread @@ -42,12 +31,15 @@ import jdk.test.lib.jfr.EventNames; * @modules jdk.jfr/jdk.jfr.internal.test * @library /test/lib /test/jdk * - * @run main/othervm -Xmx1g -XX:VMThreadStackSize=128 jdk.jfr.jcmd.TestJcmdDumpPathToGCRootsDFSWithSmallStack dfs-only + * @run main/othervm -Xmx1g -XX:VMThreadStackSize=128 jdk.jfr.jcmd.TestJcmdDumpPathToGCRootsDFSWithSmallStack */ -public class TestJcmdDumpPathToGCRootsDFSWithSmallStack { +public class TestJcmdDumpPathToGCRootsDFSWithSmallStack extends TestJcmdDumpPathToGCRootsDFSBase { // Tests the new non-recursive implementation of the JFR leak profiler path-to-gc-roots-search. + // An non-zero exit code, together with a missing hs-err file or possibly a missing jfr file, + // indicates a native stack overflow happened and is a fail condition for this test. + // We build up an array of linked lists, each containing enough entries for DFS search to // max out max_dfs_depth (but not greatly surpass it). // The old recursive implementation, started with such a small stack, will fail to reach @@ -62,77 +54,10 @@ public class TestJcmdDumpPathToGCRootsDFSWithSmallStack { private static final int TOTAL_OBJECTS = 10_000_000; private static final int OBJECTS_PER_LIST = 5_000; - private static LinkedList[] leak; + private LinkedList[] leak; - public static void main(String[] args) throws Exception { - WhiteBox.setWriteAllObjectSamples(true); - String settingName = EventNames.OldObjectSample + "#" + "cutoff"; - - int leakedObjectCount = 10_000_000; - boolean skipBFS = true; - - WhiteBox.setSkipBFS(skipBFS); - testDump(Collections.singletonMap(settingName, "infinity"), leakedObjectCount); - } - - private static void testDump(Map settings, int leakedObjectCount) throws Exception { - final String pathToGcRoots = "path-to-gc-roots=true"; - int numTries = 3; - while (--numTries >= 0) { - try (Recording r = new Recording()) { - Map p = new HashMap<>(settings); - p.put(EventNames.OldObjectSample + "#" + Enabled.NAME, "true"); - r.setName("dodo"); - r.setSettings(p); - r.setToDisk(true); - r.start(); - clearLeak(); - System.out.println("Recording id: " + r.getId()); - System.out.println("Settings: " + settings.toString()); - System.out.println("Command: JFR.dump " + pathToGcRoots); - buildLeak(leakedObjectCount); - System.gc(); - System.gc(); - File recording = new File("TestJcmdDumpPathToGCRootsDFSWithSmallStack" + r.getId() + ".jfr"); - recording.delete(); - JcmdHelper.jcmd("JFR.dump", "name=dodo", pathToGcRoots, "filename=" + recording.getAbsolutePath()); - r.setSettings(Collections.emptyMap()); - List events = RecordingFile.readAllEvents(recording.toPath()); - if (events.isEmpty()) { - System.out.println("No events found in recording. Retrying."); - continue; - } - int chains = countChains(events); - final int minNumberOfChains = 30; // very conservative; normally ~250 - if (chains < minNumberOfChains) { - System.out.println(events); - System.out.println("Not enough chains found (" + chains + "), retrying."); - continue; - } - return; // Success - } - } - throw new RuntimeException("Failed"); - } - - private static void clearLeak() { - leak = null; - System.gc(); - } - - private static int countChains(List events) throws IOException { - int found = 0; - for (RecordedEvent e : events) { - RecordedObject ro = e.getValue("object"); - if (ro.getValue("referrer") != null) { - found++; - } - } - System.out.println("Found chains: " + found); - return found; - } - - private static void buildLeak(int objectCount) { + @Override + protected final void buildLeak() { leak = new LinkedList[TOTAL_OBJECTS/OBJECTS_PER_LIST]; for (int i = 0; i < leak.length; i++) { leak[i] = new LinkedList(); @@ -141,4 +66,14 @@ public class TestJcmdDumpPathToGCRootsDFSWithSmallStack { } } } + + protected final void clearLeak() { + leak = null; + System.gc(); + } + + public static void main(String[] args) throws Exception { + new TestJcmdDumpPathToGCRootsDFSWithSmallStack().testDump("TestJcmdDumpPathToGCRootsDFSWithSmallStack", 30); + } + }