--- /dev/null 2020-03-17 11:31:50.000000000 -0700 +++ new/test/jdk/java/lang/invoke/defineHiddenClass/UnloadingTest.java 2020-03-17 11:31:49.000000000 -0700 @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2020, 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 + * @summary verify if the hidden class is unloaded when the class loader is GC'ed + * @library /test/lib/ + * @build jdk.test.lib.util.ForceGC + * @run testng/othervm UnloadingTest + */ + +import java.io.IOException; +import java.lang.invoke.MethodHandles.Lookup; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.concurrent.atomic.AtomicInteger; + +import jdk.test.lib.util.ForceGC; + +import jdk.test.lib.compiler.CompilerUtils; +import jdk.test.lib.Utils; + +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +import static java.lang.invoke.MethodHandles.lookup; +import static org.testng.Assert.*; + +public class UnloadingTest { + private static final Path CLASSES_DIR = Paths.get("classes"); + private static byte[] hiddenClassBytes; + + @BeforeTest + static void setup() throws IOException { + Path src = Paths.get(Utils.TEST_SRC, "src", "LookupHelper.java"); + if (!CompilerUtils.compile(src, CLASSES_DIR)) { + throw new RuntimeException("Compilation of the test failed: " + src); + } + + hiddenClassBytes = Files.readAllBytes(CLASSES_DIR.resolve("LookupHelper.class")); + } + + /* + * Test that a hidden class is unloaded while the loader remains strongly reachable + */ + @Test + public void unloadable() throws Exception { + TestLoader loader = new TestLoader(); + Class helper = Class.forName("LookupHelper", true, loader); + Method m = helper.getMethod("getLookup"); + Lookup lookup = (Lookup)m.invoke(null); + HiddenClassUnloader unloader = new HiddenClassUnloader(lookup, false); + // the hidden class should be unloaded + unloader.unload(); + + // loader is strongly reachable + Reference.reachabilityFence(loader); + } + + /* + * Test that a hidden class is not unloaded when the loader is strongly reachable + */ + @Test + public void notUnloadable() throws Exception { + TestLoader loader = new TestLoader(); + Class helper = Class.forName("LookupHelper", true, loader); + Method m = helper.getMethod("getLookup"); + Lookup lookup = (Lookup)m.invoke(null); + HiddenClassUnloader unloader = new HiddenClassUnloader(lookup, true); + assertFalse(unloader.tryUnload()); // hidden class is not unloaded + } + + static class HiddenClassUnloader { + private final WeakReference weakRef; + HiddenClassUnloader(Lookup lookup, boolean strong) throws Exception { + Class hc; + if (strong) { + hc = lookup.defineHiddenClass(hiddenClassBytes, false, Lookup.ClassOption.STRONG).lookupClass(); + } else { + hc = lookup.defineHiddenClass(hiddenClassBytes, false).lookupClass(); + } + this.weakRef = new WeakReference<>(hc); + assertTrue(hc.getClassLoader() == lookup.lookupClass().getClassLoader()); + } + + void unload() { + // Force garbage collection to trigger unloading of class loader and native library + ForceGC gc = new ForceGC(); + assertTrue(gc.await(() -> weakRef.get() == null)); + + if (weakRef.get() != null) { + throw new RuntimeException("loader " + " not unloaded!"); + } + } + + boolean tryUnload() { + ForceGC gc = new ForceGC(); + return gc.await(() -> weakRef.get() == null); + } + } + + static class TestLoader extends URLClassLoader { + static URL[] toURLs() { + try { + return new URL[] { CLASSES_DIR.toUri().toURL() }; + } catch (MalformedURLException e) { + throw new Error(e); + } + } + + static AtomicInteger counter = new AtomicInteger(); + TestLoader() { + super("testloader-" + counter.addAndGet(1), toURLs(), ClassLoader.getSystemClassLoader()); + } + } +}