d52a995f35
Reviewed-by: lmesnik, dholmes, rriggs, stefank
182 lines
6.9 KiB
Java
182 lines
6.9 KiB
Java
/*
|
|
* Copyright (c) 2017, 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
|
|
* @bug 8174749 8213307
|
|
* @summary MemberNameTable should reuse entries
|
|
* @library /test/lib
|
|
* @modules java.base/jdk.internal.misc
|
|
* @modules java.compiler
|
|
* @build jdk.test.whitebox.WhiteBox
|
|
* @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
|
|
* @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. MemberNameLeak
|
|
*/
|
|
|
|
import java.io.*;
|
|
import java.nio.file.*;
|
|
import java.lang.invoke.*;
|
|
import java.lang.reflect.*;
|
|
import java.text.*;
|
|
import java.util.*;
|
|
import jdk.test.lib.process.OutputAnalyzer;
|
|
import jdk.test.lib.process.ProcessTools;
|
|
import jdk.test.lib.Utils;
|
|
import jdk.test.whitebox.WhiteBox;
|
|
import jdk.test.whitebox.code.Compiler;
|
|
import jdk.test.whitebox.gc.GC;
|
|
import jdk.test.lib.classloader.ClassWithManyMethodsClassLoader;
|
|
|
|
public class MemberNameLeak {
|
|
private static String className = "MemberNameLeakTestClass";
|
|
private static String methodPrefix = "method";
|
|
// The size of the ResolvedMethodTable is 1024. 2000 entries
|
|
// is enough to trigger a grow/cleaning of the table after a GC.
|
|
private static int methodCount = 2000;
|
|
public static ArrayList<MethodHandle> keepAlive;
|
|
|
|
static class Leak {
|
|
public void callMe() {
|
|
}
|
|
|
|
public static void main(String[] args) throws Throwable {
|
|
Leak leak = new Leak();
|
|
WhiteBox wb = WhiteBox.getWhiteBox();
|
|
|
|
keepAlive = new ArrayList<>(methodCount);
|
|
|
|
ClassWithManyMethodsClassLoader classLoader = new ClassWithManyMethodsClassLoader();
|
|
Class<?> clazz = classLoader.create(className, methodPrefix, methodCount);
|
|
|
|
long before = wb.resolvedMethodItemsCount();
|
|
|
|
Object o = clazz.newInstance();
|
|
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(clazz, MethodHandles.lookup());
|
|
|
|
for (int i = 0; i < methodCount; i++) {
|
|
MethodType mt = MethodType.fromMethodDescriptorString("()V", classLoader);
|
|
String methodName = methodPrefix + i;
|
|
// findSpecial leaks some native mem
|
|
// Add entry to ResolvedMethodTable.
|
|
MethodHandle mh0 = lookup.findSpecial(clazz, methodName, mt, clazz);
|
|
// Find entry in ResolvedMethodTable.
|
|
MethodHandle mh1 = lookup.findSpecial(clazz, methodName, mt, clazz);
|
|
|
|
mh1.invoke(o);
|
|
|
|
keepAlive.add(mh1);
|
|
}
|
|
|
|
long after = wb.resolvedMethodItemsCount();
|
|
|
|
System.out.println("wb.resolvedMethodItemsCount() after setup: " + after);
|
|
|
|
if (after == before) {
|
|
throw new RuntimeException("Too few resolved methods");
|
|
}
|
|
|
|
keepAlive = null;
|
|
|
|
// Wait until ServiceThread cleans ResolvedMethod table
|
|
int cnt = 0;
|
|
while (true) {
|
|
if (cnt++ % 30 == 0) {
|
|
System.gc(); // make mh unused
|
|
}
|
|
|
|
if (after > wb.resolvedMethodItemsCount() + 50) {
|
|
// Entries have been removed.
|
|
break;
|
|
}
|
|
|
|
Thread.sleep(100);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static Path createGcLogPath(String prefix) throws IOException {
|
|
Path gcLog = Utils.createTempFile(prefix, "log");
|
|
Files.delete(gcLog);
|
|
return gcLog;
|
|
}
|
|
|
|
private static DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
|
|
|
public static void test(GC gc, boolean doConcurrent) throws Throwable {
|
|
Path gcLogPath = createGcLogPath("gc." + gc + "." + doConcurrent);
|
|
System.err.println("test(" + gc + ", " + doConcurrent + ")" + " " + dateFormat.format(new Date()));
|
|
// Run this Leak class with logging
|
|
ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(
|
|
"-Xlog:membername+table=trace,gc+verify=debug,gc:" + gcLogPath + ":time,utctime,uptime,pid,level,tags",
|
|
"-XX:+UnlockExperimentalVMOptions",
|
|
"-XX:+UnlockDiagnosticVMOptions",
|
|
"-XX:+WhiteBoxAPI",
|
|
"-Xbootclasspath/a:.",
|
|
"-XX:+VerifyBeforeGC",
|
|
"-XX:+VerifyAfterGC",
|
|
doConcurrent ? "-XX:+ExplicitGCInvokesConcurrent" : "-XX:-ExplicitGCInvokesConcurrent",
|
|
"-XX:+ClassUnloading",
|
|
"-XX:+ClassUnloadingWithConcurrentMark",
|
|
"-XX:+Use" + gc + "GC",
|
|
Leak.class.getName());
|
|
|
|
// Check process
|
|
OutputAnalyzer output = new OutputAnalyzer(pb.start());
|
|
output.outputTo(System.out);
|
|
output.errorTo(System.err);
|
|
output.shouldHaveExitValue(0);
|
|
|
|
// Check gc log file
|
|
OutputAnalyzer gcLogOutput = new OutputAnalyzer(gcLogPath);
|
|
|
|
// Hardcoded names for classes generated by GeneratedClassLoader
|
|
String descriptor = className + "." + methodPrefix + "0()V";
|
|
gcLogOutput.shouldContain("ResolvedMethod entry added for " + descriptor);
|
|
gcLogOutput.shouldContain("ResolvedMethod entry found for " + descriptor);
|
|
gcLogOutput.shouldContain("ResolvedMethod entry removed");
|
|
|
|
System.err.println("test(" + gc + ", " + doConcurrent + ")" + " done " + dateFormat.format(new Date()));
|
|
}
|
|
|
|
private static boolean supportsSTW(GC gc) {
|
|
return !(gc == GC.Epsilon);
|
|
}
|
|
|
|
private static boolean supportsConcurrent(GC gc) {
|
|
return !(gc == GC.Epsilon || gc == GC.Serial || gc == GC.Parallel);
|
|
}
|
|
|
|
private static void test(GC gc) throws Throwable {
|
|
if (supportsSTW(gc)) {
|
|
test(gc, false);
|
|
}
|
|
if (supportsConcurrent(gc)) {
|
|
test(gc, true);
|
|
}
|
|
}
|
|
|
|
public static void main(java.lang.String[] unused) throws Throwable {
|
|
test(GC.selected());
|
|
}
|
|
}
|