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 * @modules jdk.compiler 28 * @library /test/lib/ 29 * @build jdk.test.lib.util.ForceGC 30 * @run testng/othervm UnloadingTest 31 */ 32 33 import java.io.IOException; 34 import java.lang.invoke.MethodHandles.Lookup; 35 import java.lang.ref.Reference; 36 import java.lang.ref.WeakReference; 37 import java.lang.reflect.Method; 38 import java.net.MalformedURLException; 39 import java.net.URL; 40 import java.net.URLClassLoader; 41 import java.nio.file.Files; 42 import java.nio.file.Path; 43 import java.nio.file.Paths; 44 import java.util.concurrent.atomic.AtomicInteger; 45 46 import jdk.test.lib.util.ForceGC; 47 48 import jdk.test.lib.compiler.CompilerUtils; 49 import jdk.test.lib.Utils; 50 51 import org.testng.annotations.BeforeTest; 52 import org.testng.annotations.Test; 53 54 import static java.lang.invoke.MethodHandles.lookup; 55 import static java.lang.invoke.MethodHandles.Lookup.ClassOption.*; 56 import static org.testng.Assert.*; 57 58 public class UnloadingTest { 59 private static final Path CLASSES_DIR = Paths.get("classes"); 60 private static byte[] hiddenClassBytes; 61 62 @BeforeTest 63 static void setup() throws IOException { 64 Path src = Paths.get(Utils.TEST_SRC, "src", "LookupHelper.java"); 65 if (!CompilerUtils.compile(src, CLASSES_DIR)) { 66 throw new RuntimeException("Compilation of the test failed: " + src); 67 } 68 69 hiddenClassBytes = Files.readAllBytes(CLASSES_DIR.resolve("LookupHelper.class")); 70 } 71 72 /* 73 * Test that a hidden class is unloaded while the loader remains strongly reachable 74 */ 75 @Test 76 public void unloadable() throws Exception { 77 TestLoader loader = new TestLoader(); 78 Class<?> helper = Class.forName("LookupHelper", true, loader); 79 Method m = helper.getMethod("getLookup"); 80 Lookup lookup = (Lookup)m.invoke(null); 81 HiddenClassUnloader unloader = createHiddenClass(lookup, false); 82 // the hidden class should be unloaded 83 unloader.unload(); 84 85 // loader is strongly reachable 86 Reference.reachabilityFence(loader); 87 } 88 89 /* 90 * Test that a hidden class is not unloaded when the loader is strongly reachable 91 */ 92 @Test 93 public void notUnloadable() throws Exception { 94 TestLoader loader = new TestLoader(); 95 Class<?> helper = Class.forName("LookupHelper", true, loader); 96 Method m = helper.getMethod("getLookup"); 97 Lookup lookup = (Lookup)m.invoke(null); 98 HiddenClassUnloader unloader = createHiddenClass(lookup, true); 99 assertFalse(unloader.tryUnload()); // hidden class is not unloaded 100 101 // loader is strongly reachable 102 Reference.reachabilityFence(loader); 103 } 104 105 /* 106 * Create a nest of two hidden classes. 107 * They can be unloaded even the loader is strongly reachable 108 */ 109 @Test 110 public void hiddenClassNest() throws Exception { 111 TestLoader loader = new TestLoader(); 112 Class<?> helper = Class.forName("LookupHelper", true, loader); 113 Method m = helper.getMethod("getLookup"); 114 Lookup lookup = (Lookup)m.invoke(null); 115 HiddenClassUnloader[] unloaders = createNestOfTwoHiddenClasses(lookup, false, false); 116 117 // keep a strong reference to the nest member class 118 Class<?> member = unloaders[1].weakRef.get(); 119 assertTrue(member != null); 120 // nest host and member will not be unloaded 121 assertFalse(unloaders[0].tryUnload()); 122 assertFalse(unloaders[1].tryUnload()); 123 124 // clear the reference to the nest member 125 Reference.reachabilityFence(member); 126 member = null; 127 128 // nest host and member will be unloaded 129 unloaders[0].unload(); 130 unloaders[1].unload(); 131 132 // loader is strongly reachable 133 Reference.reachabilityFence(loader); 134 } 135 136 /* 137 * Create a nest with a hidden class nest host and strong nest member. 138 * Test that both are not unloaded 139 */ 140 @Test 141 public void hiddenClassNestStrongMember() throws Exception { 142 TestLoader loader = new TestLoader(); 143 Class<?> helper = Class.forName("LookupHelper", true, loader); 144 Method m = helper.getMethod("getLookup"); 145 Lookup lookup = (Lookup)m.invoke(null); 146 HiddenClassUnloader[] unloaders = createNestOfTwoHiddenClasses(lookup, false, true); 147 assertFalse(unloaders[0].tryUnload()); // nest host cannot be unloaded 148 assertFalse(unloaders[1].tryUnload()); // nest member cannot be unloaded 149 150 // loader is strongly reachable 151 Reference.reachabilityFence(loader); 152 } 153 154 /* 155 * Create a nest with a strong hidden nest host and a hidden class member. 156 * The nest member can be unloaded whereas the nest host will not be unloaded. 157 */ 158 @Test 159 public void hiddenClassNestStrongHost() throws Exception { 160 TestLoader loader = new TestLoader(); 161 Class<?> helper = Class.forName("LookupHelper", true, loader); 162 Method m = helper.getMethod("getLookup"); 163 Lookup lookup = (Lookup)m.invoke(null); 164 HiddenClassUnloader[] unloaders = createNestOfTwoHiddenClasses(lookup, true, false); 165 assertFalse(unloaders[0].tryUnload()); // nest host cannot be unloaded 166 unloaders[1].unload(); 167 168 // loader is strongly reachable 169 Reference.reachabilityFence(loader); 170 } 171 172 /* 173 * Create a HiddenClassUnloader that holds a weak reference to the newly created 174 * hidden class. 175 */ 176 static HiddenClassUnloader createHiddenClass(Lookup lookup, boolean strong) throws Exception { 177 Class<?> hc; 178 if (strong) { 179 hc = lookup.defineHiddenClass(hiddenClassBytes, false, STRONG).lookupClass(); 180 } else { 181 hc = lookup.defineHiddenClass(hiddenClassBytes, false).lookupClass(); 182 } 183 assertTrue(hc.getClassLoader() == lookup.lookupClass().getClassLoader()); 184 return new HiddenClassUnloader(hc); 185 } 186 187 /* 188 * Create an array of HiddenClassUnloader with two elements: the first element 189 * is for the nest host and the second element is for the nest member. 190 */ 191 static HiddenClassUnloader[] createNestOfTwoHiddenClasses(Lookup lookup, boolean strongHost, boolean strongMember) throws Exception { 192 Lookup hostLookup; 193 if (strongHost) { 194 hostLookup = lookup.defineHiddenClass(hiddenClassBytes, false, STRONG); 195 } else { 196 hostLookup = lookup.defineHiddenClass(hiddenClassBytes, false); 197 } 198 Class<?> host = hostLookup.lookupClass(); 199 Class<?> member; 200 if (strongMember) { 201 member = hostLookup.defineHiddenClass(hiddenClassBytes, false, NESTMATE, STRONG).lookupClass(); 202 } else { 203 member = hostLookup.defineHiddenClass(hiddenClassBytes, false, NESTMATE).lookupClass(); 204 } 205 assertTrue(member.getNestHost() == host); 206 return new HiddenClassUnloader[] { new HiddenClassUnloader(host), new HiddenClassUnloader(member) }; 207 } 208 209 static class HiddenClassUnloader { 210 private final WeakReference<Class<?>> weakRef; 211 private HiddenClassUnloader(Class<?> hc) { 212 assertTrue(hc.isHidden()); 213 this.weakRef = new WeakReference<>(hc); 214 } 215 216 void unload() { 217 // Force garbage collection to trigger unloading of class loader and native library 218 ForceGC gc = new ForceGC(); 219 assertTrue(gc.await(() -> weakRef.get() == null)); 220 221 if (weakRef.get() != null) { 222 throw new RuntimeException("loader " + " not unloaded!"); 223 } 224 } 225 226 boolean tryUnload() { 227 ForceGC gc = new ForceGC(); 228 return gc.await(() -> weakRef.get() == null); 229 } 230 } 231 232 static class TestLoader extends URLClassLoader { 233 static URL[] toURLs() { 234 try { 235 return new URL[] { CLASSES_DIR.toUri().toURL() }; 236 } catch (MalformedURLException e) { 237 throw new Error(e); 238 } 239 } 240 241 static AtomicInteger counter = new AtomicInteger(); 242 TestLoader() { 243 super("testloader-" + counter.addAndGet(1), toURLs(), ClassLoader.getSystemClassLoader()); 244 } 245 } 246 }