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 }