1 /* 2 * Copyright (c) 2013, 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 package metaspace.gc; 25 26 import java.io.IOException; 27 import java.lang.management.ManagementFactory; 28 import java.lang.management.MemoryPoolMXBean; 29 import java.lang.management.MemoryUsage; 30 import java.lang.reflect.Field; 31 import java.lang.reflect.InvocationHandler; 32 import java.lang.reflect.Method; 33 import java.lang.reflect.Proxy; 34 import java.net.URL; 35 import java.net.URLClassLoader; 36 import java.nio.file.Files; 37 import java.nio.file.Paths; 38 import java.util.HashMap; 39 import java.util.HashSet; 40 import java.util.List; 41 import java.util.Map; 42 import java.util.Set; 43 import jdk.internal.misc.Unsafe; 44 45 /** 46 * Test that checks how GC works with Metaspace and "Compared Class Space". 47 * 48 * It comprises 3 test cases: 49 * <ul> 50 * <li>testcase1 - checks that used/committed memory doesn't grow 51 * when gc is invoked</li> 52 * <li>testcase2 - checks that gc is invoked when the class metadata u 53 * sage reaches MetaspaceSize</li> 54 * <li>testcase3 - checks used/committed grow, inspite of gc is invoked</li> 55 * </ul> 56 * 57 * It's supposed that this class will be executed with various setting of VM 58 * flags. Via execute args it's possible to say which test cases to run and 59 * what space to test: Metaspace or Compared Class Space. 60 */ 61 public abstract class MetaspaceBaseGC { 62 63 // storage of loaded classes 64 private final Map<String, MetaspaceBaseGC.Foo> loadedClasses = new HashMap<>(); 65 private static int counter = 0; 66 67 // pool to test 68 protected MemoryPoolMXBean pool = null; 69 70 // memory page size 71 protected static final long PAGE_SIZE = detectPageSize(); 72 73 // true when PAGE_SIZE is large and 74 protected boolean useLargepages = false; 75 76 // where the log will be saved 77 protected String gclogFileName = null; 78 79 protected final Set<String> vmArgs = new HashSet<>(); 80 81 protected abstract void parseArgs(String args[]); 82 protected abstract String getPoolName(); 83 protected abstract void doCheck(); 84 85 public final void run(String args[]) { 86 configure(args); 87 if (pool == null) { 88 System.out.println("%%% Cannot pull the pool, most likely 32-bits only"); 89 return; 90 } 91 System.out.println("%%% Working with " + getPoolName()); 92 for (String vmA: vmArgs) { 93 if (vmA.contains("Metaspace") || vmA.contains("Compressed")) { 94 System.out.println("% " + vmA); 95 } 96 } 97 doCheck(); 98 System.out.println("% Test passed."); 99 } 100 101 102 protected void configure(String args[]) { 103 vmArgs.addAll(ManagementFactory.getRuntimeMXBean().getInputArguments()); 104 105 System.out.println(vmArgs); 106 107 pool = getMemoryPool(getPoolName()); 108 if (pool == null) { 109 return; // nothing to check 110 } 111 for (String arg: vmArgs) { 112 if (arg.startsWith("-Xlog:gc") && arg.length() > 8) { 113 gclogFileName = arg.substring(arg.lastIndexOf(':') + 1); 114 } 115 } 116 parseArgs(args); 117 } 118 119 120 /** 121 * Imitates class loading. 122 * Each invocation of this method causes a new class loader object is created 123 * and a new class is loaded by this class loader. 124 * Method throws OOM when run out of memory. 125 * 126 * @param times how many classes to load 127 * @param keepRefs true, if references to created classes should be stored 128 */ 129 protected void loadNewClasses(int times, boolean keepRefs) { 130 for (int i = 0; i < times; i++) { 131 try { 132 String jarUrl = "file:" + counter + ".jar"; 133 counter++; 134 URL[] urls = new URL[]{new URL(jarUrl)}; 135 URLClassLoader cl = new URLClassLoader(urls); 136 MetaspaceBaseGC.Foo foo = (MetaspaceBaseGC.Foo) Proxy.newProxyInstance(cl, 137 new Class[]{MetaspaceBaseGC.Foo.class}, 138 new MetaspaceBaseGC.FooInvocationHandler(new MetaspaceBaseGC.FooBar())); 139 if (keepRefs) { 140 loadedClasses.put(jarUrl, foo); 141 } 142 } catch (java.net.MalformedURLException badThing) { 143 // should never occur 144 System.err.println("Unexpeted error: " + badThing); 145 throw new RuntimeException(badThing); 146 } 147 } 148 149 } 150 151 /** 152 * Cleans references to loaded classes. 153 */ 154 protected void cleanLoadedClasses() { 155 loadedClasses.clear(); 156 } 157 158 /** 159 * Invokes System.gc() and sleeps a little. 160 */ 161 protected void gc() { 162 System.gc(); 163 try { 164 Thread.currentThread().sleep(500); 165 } catch (Exception whatever) { 166 } 167 } 168 169 /** 170 * Reads gc.log file and returns it as a list of lines. 171 * It's supposed that the test is executed with -Xlog:gc:gc.log option. 172 * 173 * @return List of strings the gc.log file is comprised. 174 * @throws IOException if problem occurred while reading. 175 */ 176 protected List<String> readGCLog() throws IOException { 177 return Files.readAllLines(Paths.get(".", gclogFileName)); 178 } 179 180 /** 181 * Reads gc.log file and counts GC induced by metaspace. 182 * @return how many times GC induced by metaspace has occurred. 183 */ 184 protected int getMetaspaceGCCount() { 185 int count = 0; 186 try { 187 for (String line: readGCLog()) { 188 if (line.indexOf("Metadata GC ") > 0) { 189 count++; 190 } 191 } 192 return count; 193 } catch (Throwable t) { 194 t.printStackTrace(System.err); 195 return -1; 196 } 197 } 198 199 protected String lastGCLogLine() { 200 if (gclogFileName == null) { 201 return ""; 202 } 203 try { 204 List<String> list = Files.readAllLines(Paths.get(".", gclogFileName)); 205 return list.get(list.size() - 1); 206 } catch (IOException e) { 207 return "File not found"; 208 } 209 } 210 211 /** 212 * Does it best to checks if the last GC was caused by metaspace. 213 * 214 * This method looks into gc.log file (if -Xloggc:file is given) and returns 215 * true if the last line in the log contains the "Metadata" word. 216 * It's not very reliable way to check, log might not be flushed yet. 217 * 218 * @return 219 */ 220 protected boolean isMetaspaceGC() { 221 return lastGCLogLine().contains("Metadata"); 222 } 223 224 /** 225 * Prints amounts of used and committed metaspace preceeded by the message 226 * @param mesg a message to printed prior usages 227 */ 228 protected void printMemoryUsage(String mesg) { 229 MemoryUsage mu = pool.getUsage(); 230 printMemoryUsage(mesg, mu.getUsed(), mu.getCommitted()); 231 } 232 protected void printMemoryUsage(String mesg, long v1, long v2) { 233 System.out.println(mesg + ": " + bytes2k(v1) + " : " + bytes2k(v2)); 234 } 235 protected String bytes2k(long v) { 236 return (v / 1024) + "k"; 237 } 238 239 240 241 /** 242 * @return amount of used memory 243 */ 244 public long getUsed() { 245 return pool.getUsage().getUsed(); 246 } 247 248 /** 249 * @return amount of committed memory 250 */ 251 public long getCommitted() { 252 return pool.getUsage().getCommitted(); 253 } 254 255 private static MemoryPoolMXBean getMemoryPool(String name) { 256 List<MemoryPoolMXBean> pools = ManagementFactory.getMemoryPoolMXBeans(); 257 for (MemoryPoolMXBean pool : pools) { 258 if (pool.getName().equals(name)) { 259 return pool; 260 } 261 } 262 return null; 263 } 264 265 private static long detectPageSize() { 266 try { 267 Unsafe unsafe = Unsafe.getUnsafe(); 268 269 int pageSize = unsafe.pageSize(); 270 System.out.println("Page size: " + pageSize); 271 return pageSize; 272 } catch (Exception e) { 273 throw new Fault("Cannot detect page size"); 274 } 275 } 276 277 278 long parseValue(String s) { 279 s = s.toLowerCase(); 280 int multiplier = 1; 281 switch (s.charAt(s.length() - 1)) { 282 case 'g': multiplier = 1024*1024*1024; break; 283 case 'm': multiplier = 1024*1024; break; 284 case 'k': multiplier = 1024; break; 285 } 286 if (multiplier == 1) { 287 return Long.parseLong(s); 288 } else { 289 return Long.parseLong(s.substring(0, s.length() - 1)) * multiplier; 290 } 291 } 292 293 public static interface Foo { 294 } 295 296 public static class FooBar implements Foo { 297 } 298 299 class FooInvocationHandler implements InvocationHandler { 300 private final Foo foo; 301 302 FooInvocationHandler(MetaspaceBaseGC.Foo foo) { 303 this.foo = foo; 304 } 305 306 @Override 307 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 308 return method.invoke(foo, args); 309 } 310 } 311 312 }