2023-12-13 18:47:04 +00:00

301 lines
10 KiB
Java

/*
* Copyright (c) 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.
*/
import java.io.File;
import java.lang.ref.Reference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import jdk.test.lib.Asserts;
import jdk.test.lib.JDKToolLauncher;
import jdk.test.lib.apps.LingeredApp;
import jdk.test.lib.process.ProcessTools;
import jdk.test.lib.hprof.model.JavaClass;
import jdk.test.lib.hprof.model.JavaHeapObject;
import jdk.test.lib.hprof.model.Root;
import jdk.test.lib.hprof.model.Snapshot;
import jdk.test.lib.hprof.model.StackFrame;
import jdk.test.lib.hprof.model.StackTrace;
import jdk.test.lib.hprof.model.ThreadObject;
import jdk.test.lib.hprof.parser.Reader;
/**
* @test id=default
* @requires vm.jvmti
* @requires vm.continuations
* @library /test/lib
* @run main VThreadInHeapDump
*/
/**
* @test id=no-vmcontinuations
* @requires vm.jvmti
* @library /test/lib
* @comment pass extra VM arguments as the test arguments
* @run main VThreadInHeapDump
* -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations
*/
class VThreadInHeapDumpTarg extends LingeredApp {
public static class VThreadUnmountedReferenced {
}
public static class VThreadMountedReferenced {
}
public static class PThreadReferenced {
}
public class ThreadBase {
private volatile boolean threadReady = false;
protected void ready() {
threadReady = true;
}
public void waitReady() {
while (!threadReady) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
}
}
}
}
public class VthreadUnmounted extends ThreadBase implements Runnable {
public void run() {
Object referenced = new VThreadUnmountedReferenced();
ready();
// The thread will be unmounted in awaitToStop().
awaitToStop();
Reference.reachabilityFence(referenced);
}
}
public class VthreadMounted extends ThreadBase implements Runnable {
int dummy = -1;
public void run() {
Object referenced = new VThreadMountedReferenced();
ready();
// Don't give a chance for the thread to unmount.
while (!timeToStop) {
if (++dummy == 10000) {
dummy = 0;
}
}
Reference.reachabilityFence(referenced);
}
}
public class Pthread extends ThreadBase implements Runnable {
public void run() {
Object referenced = new PThreadReferenced();
ready();
awaitToStop();
Reference.reachabilityFence(referenced);
}
}
CountDownLatch timeToStopLatch = new CountDownLatch(1);
volatile boolean timeToStop = false;
void awaitToStop() {
try {
timeToStopLatch.await();
} catch (InterruptedException e) {
}
}
private void runTest(String[] args) {
try {
// Unmounted virtual thread.
VthreadUnmounted vthreadUnmounted = new VthreadUnmounted();
Thread.ofVirtual().start(vthreadUnmounted);
vthreadUnmounted.waitReady();
// Mounted virtual thread.
VthreadMounted vthreadMounted = new VthreadMounted();
Thread.ofVirtual().start(vthreadMounted);
vthreadMounted.waitReady();
// Platform thread.
Pthread pthread = new Pthread();
Thread.ofPlatform().start(pthread);
pthread.waitReady();
// We are ready.
LingeredApp.main(args);
} finally {
// Signal all threads to finish.
timeToStop = true;
timeToStopLatch.countDown();
}
}
public static void main(String[] args) {
VThreadInHeapDumpTarg test = new VThreadInHeapDumpTarg();
test.runTest(args);
}
}
public class VThreadInHeapDump {
// test arguments are extra VM options for target process
public static void main(String[] args) throws Exception {
File dumpFile = new File("Myheapdump.hprof");
createDump(dumpFile, args);
verifyDump(dumpFile);
}
private static void createDump(File dumpFile, String[] extraOptions) throws Exception {
LingeredApp theApp = null;
try {
theApp = new VThreadInHeapDumpTarg();
List<String> extraVMArgs = new ArrayList<>();
extraVMArgs.add("-Djdk.virtualThreadScheduler.parallelism=1");
extraVMArgs.add("-Xlog:heapdump");
extraVMArgs.addAll(Arrays.asList(extraOptions));
LingeredApp.startApp(theApp, extraVMArgs.toArray(new String[0]));
//jcmd <pid> GC.heap_dump <file_path>
JDKToolLauncher launcher = JDKToolLauncher
.createUsingTestJDK("jcmd")
.addToolArg(Long.toString(theApp.getPid()))
.addToolArg("GC.heap_dump")
.addToolArg(dumpFile.getAbsolutePath());
Process p = ProcessTools.startProcess("jcmd", new ProcessBuilder(launcher.getCommand()));
// If something goes wrong with heap dumping most likely we'll get crash of the target VM.
while (!p.waitFor(5, TimeUnit.SECONDS)) {
if (!theApp.getProcess().isAlive()) {
log("ERROR: target VM died, killing jcmd...");
p.destroyForcibly();
throw new Exception("Target VM died");
}
}
if (p.exitValue() != 0) {
throw new Exception("Jcmd exited with code " + p.exitValue());
}
} finally {
LingeredApp.stopApp(theApp);
}
}
private static void verifyDump(File dumpFile) throws Exception {
Asserts.assertTrue(dumpFile.exists(), "Heap dump file not found.");
log("Reading " + dumpFile + "...");
try (Snapshot snapshot = Reader.readFile(dumpFile.getPath(), true, 0)) {
log("Resolving snapshot...");
snapshot.resolve(true);
log("Snapshot resolved.");
// Log all threads with stack traces and stack references.
List<ThreadObject> threads = snapshot.getThreads();
List<Root> roots = Collections.list(snapshot.getRoots());
log("Threads:");
for (ThreadObject thread: threads) {
StackTrace st = thread.getStackTrace();
StackFrame[] frames = st.getFrames();
log("thread " + thread.getIdString() + ", " + frames.length + " frames");
List<Root> stackRoots = findStackRoot(roots, thread);
for (int i = 0; i < frames.length; i++) {
log(" - [" + i + "] "
+ frames[i].getClassName() + "." + frames[i].getMethodName()
+ frames[i].getMethodSignature()
+ " (" + frames[i].getSourceFileName()
+ ":" + frames[i].getLineNumber() + ")");
for (Root r: stackRoots) {
StackFrame[] rootFrames = r.getStackTrace().getFrames();
// the frame this local belongs to
StackFrame frame = rootFrames[rootFrames.length - 1];
if (frame == frames[i]) {
JavaHeapObject obj = snapshot.findThing(r.getId());
JavaClass objClass = obj.getClazz();
log(" " + r.getDescription() + ": " + objClass.getName());
}
}
}
}
// Verify objects from thread stacks are dumped.
test(snapshot, VThreadInHeapDumpTarg.VThreadMountedReferenced.class);
test(snapshot, VThreadInHeapDumpTarg.PThreadReferenced.class);
test(snapshot, VThreadInHeapDumpTarg.VThreadUnmountedReferenced.class);
}
}
private static List<Root> findStackRoot(List<Root> roots, ThreadObject thread) {
List<Root> result = new ArrayList<>();
for (Root root: roots) {
if (root.getReferrerId() == thread.getId()) {
result.add(root);
}
}
return result;
}
private static void test(Snapshot snapshot, String className) {
log("Testing " + className + "...");
JavaClass jClass = snapshot.findClass(className);
if (jClass == null) {
throw new RuntimeException("'" + className + "' not found");
}
int instanceCount = jClass.getInstancesCount(false);
if (instanceCount != 1) {
throw new RuntimeException("Expected 1 instance, " + instanceCount + " instances found");
}
// There is the only instance.
JavaHeapObject heapObj = jClass.getInstances(false).nextElement();
Root root = heapObj.getRoot();
if (root == null) {
throw new RuntimeException("No root for " + className + " instance");
}
log(" root: " + root.getDescription());
JavaHeapObject referrer = root.getReferrer();
log(" referrer: " + referrer);
}
private static void test(Snapshot snapshot, Class cls) {
test(snapshot, cls.getName());
}
private static void log(Object s) {
System.out.println(s);
}
}