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 }