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 }