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 }