1 /*
   2  * Copyright (c) 2014, 2018, 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 /*
  26  * @test
  27  *
  28  * @summary converted from VM Testbase vm/mlvm/indy/stress/gc/lotsOfCallSites.
  29  * VM Testbase keywords: [feature_mlvm, nonconcurrent]
  30  *
  31  * @library /vmTestbase
  32  *          /test/lib
  33  * @run driver jdk.test.lib.FileInstaller . .
  34  * @build sun.hotspot.WhiteBox
  35  * @run driver ClassFileInstaller sun.hotspot.WhiteBox
  36  *                                sun.hotspot.WhiteBox$WhiteBoxPermission
  37  *
  38  * @comment build test class and indify classes
  39  * @build vm.mlvm.indy.stress.gc.lotsOfCallSites.Test
  40  *        vm.mlvm.indy.stress.gc.lotsOfCallSites.INDIFY_Testee
  41  * @run driver vm.mlvm.share.IndifiedClassesBuilder
  42  *
  43  * @run main/othervm -Xbootclasspath/a:.
  44  *                   -XX:+UnlockDiagnosticVMOptions
  45  *                   -XX:+WhiteBoxAPI
  46  *                   vm.mlvm.indy.stress.gc.lotsOfCallSites.Test
  47  */
  48 
  49 package vm.mlvm.indy.stress.gc.lotsOfCallSites;
  50 
  51 import java.lang.invoke.CallSite;
  52 import java.lang.invoke.MethodHandles;
  53 import java.lang.invoke.MethodHandle;
  54 import java.lang.invoke.MethodType;
  55 import java.lang.ref.PhantomReference;
  56 import java.lang.ref.Reference;
  57 import java.lang.ref.ReferenceQueue;
  58 import java.lang.reflect.Field;
  59 import java.lang.reflect.Method;
  60 import java.lang.reflect.InvocationTargetException;
  61 import java.lang.management.MemoryMXBean;
  62 import java.lang.management.MemoryPoolMXBean;
  63 import java.lang.management.ManagementFactory;
  64 import java.lang.management.MemoryUsage;
  65 import java.util.HashSet;
  66 
  67 import nsk.share.test.Stresser;
  68 import vm.mlvm.share.CustomClassLoaders;
  69 import vm.mlvm.share.Env;
  70 import vm.mlvm.share.MlvmTest;
  71 import vm.mlvm.share.WhiteBoxHelper;
  72 import vm.share.FileUtils;
  73 import vm.share.options.Option;
  74 
  75 /**
  76  * The test creates a lot of CallSites by loading a class with a bootstrap method and invokedynamic
  77  * via a custom classloader in a loop.
  78  *
  79  * The test verifies that all CallSites are "delivered to heaven" by creating a PhantomReference per
  80  *  a CallSite and checking the number of references put into a queue.
  81  *
  82  */
  83 public class Test extends MlvmTest {
  84 
  85     // TODO (separate bug should be filed): move this option to MlvmTest level
  86     @Option(name = "heapdump", default_value = "false", description = "Dump heap after test has finished")
  87     private boolean heapDumpOpt = false;
  88 
  89     @Option(name = "iterations", default_value = "100000", description = "Iterations: each iteration loads one new class")
  90     private int iterations = 100_000;
  91 
  92     private static final int GC_COUNT = 6;
  93     private static final boolean TERMINATE_ON_FULL_METASPACE = false;
  94 
  95     private static final ReferenceQueue<CallSite> objQueue = new ReferenceQueue<CallSite>();
  96     private static final HashSet<PhantomReference<CallSite>> references = new HashSet<PhantomReference<CallSite>>();
  97     private static long loadedClassCount = 0;
  98 
  99     // We avoid direct references to the testee class to avoid loading it by application class loader
 100     // Otherwise the testee class is loaded both by the custom and the application class loaders,
 101     // and when java.lang.invoke.MH.COMPILE_THRESHOLD={0,1} is defined, the test fails with
 102     // "java.lang.IncompatibleClassChangeError: disagree on InnerClasses attribute"
 103     private static final String TESTEE_CLASS_NAME = Test.class.getPackage().getName() + "." + "INDIFY_Testee";
 104     private static final String TESTEE_REFERENCES_FIELD = "references";
 105     private static final String TESTEE_OBJQUEUE_FIELD = "objQueue";
 106     private static final String TESTEE_BOOTSTRAP_CALLED_FIELD = "bootstrapCalled";
 107     private static final String TESTEE_TARGET_CALLED_FIELD = "targetCalled";
 108     private static final String TESTEE_INDY_METHOD = "indyWrapper";
 109 
 110     private static int removeQueuedReferences() {
 111         int count = 0;
 112         Reference<? extends CallSite> r;
 113         while ((r = objQueue.poll()) != null) {
 114             if (!references.remove(r)) {
 115                 Env.traceNormal("Reference " + r + " was not registered!");
 116             }
 117             ++count;
 118         }
 119         if (count > 0) {
 120             Env.traceVerbose("Removed " + count + " phantom references");
 121         } else {
 122             Env.traceDebug("Removed " + count + " phantom references");
 123         }
 124         return count;
 125     }
 126 
 127     private MemoryPoolMXBean getClassMetadataMemoryPoolMXBean() {
 128     MemoryMXBean mbean = ManagementFactory.getMemoryMXBean();
 129     for (MemoryPoolMXBean memPool : ManagementFactory.getMemoryPoolMXBeans()) {
 130             String name = memPool.getName();
 131         if ((name.contains("Compressed class space") || name.contains("Metaspace")) && memPool.getUsage() != null) {
 132                 return memPool;
 133             }
 134         }
 135         return null;
 136     }
 137 
 138     private MethodHandle getFullGCMethod() throws NoSuchMethodException, IllegalAccessException {
 139         try {
 140             return WhiteBoxHelper.getMethod("fullGC", MethodType.methodType(void.class));
 141         } catch (NoSuchMethodException | ClassNotFoundException | InvocationTargetException e) {
 142             Env.traceDebug(e, "No WhiteBox API. Will use System.gc() instead of WhiteBox.fullGC()");
 143             return MethodHandles.lookup().findStatic(System.class, "gc", MethodType.methodType(void.class));
 144         }
 145     }
 146 
 147     @Override
 148     public boolean run() throws Throwable {
 149         setHeapDumpAfter(heapDumpOpt);
 150 
 151         final byte[] classBytes = FileUtils.readClass(TESTEE_CLASS_NAME);
 152         final MemoryPoolMXBean classMetadataPoolMXB = getClassMetadataMemoryPoolMXBean();
 153         final String memPoolName = classMetadataPoolMXB == null ? "" : classMetadataPoolMXB.getName();
 154 
 155         MethodHandle mhCollectHeap = getFullGCMethod();
 156 
 157         int removedEntries = 0;
 158 
 159         Stresser stresser = createStresser();
 160         stresser.start(iterations);
 161         try {
 162             while (stresser.continueExecution()) {
 163                 stresser.iteration();
 164 
 165                 iteration(classBytes, TESTEE_CLASS_NAME);
 166                 removedEntries += removeQueuedReferences();
 167 
 168                 if (stresser.getIteration() % 1000 == 0) {
 169                     Env.traceNormal("Iterations: " + stresser.getIteration() + " removed entries: " + removedEntries);
 170                     if (TERMINATE_ON_FULL_METASPACE && classMetadataPoolMXB != null) {
 171                         MemoryUsage mu = classMetadataPoolMXB.getUsage();
 172                         Env.traceNormal(memPoolName + " usage: " + mu);
 173                         if  (mu.getUsed() >= mu.getMax() * 9 / 10) {
 174                             Env.traceNormal(memPoolName + " is nearly out of space: " + mu + ". Terminating.");
 175                             break;
 176                         }
 177                     }
 178                 }
 179             }
 180 
 181         } catch (OutOfMemoryError e) {
 182             Env.traceNormal(e, "Out of memory. This is OK");
 183         } finally {
 184             stresser.finish();
 185         }
 186 
 187         for (int i = 0; i < GC_COUNT; ++i) {
 188             mhCollectHeap.invoke();
 189             Thread.sleep(500);
 190             removedEntries += removeQueuedReferences();
 191         }
 192 
 193         removedEntries += removeQueuedReferences();
 194 
 195         Env.traceNormal("Loaded classes: " + loadedClassCount
 196                       + "; References left in set: " + references.size()
 197                       + "; References removed from queue: " + removedEntries);
 198 
 199         if (references.size() != 0 || removedEntries != loadedClassCount) {
 200             Env.complain("Not all of the created CallSites were GC-ed");
 201             return false;
 202         }
 203 
 204         return true;
 205     }
 206 
 207     private void iteration(byte[] classBytes, String indyClassName) throws Throwable {
 208         ClassLoader cl = CustomClassLoaders.makeClassBytesLoader(classBytes, indyClassName);
 209         Class<?> c = cl.loadClass(indyClassName);
 210         ++loadedClassCount;
 211 
 212         if (c.getClassLoader() != cl) {
 213             throw new RuntimeException("Invalid class loader: " + c.getClassLoader() + "; required=" + cl);
 214         }
 215 
 216         Field vr = c.getDeclaredField(TESTEE_REFERENCES_FIELD);
 217         vr.set(null, references);
 218 
 219         Field voq = c.getDeclaredField(TESTEE_OBJQUEUE_FIELD);
 220         voq.set(null, objQueue);
 221 
 222         Field vbc = c.getDeclaredField(TESTEE_BOOTSTRAP_CALLED_FIELD);
 223         if (vbc.getBoolean(null)) {
 224             throw new RuntimeException(TESTEE_BOOTSTRAP_CALLED_FIELD + " flag should not be set. Not a fresh copy of the testee class?");
 225         }
 226 
 227         Field vt = c.getDeclaredField(TESTEE_TARGET_CALLED_FIELD);
 228         if (vt.getBoolean(null)) {
 229             throw new RuntimeException(TESTEE_TARGET_CALLED_FIELD + " flag should not be set. Not a fresh copy of the testee class?");
 230         }
 231 
 232         Method m = c.getDeclaredMethod(TESTEE_INDY_METHOD);
 233         m.invoke(null);
 234 
 235         if (!vbc.getBoolean(null) ) {
 236             throw new RuntimeException("Bootstrap method of the testee class was not called");
 237         }
 238 
 239         if (!vt.getBoolean(null) ) {
 240             throw new RuntimeException("Target method of the testee class was not called");
 241         }
 242     }
 243 
 244     public static void main(String[] args) {
 245         MlvmTest.launch(args);
 246     }
 247 }