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