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 * Note: this method doesn't work for ConcMarkSweep... 184 * @return how many times GC induced by metaspace has occurred. 185 */ 186 protected int getMetaspaceGCCount() { 187 int count = 0; 188 try { 189 for (String line: readGCLog()) { 190 if (line.indexOf("Pause Full") > 0 && line.indexOf("Meta") > 0) { 191 count++; 192 } 193 } 194 return count; 195 } catch (Throwable t) { 196 t.printStackTrace(System.err); 197 return -1; 198 } 199 } 200 201 protected String lastGCLogLine() { 202 if (gclogFileName == null) { 203 return ""; 204 } 205 try { 206 List<String> list = Files.readAllLines(Paths.get(".", gclogFileName)); 207 return list.get(list.size() - 1); 208 } catch (IOException e) { 209 return "File not found"; 210 } 211 } 212 213 /** 214 * Does it best to checks if the last GC was caused by metaspace. 215 * 216 * This method looks into gc.log file (if -Xloggc:file is given) and returns 217 * true if the last line in the log contains the "Metadata" word. 218 * It's not very reliable way to check, log might not be flushed yet. 219 * 220 * @return 221 */ 222 protected boolean isMetaspaceGC() { 223 return lastGCLogLine().contains("Metadata"); 224 } 225 226 /** 227 * Prints amounts of used and committed metaspace preceeded by the message 228 * @param mesg a message to printed prior usages 229 */ 230 protected void printMemoryUsage(String mesg) { 231 MemoryUsage mu = pool.getUsage(); 232 printMemoryUsage(mesg, mu.getUsed(), mu.getCommitted()); 233 } 234 protected void printMemoryUsage(String mesg, long v1, long v2) { 235 System.out.println(mesg + ": " + bytes2k(v1) + " : " + bytes2k(v2)); 236 } 237 protected String bytes2k(long v) { 238 return (v / 1024) + "k"; 239 } 240 241 242 243 /** 244 * @return amount of used memory 245 */ 246 public long getUsed() { 247 return pool.getUsage().getUsed(); 248 } 249 250 /** 251 * @return amount of committed memory 252 */ 253 public long getCommitted() { 254 return pool.getUsage().getCommitted(); 255 } 256 257 private static MemoryPoolMXBean getMemoryPool(String name) { 258 List<MemoryPoolMXBean> pools = ManagementFactory.getMemoryPoolMXBeans(); 259 for (MemoryPoolMXBean pool : pools) { 260 if (pool.getName().equals(name)) { 261 return pool; 262 } 263 } 264 return null; 265 } 266 267 private static long detectPageSize() { 268 try { 269 Unsafe unsafe = Unsafe.getUnsafe(); 270 271 int pageSize = unsafe.pageSize(); 272 System.out.println("Page size: " + pageSize); 273 return pageSize; 274 } catch (Exception e) { 275 throw new Fault("Cannot detect page size"); 276 } 277 } 278 279 280 long parseValue(String s) { 281 s = s.toLowerCase(); 282 int multiplier = 1; 283 switch (s.charAt(s.length() - 1)) { 284 case 'g': multiplier = 1024*1024*1024; break; 285 case 'm': multiplier = 1024*1024; break; 286 case 'k': multiplier = 1024; break; 287 } 288 if (multiplier == 1) { 289 return Long.parseLong(s); 290 } else { 291 return Long.parseLong(s.substring(0, s.length() - 1)) * multiplier; 292 } 293 } 294 295 public static interface Foo { 296 } 297 298 public static class FooBar implements Foo { 299 } 300 301 class FooInvocationHandler implements InvocationHandler { 302 private final Foo foo; 303 304 FooInvocationHandler(MetaspaceBaseGC.Foo foo) { 305 this.foo = foo; 306 } 307 308 @Override 309 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 310 return method.invoke(foo, args); 311 } 312 } 313 314 }