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 useLargepages = PAGE_SIZE > 1_000_000 && !vmArgs.contains("-XX:-UseLargePagesInMetaspace"); 105 106 System.out.println(vmArgs); 107 108 pool = getMemoryPool(getPoolName()); 109 if (pool == null) { 110 return; // nothing to check 111 } 112 for (String arg: vmArgs) { 113 if (arg.startsWith("-Xlog:gc") && arg.length() > 8) { 114 gclogFileName = arg.substring(arg.lastIndexOf(':') + 1); 115 } 116 } 117 parseArgs(args); 118 } 119 120 121 /** 122 * Imitates class loading. 123 * Each invocation of this method causes a new class loader object is created 124 * and a new class is loaded by this class loader. 125 * Method throws OOM when run out of memory. 126 * 127 * @param times how many classes to load 128 * @param keepRefs true, if references to created classes should be stored 129 */ 130 protected void loadNewClasses(int times, boolean keepRefs) { 131 for (int i = 0; i < times; i++) { 132 try { 133 String jarUrl = "file:" + counter + ".jar"; 134 counter++; 135 URL[] urls = new URL[]{new URL(jarUrl)}; 136 URLClassLoader cl = new URLClassLoader(urls); 137 MetaspaceBaseGC.Foo foo = (MetaspaceBaseGC.Foo) Proxy.newProxyInstance(cl, 138 new Class[]{MetaspaceBaseGC.Foo.class}, 139 new MetaspaceBaseGC.FooInvocationHandler(new MetaspaceBaseGC.FooBar())); 140 if (keepRefs) { 141 loadedClasses.put(jarUrl, foo); 142 } 143 } catch (java.net.MalformedURLException badThing) { 144 // should never occur 145 System.err.println("Unexpeted error: " + badThing); 146 throw new RuntimeException(badThing); 147 } 148 } 149 150 } 151 152 /** 153 * Cleans references to loaded classes. 154 */ 155 protected void cleanLoadedClasses() { 156 loadedClasses.clear(); 157 } 158 159 /** 160 * Invokes System.gc() and sleeps a little. 161 */ 162 protected void gc() { 163 System.gc(); 164 try { 165 Thread.currentThread().sleep(500); 166 } catch (Exception whatever) { 167 } 168 } 169 170 /** 171 * Reads gc.log file and returns it as a list of lines. 172 * It's supposed that the test is executed with -Xlog:gc:gc.log option. 173 * 174 * @return List of strings the gc.log file is comprised. 175 * @throws IOException if problem occurred while reading. 176 */ 177 protected List<String> readGCLog() throws IOException { 178 return Files.readAllLines(Paths.get(".", gclogFileName)); 179 } 180 181 /** 182 * Reads gc.log file and counts GC induced by metaspace. 183 * @return how many times GC induced by metaspace has occurred. 184 */ 185 protected int getMetaspaceGCCount() { 186 int count = 0; 187 try { 188 for (String line: readGCLog()) { 189 if (line.indexOf("Metadata GC ") > 0) { 190 count++; 191 } 192 } 193 return count; 194 } catch (Throwable t) { 195 t.printStackTrace(System.err); 196 return -1; 197 } 198 } 199 200 protected String lastGCLogLine() { 201 if (gclogFileName == null) { 202 return ""; 203 } 204 try { 205 List<String> list = Files.readAllLines(Paths.get(".", gclogFileName)); 206 return list.get(list.size() - 1); 207 } catch (IOException e) { 208 return "File not found"; 209 } 210 } 211 212 /** 213 * Does it best to checks if the last GC was caused by metaspace. 214 * 215 * This method looks into gc.log file (if -Xloggc:file is given) and returns 216 * true if the last line in the log contains the "Metadata" word. 217 * It's not very reliable way to check, log might not be flushed yet. 218 * 219 * @return 220 */ 221 protected boolean isMetaspaceGC() { 222 return lastGCLogLine().contains("Metadata"); 223 } 224 225 /** 226 * Prints amounts of used and committed metaspace preceeded by the message 227 * @param mesg a message to printed prior usages 228 */ 229 protected void printMemoryUsage(String mesg) { 230 MemoryUsage mu = pool.getUsage(); 231 printMemoryUsage(mesg, mu.getUsed(), mu.getCommitted()); 232 } 233 protected void printMemoryUsage(String mesg, long v1, long v2) { 234 System.out.println(mesg + ": " + bytes2k(v1) + " : " + bytes2k(v2)); 235 } 236 protected String bytes2k(long v) { 237 return (v / 1024) + "k"; 238 } 239 240 241 242 /** 243 * @return amount of used memory 244 */ 245 public long getUsed() { 246 return pool.getUsage().getUsed(); 247 } 248 249 /** 250 * @return amount of committed memory 251 */ 252 public long getCommitted() { 253 return pool.getUsage().getCommitted(); 254 } 255 256 private static MemoryPoolMXBean getMemoryPool(String name) { 257 List<MemoryPoolMXBean> pools = ManagementFactory.getMemoryPoolMXBeans(); 258 for (MemoryPoolMXBean pool : pools) { 259 if (pool.getName().equals(name)) { 260 return pool; 261 } 262 } 263 return null; 264 } 265 266 private static long detectPageSize() { 267 try { 268 Unsafe unsafe = Unsafe.getUnsafe(); 269 270 int pageSize = unsafe.pageSize(); 271 System.out.println("Page size: " + pageSize); 272 return pageSize; 273 } catch (Exception e) { 274 throw new Fault("Cannot detect page size"); 275 } 276 } 277 278 279 long parseValue(String s) { 280 s = s.toLowerCase(); 281 int multiplier = 1; 282 switch (s.charAt(s.length() - 1)) { 283 case 'g': multiplier = 1024*1024*1024; break; 284 case 'm': multiplier = 1024*1024; break; 285 case 'k': multiplier = 1024; break; 286 } 287 if (multiplier == 1) { 288 return Long.parseLong(s); 289 } else { 290 return Long.parseLong(s.substring(0, s.length() - 1)) * multiplier; 291 } 292 } 293 294 public static interface Foo { 295 } 296 297 public static class FooBar implements Foo { 298 } 299 300 class FooInvocationHandler implements InvocationHandler { 301 private final Foo foo; 302 303 FooInvocationHandler(MetaspaceBaseGC.Foo foo) { 304 this.foo = foo; 305 } 306 307 @Override 308 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 309 return method.invoke(foo, args); 310 } 311 } 312 313 }