/* * 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 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()); } }