1 /* 2 * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /* 25 * @test 26 * @summary verify if the hidden class is unloaded when the class loader is GC'ed 27 * @library /test/lib/ 28 * @build jdk.test.lib.util.ForceGC 29 * @run testng/othervm UnloadingTest 30 */ 31 32 import java.io.IOException; 33 import java.lang.invoke.MethodHandles.Lookup; 34 import java.lang.ref.Reference; 35 import java.lang.ref.WeakReference; 36 import java.lang.reflect.Method; 37 import java.net.MalformedURLException; 38 import java.net.URL; 39 import java.net.URLClassLoader; 40 import java.nio.file.Files; 41 import java.nio.file.Path; 42 import java.nio.file.Paths; 43 import java.util.concurrent.atomic.AtomicInteger; 44 45 import jdk.test.lib.util.ForceGC; 46 47 import jdk.test.lib.compiler.CompilerUtils; 48 import jdk.test.lib.Utils; 49 50 import org.testng.annotations.BeforeTest; 51 import org.testng.annotations.Test; 52 53 import static java.lang.invoke.MethodHandles.lookup; 54 import static org.testng.Assert.*; 55 56 public class UnloadingTest { 57 private static final Path CLASSES_DIR = Paths.get("classes"); 58 private static byte[] hiddenClassBytes; 59 60 @BeforeTest 61 static void setup() throws IOException { 62 Path src = Paths.get(Utils.TEST_SRC, "src", "LookupHelper.java"); 63 if (!CompilerUtils.compile(src, CLASSES_DIR)) { 64 throw new RuntimeException("Compilation of the test failed: " + src); 65 } 66 67 hiddenClassBytes = Files.readAllBytes(CLASSES_DIR.resolve("LookupHelper.class")); 68 } 69 70 /* 71 * Test that a hidden class is unloaded while the loader remains strongly reachable 72 */ 73 @Test 74 public void unloadable() throws Exception { 75 TestLoader loader = new TestLoader(); 76 Class<?> helper = Class.forName("LookupHelper", true, loader); 77 Method m = helper.getMethod("getLookup"); 78 Lookup lookup = (Lookup)m.invoke(null); 79 HiddenClassUnloader unloader = new HiddenClassUnloader(lookup, false); 80 // the hidden class should be unloaded 81 unloader.unload(); 82 83 // loader is strongly reachable 84 Reference.reachabilityFence(loader); 85 } 86 87 /* 88 * Test that a hidden class is not unloaded when the loader is strongly reachable 89 */ 90 @Test 91 public void notUnloadable() throws Exception { 92 TestLoader loader = new TestLoader(); 93 Class<?> helper = Class.forName("LookupHelper", true, loader); 94 Method m = helper.getMethod("getLookup"); 95 Lookup lookup = (Lookup)m.invoke(null); 96 HiddenClassUnloader unloader = new HiddenClassUnloader(lookup, true); 97 assertFalse(unloader.tryUnload()); // hidden class is not unloaded 98 } 99 100 static class HiddenClassUnloader { 101 private final WeakReference<?> weakRef; 102 HiddenClassUnloader(Lookup lookup, boolean strong) throws Exception { 103 Class<?> hc; 104 if (strong) { 105 hc = lookup.defineHiddenClass(hiddenClassBytes, false, Lookup.ClassOption.STRONG).lookupClass(); 106 } else { 107 hc = lookup.defineHiddenClass(hiddenClassBytes, false).lookupClass(); 108 } 109 this.weakRef = new WeakReference<>(hc); 110 assertTrue(hc.getClassLoader() == lookup.lookupClass().getClassLoader()); 111 } 112 113 void unload() { 114 // Force garbage collection to trigger unloading of class loader and native library 115 ForceGC gc = new ForceGC(); 116 assertTrue(gc.await(() -> weakRef.get() == null)); 117 118 if (weakRef.get() != null) { 119 throw new RuntimeException("loader " + " not unloaded!"); 120 } 121 } 122 123 boolean tryUnload() { 124 ForceGC gc = new ForceGC(); 125 return gc.await(() -> weakRef.get() == null); 126 } 127 } 128 129 static class TestLoader extends URLClassLoader { 130 static URL[] toURLs() { 131 try { 132 return new URL[] { CLASSES_DIR.toUri().toURL() }; 133 } catch (MalformedURLException e) { 134 throw new Error(e); 135 } 136 } 137 138 static AtomicInteger counter = new AtomicInteger(); 139 TestLoader() { 140 super("testloader-" + counter.addAndGet(1), toURLs(), ClassLoader.getSystemClassLoader()); 141 } 142 } 143 }