e0d361cea9
Reviewed-by: lancea, wetmore, naoto, iris, kevinw, xuelei
418 lines
15 KiB
Java
418 lines
15 KiB
Java
/*
|
|
* Copyright (c) 2016 Red Hat Inc.
|
|
*
|
|
* 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 8153711
|
|
* @summary JDWP: Memory Leak (global references not deleted after invokeMethod).
|
|
*
|
|
* @author Severin Gehwolf <sgehwolf@redhat.com>
|
|
*
|
|
* @requires vm.gc != "Z"
|
|
* @library ..
|
|
* @run build TestScaffold VMConnection TargetListener TargetAdapter
|
|
* @run compile -g OomDebugTest.java
|
|
* @run main OomDebugTest OomDebugTestTarget test1
|
|
* @run main OomDebugTest OomDebugTestTarget test2
|
|
* @run main OomDebugTest OomDebugTestTarget test3
|
|
* @run main OomDebugTest OomDebugTestTarget test4
|
|
* @run main OomDebugTest OomDebugTestTarget test5
|
|
*/
|
|
import java.io.File;
|
|
import java.io.FileInputStream;
|
|
import java.io.FileNotFoundException;
|
|
import java.io.FileOutputStream;
|
|
import java.io.IOException;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Collections;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Properties;
|
|
import java.util.Set;
|
|
|
|
import com.sun.jdi.ArrayReference;
|
|
import com.sun.jdi.ArrayType;
|
|
import com.sun.jdi.ClassType;
|
|
import com.sun.jdi.Field;
|
|
import com.sun.jdi.InvocationException;
|
|
import com.sun.jdi.Method;
|
|
import com.sun.jdi.ObjectReference;
|
|
import com.sun.jdi.ReferenceType;
|
|
import com.sun.jdi.StackFrame;
|
|
import com.sun.jdi.VMOutOfMemoryException;
|
|
import com.sun.jdi.Value;
|
|
import com.sun.jdi.event.BreakpointEvent;
|
|
import com.sun.jdi.event.ExceptionEvent;
|
|
|
|
/***************** Target program **********************/
|
|
|
|
class OomDebugTestTarget {
|
|
|
|
OomDebugTestTarget() {
|
|
System.out.println("DEBUG: invoked constructor");
|
|
}
|
|
static class FooCls {
|
|
@SuppressWarnings("unused")
|
|
private byte[] bytes = new byte[3000000];
|
|
};
|
|
|
|
FooCls fooCls = new FooCls();
|
|
byte[] byteArray = new byte[0];
|
|
|
|
void testMethod(FooCls foo) {
|
|
System.out.println("DEBUG: invoked 'void testMethod(FooCls)', foo == " + foo);
|
|
}
|
|
|
|
void testPrimitive(byte[] foo) {
|
|
System.out.println("DEBUG: invoked 'void testPrimitive(byte[])', foo == " + foo);
|
|
}
|
|
|
|
byte[] testPrimitiveArrRetval() {
|
|
System.out.println("DEBUG: invoked 'byte[] testPrimitiveArrRetval()'");
|
|
return new byte[3000000];
|
|
}
|
|
|
|
FooCls testFooClsRetval() {
|
|
System.out.println("DEBUG: invoked 'FooCls testFooClsRetval()'");
|
|
return new FooCls();
|
|
}
|
|
|
|
public void entry() {}
|
|
|
|
public static void main(String[] args){
|
|
System.out.println("DEBUG: OomDebugTestTarget.main");
|
|
new OomDebugTestTarget().entry();
|
|
}
|
|
}
|
|
|
|
/***************** Test program ************************/
|
|
|
|
public class OomDebugTest extends TestScaffold {
|
|
|
|
private static final String[] ALL_TESTS = new String[] {
|
|
"test1", "test2", "test3", "test4", "test5"
|
|
};
|
|
private static final Set<String> ALL_TESTS_SET = new HashSet<String>();
|
|
static {
|
|
ALL_TESTS_SET.addAll(Arrays.asList(ALL_TESTS));
|
|
}
|
|
private static final String TEST_CLASSES = System.getProperty("test.classes", ".");
|
|
private static final File RESULT_FILE = new File(TEST_CLASSES, "results.properties");
|
|
private static final String LAST_TEST = ALL_TESTS[ALL_TESTS.length - 1];
|
|
private ReferenceType targetClass;
|
|
private ObjectReference thisObject;
|
|
private int failedTests;
|
|
private final String testMethod;
|
|
|
|
public OomDebugTest(String[] args) {
|
|
super(args);
|
|
if (args.length != 2) {
|
|
throw new RuntimeException("Test failed unexpectedly.");
|
|
}
|
|
this.testMethod = args[1];
|
|
}
|
|
|
|
@Override
|
|
protected void runTests() throws Exception {
|
|
try {
|
|
addListener(new TargetAdapter() {
|
|
|
|
@Override
|
|
public void exceptionThrown(ExceptionEvent event) {
|
|
String name = event.exception().referenceType().name();
|
|
System.err.println("DEBUG: Exception thrown in debuggee was: " + name);
|
|
}
|
|
});
|
|
/*
|
|
* Get to the top of entry()
|
|
* to determine targetClass and mainThread
|
|
*/
|
|
BreakpointEvent bpe = startTo("OomDebugTestTarget", "entry", "()V");
|
|
targetClass = bpe.location().declaringType();
|
|
|
|
mainThread = bpe.thread();
|
|
|
|
StackFrame frame = mainThread.frame(0);
|
|
thisObject = frame.thisObject();
|
|
java.lang.reflect.Method m = findTestMethod();
|
|
m.invoke(this);
|
|
} catch (NoSuchMethodException e) {
|
|
e.printStackTrace();
|
|
failure();
|
|
} catch (SecurityException e) {
|
|
e.printStackTrace();
|
|
failure();
|
|
}
|
|
/*
|
|
* resume the target, listening for events
|
|
*/
|
|
listenUntilVMDisconnect();
|
|
}
|
|
|
|
private java.lang.reflect.Method findTestMethod()
|
|
throws NoSuchMethodException, SecurityException {
|
|
return OomDebugTest.class.getDeclaredMethod(testMethod);
|
|
}
|
|
|
|
private void failure() {
|
|
failedTests++;
|
|
}
|
|
|
|
/*
|
|
* Test case: Object reference as method parameter.
|
|
*/
|
|
@SuppressWarnings("unused") // called via reflection
|
|
private void test1() throws Exception {
|
|
System.out.println("DEBUG: ------------> Running test1");
|
|
try {
|
|
Field field = targetClass.fieldByName("fooCls");
|
|
ClassType clsType = (ClassType)field.type();
|
|
Method constructor = getConstructorForClass(clsType);
|
|
for (int i = 0; i < 15; i++) {
|
|
@SuppressWarnings({ "rawtypes", "unchecked" })
|
|
ObjectReference objRef = clsType.newInstance(mainThread,
|
|
constructor,
|
|
new ArrayList(0),
|
|
ObjectReference.INVOKE_NONVIRTUAL);
|
|
if (objRef.isCollected()) {
|
|
System.out.println("DEBUG: Object got GC'ed before we can use it. NO-OP.");
|
|
continue;
|
|
}
|
|
invoke("testMethod", "(LOomDebugTestTarget$FooCls;)V", objRef);
|
|
}
|
|
} catch (InvocationException e) {
|
|
handleFailure(e);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Test case: Array reference as method parameter.
|
|
*/
|
|
@SuppressWarnings("unused") // called via reflection
|
|
private void test2() throws Exception {
|
|
System.out.println("DEBUG: ------------> Running test2");
|
|
try {
|
|
Field field = targetClass.fieldByName("byteArray");
|
|
ArrayType arrType = (ArrayType)field.type();
|
|
|
|
for (int i = 0; i < 15; i++) {
|
|
ArrayReference byteArrayVal = arrType.newInstance(3000000);
|
|
if (byteArrayVal.isCollected()) {
|
|
System.out.println("DEBUG: Object got GC'ed before we can use it. NO-OP.");
|
|
continue;
|
|
}
|
|
invoke("testPrimitive", "([B)V", byteArrayVal);
|
|
}
|
|
} catch (VMOutOfMemoryException e) {
|
|
defaultHandleOOMFailure(e);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Test case: Array reference as return value.
|
|
*/
|
|
@SuppressWarnings("unused") // called via reflection
|
|
private void test3() throws Exception {
|
|
System.out.println("DEBUG: ------------> Running test3");
|
|
try {
|
|
for (int i = 0; i < 15; i++) {
|
|
invoke("testPrimitiveArrRetval",
|
|
"()[B",
|
|
Collections.EMPTY_LIST,
|
|
vm().mirrorOfVoid());
|
|
}
|
|
} catch (InvocationException e) {
|
|
handleFailure(e);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Test case: Object reference as return value.
|
|
*/
|
|
@SuppressWarnings("unused") // called via reflection
|
|
private void test4() throws Exception {
|
|
System.out.println("DEBUG: ------------> Running test4");
|
|
try {
|
|
for (int i = 0; i < 15; i++) {
|
|
invoke("testFooClsRetval",
|
|
"()LOomDebugTestTarget$FooCls;",
|
|
Collections.EMPTY_LIST,
|
|
vm().mirrorOfVoid());
|
|
}
|
|
} catch (InvocationException e) {
|
|
handleFailure(e);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Test case: Constructor
|
|
*/
|
|
@SuppressWarnings({ "unused", "unchecked", "rawtypes" }) // called via reflection
|
|
private void test5() throws Exception {
|
|
System.out.println("DEBUG: ------------> Running test5");
|
|
try {
|
|
ClassType type = (ClassType)thisObject.type();
|
|
for (int i = 0; i < 15; i++) {
|
|
type.newInstance(mainThread,
|
|
findMethod(targetClass, "<init>", "()V"),
|
|
new ArrayList(0),
|
|
ObjectReference.INVOKE_NONVIRTUAL);
|
|
}
|
|
} catch (InvocationException e) {
|
|
handleFailure(e);
|
|
}
|
|
}
|
|
|
|
private Method getConstructorForClass(ClassType clsType) {
|
|
List<Method> methods = clsType.methodsByName("<init>");
|
|
if (methods.size() != 1) {
|
|
throw new RuntimeException("FAIL. Expected only one, the default, constructor");
|
|
}
|
|
return methods.get(0);
|
|
}
|
|
|
|
private void handleFailure(InvocationException e) {
|
|
// There is no good way to see the OOME diagnostic message in the target since the
|
|
// TestScaffold might throw an exception while trying to print the stack trace. I.e
|
|
// it might get a VMDisconnectedException before the stack trace printing finishes.
|
|
System.err.println("FAILURE: InvocationException thrown. Trying to determine cause...");
|
|
defaultHandleOOMFailure(e);
|
|
}
|
|
|
|
private void defaultHandleOOMFailure(Exception e) {
|
|
e.printStackTrace();
|
|
failure();
|
|
}
|
|
|
|
@SuppressWarnings({ "rawtypes", "unchecked" })
|
|
void invoke(String methodName, String methodSig, Value value)
|
|
throws Exception {
|
|
List args = new ArrayList(1);
|
|
args.add(value);
|
|
invoke(methodName, methodSig, args, value);
|
|
}
|
|
|
|
void invoke(String methodName,
|
|
String methodSig,
|
|
@SuppressWarnings("rawtypes") List args,
|
|
Value value) throws Exception {
|
|
Method method = findMethod(targetClass, methodName, methodSig);
|
|
if ( method == null) {
|
|
failure("FAILED: Can't find method: "
|
|
+ methodName + " for class = " + targetClass);
|
|
return;
|
|
}
|
|
invoke(method, args, value);
|
|
}
|
|
|
|
@SuppressWarnings({ "rawtypes", "unchecked" })
|
|
void invoke(Method method, List args, Value value) throws Exception {
|
|
thisObject.invokeMethod(mainThread, method, args, 0);
|
|
System.out.println("DEBUG: Done invoking method via debugger.");
|
|
}
|
|
|
|
Value fieldValue(String fieldName) {
|
|
Field field = targetClass.fieldByName(fieldName);
|
|
return thisObject.getValue(field);
|
|
}
|
|
|
|
// Determine the pass/fail status on some heuristic and don't fail the
|
|
// test if < 3 of the total number of tests (currently 5) fail. This also
|
|
// has the nice side effect that all tests are first attempted and only
|
|
// all tests ran an overall pass/fail status is determined.
|
|
private static void determineOverallTestStatus(OomDebugTest oomTest)
|
|
throws IOException, FileNotFoundException {
|
|
Properties resultProps = new Properties();
|
|
if (!RESULT_FILE.exists()) {
|
|
RESULT_FILE.createNewFile();
|
|
}
|
|
FileInputStream fin = null;
|
|
try {
|
|
fin = new FileInputStream(RESULT_FILE);
|
|
resultProps.load(fin);
|
|
resultProps.put(oomTest.testMethod,
|
|
Integer.toString(oomTest.failedTests));
|
|
} finally {
|
|
if (fin != null) {
|
|
fin.close();
|
|
}
|
|
}
|
|
System.out.println("DEBUG: Finished running test '"
|
|
+ oomTest.testMethod + "'.");
|
|
if (LAST_TEST.equals(oomTest.testMethod)) {
|
|
System.out.println("DEBUG: Determining overall test status.");
|
|
Set<String> actualTestsRun = new HashSet<String>();
|
|
int totalTests = ALL_TESTS.length;
|
|
int failedTests = 0;
|
|
for (Object key: resultProps.keySet()) {
|
|
actualTestsRun.add((String)key);
|
|
Object propVal = resultProps.get(key);
|
|
int value = Integer.parseInt((String)propVal);
|
|
failedTests += value;
|
|
}
|
|
if (!ALL_TESTS_SET.equals(actualTestsRun)) {
|
|
String errorMsg = "Test failed! Expected to run tests '"
|
|
+ ALL_TESTS_SET + "', but only these were run '"
|
|
+ actualTestsRun + "'";
|
|
throw new RuntimeException(errorMsg);
|
|
}
|
|
if (failedTests >= 3) {
|
|
String errorMsg = "Test failed. Expected < 3 sub-tests to fail "
|
|
+ "for a pass. Got " + failedTests
|
|
+ " failed tests out of " + totalTests + ".";
|
|
throw new RuntimeException(errorMsg);
|
|
}
|
|
RESULT_FILE.delete();
|
|
System.out.println("All " + totalTests + " tests passed.");
|
|
} else {
|
|
System.out.println("DEBUG: More tests to run. Coninuing.");
|
|
FileOutputStream fout = null;
|
|
try {
|
|
fout = new FileOutputStream(RESULT_FILE);
|
|
resultProps.store(fout, "Storing results after test "
|
|
+ oomTest.testMethod);
|
|
} finally {
|
|
if (fout != null) {
|
|
fout.close();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void main(String[] args) throws Exception {
|
|
System.setProperty("test.vm.opts", "-Xmx40m"); // Set debuggee VM option
|
|
OomDebugTest oomTest = new OomDebugTest(args);
|
|
try {
|
|
oomTest.startTests();
|
|
} catch (Throwable e) {
|
|
System.out.println("DEBUG: Got exception for test run. " + e);
|
|
e.printStackTrace();
|
|
oomTest.failure();
|
|
}
|
|
determineOverallTestStatus(oomTest);
|
|
}
|
|
|
|
}
|