c5e0464098
Reviewed-by: cjplummer, amenkov
216 lines
9.1 KiB
Java
216 lines
9.1 KiB
Java
/*
|
|
* Copyright (c) 2022, 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
|
|
* @bug 8256811
|
|
* @modules java.base/jdk.internal.org.objectweb.asm
|
|
* java.base/jdk.internal.misc
|
|
* @library /test/lib
|
|
* @build jdk.test.whitebox.WhiteBox
|
|
* @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
|
|
* @run main/othervm/native ClassUnloadEventTest run
|
|
*/
|
|
|
|
import jdk.internal.org.objectweb.asm.ClassWriter;
|
|
import jdk.internal.org.objectweb.asm.Label;
|
|
import jdk.internal.org.objectweb.asm.MethodVisitor;
|
|
import jdk.internal.org.objectweb.asm.Opcodes;
|
|
import jdk.test.lib.classloader.ClassUnloadCommon;
|
|
|
|
import com.sun.jdi.*;
|
|
import com.sun.jdi.connect.*;
|
|
import com.sun.jdi.event.*;
|
|
import com.sun.jdi.request.*;
|
|
|
|
import java.util.*;
|
|
import java.io.*;
|
|
|
|
public class ClassUnloadEventTest {
|
|
static final String CLASS_NAME_PREFIX = "SampleClass__";
|
|
static final String CLASS_NAME_ALT_PREFIX = CLASS_NAME_PREFIX + "Alt__";
|
|
static final int NUM_CLASSES = 10;
|
|
static final int NUM_ALT_CLASSES = NUM_CLASSES / 2;
|
|
|
|
public static void main(String[] args) throws Exception {
|
|
if (args.length == 0) {
|
|
try {
|
|
runDebuggee();
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
throw e;
|
|
}
|
|
} else {
|
|
runDebugger();
|
|
}
|
|
}
|
|
|
|
private static class TestClassLoader extends ClassLoader implements Opcodes {
|
|
private static byte[] generateSampleClass(String name) {
|
|
ClassWriter cw = new ClassWriter(0);
|
|
|
|
cw.visit(52, ACC_SUPER | ACC_PUBLIC, name, null, "java/lang/Object", null);
|
|
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "m", "()V", null, null);
|
|
mv.visitCode();
|
|
mv.visitInsn(RETURN);
|
|
mv.visitMaxs(0, 0);
|
|
mv.visitEnd();
|
|
cw.visitEnd();
|
|
return cw.toByteArray();
|
|
}
|
|
|
|
@Override
|
|
protected Class<?> findClass(String name) throws ClassNotFoundException {
|
|
if (name.startsWith(CLASS_NAME_PREFIX)) {
|
|
byte[] bytecode = generateSampleClass(name);
|
|
return defineClass(name, bytecode, 0, bytecode.length);
|
|
} else {
|
|
return super.findClass(name);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void runDebuggee() {
|
|
System.out.println("Running debuggee");
|
|
ClassLoader loader = new TestClassLoader();
|
|
for (int index = 0; index < NUM_CLASSES; index++) {
|
|
try {
|
|
if (index < NUM_ALT_CLASSES) {
|
|
Class.forName(CLASS_NAME_ALT_PREFIX + index, true, loader);
|
|
} else {
|
|
Class.forName(CLASS_NAME_PREFIX + index, true, loader);
|
|
}
|
|
} catch (Exception e) {
|
|
throw new RuntimeException("Failed to create Sample class", e);
|
|
}
|
|
}
|
|
loader = null;
|
|
|
|
// Do a short delay to make sure that the debug agent is done processing all
|
|
// ClassPrepare events. Otherwise the debug agent might still be holding on to
|
|
// a reference to a class, which will prevent it from unloading during the GC.
|
|
try {
|
|
Thread.sleep(5000);
|
|
} catch (InterruptedException e) {
|
|
}
|
|
|
|
// Trigger class unloading
|
|
ClassUnloadCommon.triggerUnloading();
|
|
|
|
// We rely on JVMTI to post all pending ObjectFree events at VM shutdown.
|
|
// It will trigger the JDWP agent to synthesize expected ClassUnloadEvents events.
|
|
System.out.println("Exiting debuggee");
|
|
}
|
|
|
|
private static void runDebugger() throws Exception {
|
|
System.out.println("Running debugger");
|
|
HashSet<String> unloadedSampleClasses = new HashSet<>();
|
|
HashSet<String> unloadedSampleClasses_alt = new HashSet<>();
|
|
VirtualMachine vm = null;
|
|
vm = connectAndLaunchVM();
|
|
ClassUnloadRequest classUnloadRequest = vm.eventRequestManager().createClassUnloadRequest();
|
|
classUnloadRequest.addClassFilter(CLASS_NAME_PREFIX + "*");
|
|
classUnloadRequest.enable();
|
|
|
|
ClassUnloadRequest classUnloadRequest_alt = vm.eventRequestManager().createClassUnloadRequest();
|
|
classUnloadRequest_alt.addClassFilter(CLASS_NAME_ALT_PREFIX + "*");
|
|
classUnloadRequest_alt.enable();
|
|
|
|
EventSet eventSet = null;
|
|
boolean exited = false;
|
|
while (!exited && (eventSet = vm.eventQueue().remove()) != null) {
|
|
System.out.println("EventSet: " + eventSet);
|
|
for (Event event : eventSet) {
|
|
if (event instanceof ClassUnloadEvent) {
|
|
String className = ((ClassUnloadEvent)event).className();
|
|
|
|
// The unloaded class should always match CLASS_NAME_PREFIX.
|
|
if (className.indexOf(CLASS_NAME_PREFIX) == -1) {
|
|
throw new RuntimeException("FAILED: Unexpected unloaded class: " + className);
|
|
}
|
|
|
|
// Unloaded classes with ALT names should only occur on the classUnloadRequest_alt.
|
|
if (event.request() == classUnloadRequest_alt) {
|
|
unloadedSampleClasses_alt.add(className);
|
|
if (className.indexOf(CLASS_NAME_ALT_PREFIX) == -1) {
|
|
throw new RuntimeException("FAILED: non-alt class unload event for classUnloadRequest_alt.");
|
|
}
|
|
} else {
|
|
unloadedSampleClasses.add(className);
|
|
}
|
|
|
|
// If the unloaded class matches the ALT prefix, then we should have
|
|
// unload events in this EventSet for each of the two ClassUnloadRequesta.
|
|
int expectedEventSetSize;
|
|
if (className.indexOf(CLASS_NAME_ALT_PREFIX) != -1) {
|
|
expectedEventSetSize = 2;
|
|
} else {
|
|
expectedEventSetSize = 1;
|
|
}
|
|
if (eventSet.size() != expectedEventSetSize) {
|
|
throw new RuntimeException("FAILED: Unexpected eventSet size: " + eventSet.size());
|
|
}
|
|
}
|
|
|
|
if (event instanceof VMDeathEvent) {
|
|
exited = true;
|
|
break;
|
|
}
|
|
}
|
|
eventSet.resume();
|
|
}
|
|
|
|
/* Dump debuggee output. */
|
|
Process p = vm.process();
|
|
BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));
|
|
BufferedReader err = new BufferedReader(new InputStreamReader(p.getErrorStream()));
|
|
String line = in.readLine();
|
|
while (line != null) {
|
|
System.out.println("stdout: " + line);
|
|
line = in.readLine();
|
|
}
|
|
line = err.readLine();
|
|
while (line != null) {
|
|
System.out.println("stderr: " + line);
|
|
line = err.readLine();
|
|
}
|
|
|
|
if (unloadedSampleClasses.size() != NUM_CLASSES) {
|
|
throw new RuntimeException("Wrong number of class unload events: expected " + NUM_CLASSES + " got " + unloadedSampleClasses.size());
|
|
}
|
|
if (unloadedSampleClasses_alt.size() != NUM_ALT_CLASSES) {
|
|
throw new RuntimeException("Wrong number of alt class unload events: expected " + NUM_ALT_CLASSES + " got " + unloadedSampleClasses_alt.size());
|
|
}
|
|
}
|
|
|
|
private static VirtualMachine connectAndLaunchVM() throws IOException,
|
|
IllegalConnectorArgumentsException,
|
|
VMStartException {
|
|
LaunchingConnector launchingConnector = Bootstrap.virtualMachineManager().defaultConnector();
|
|
Map<String, Connector.Argument> arguments = launchingConnector.defaultArguments();
|
|
arguments.get("main").setValue(ClassUnloadEventTest.class.getName());
|
|
arguments.get("options").setValue("--add-exports java.base/jdk.internal.org.objectweb.asm=ALL-UNNAMED -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xlog:class+unload=info -Xlog:gc");
|
|
return launchingConnector.launch(arguments);
|
|
}
|
|
}
|