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.util.regex.Matcher;
  28 import java.util.regex.Pattern;
  29 import static metaspace.gc.MetaspaceBaseGC.PAGE_SIZE;
  30 
  31 /**
  32  * Test for metaspace GC
  33  *
  34  * <ul>
  35  * <li>MetaspaceSize
  36  * </ul>
  37  * Test checks that the first GC happens when metaspace committed is next to
  38  * MetaspaceSize value.
  39  *
  40  * Based on actual events (JDK 8 GC tuning document)
  41  *
  42  * Quating: Java SE 8 HotSpot[tm] Virtual Machine Garbage Collection Tuning
  43  * <pre>
  44  * Class metadata is deallocated when the corresponding Java class is unloaded.
  45  * Java classes are unloaded as a results of garbage collection and garbage
  46  * collections may be induced in order to unload classes and deallocate class
  47  * metadata. When the space used for class metadata reaches a certain level
  48  * (call it a high-water mark), a garbage collection is induced.
  49  *
  50  * The flag MetaspaceSize can be set higher to avoid early garbage collections
  51  * induced for class metadata. The amount of class metadata allocated for
  52  * an application is application dependent and general guidelines do not
  53  * exist for the selection of MetaspaceSize. The default size of MetaspaceSize
  54  * is platform dependent and ranges from 12 MB to about 20 MB.
  55  * </pre>
  56  */
  57 public class FirstGCTest extends MetaspaceBaseGC {
  58      /**
  59       * Current amount of the used metaspace
  60       */
  61      protected long used = 0;
  62 
  63      /**
  64       * Current amount of the committed metaspace
  65       */
  66      protected long committed = 0;
  67 
  68      /**
  69       * Previous amount of the used metaspace
  70       */
  71      protected long p_used = 0 ;
  72 
  73      /**
  74       * Previous amount of the committed metaspace
  75       */
  76      protected long p_committed = 0;
  77 
  78     public static void main(String... args) {
  79         new FirstGCTest().run(args);
  80     }
  81 
  82     // value given in -XX:metaspaceSize=<value>
  83     private long metaspaceSize = -1;
  84 
  85 
  86     @Override
  87     protected void parseArgs(String[] args) {
  88         final String XXSize = "-XX:MetaspaceSize=";
  89         for (String va: vmArgs) {
  90             if (va.startsWith(XXSize)) {
  91                 metaspaceSize = parseValue(va.substring(XXSize.length()));
  92             }
  93         }
  94     }
  95 
  96     @Override
  97     protected String getPoolName() {
  98         return "Metaspace";
  99     }
 100 
 101     /**
 102      * Check for the first GC moment.
 103      *
 104      * Eats memory until GC is invoked (amount of used metaspace became less);
 105      * Checks that committed memory is close to MemaspaceSize.
 106      * Eats memory until the second GC to check min/max ratio options have effect.
 107      */
 108     @Override
 109     public void doCheck() {
 110         int gcCount = super.getMetaspaceGCCount();
 111         if (gcCount == 0) {
 112             // gc hasn't happened yet. Start loading classes.
 113             boolean gcHappened = this.eatMemoryUntilGC(50000);
 114             if (!gcHappened) {
 115                 throw new Fault("GC hasn't happened");
 116             }
 117             System.out.println("% GC: " + super.lastGCLogLine());
 118             System.out.println("%   used     : " + p_used + " --> " + used);
 119             System.out.println("%   committed: " + p_committed + " --> " + committed);
 120             checkCommitted(p_committed);
 121         } else {
 122             // everything has happened before
 123             checkCommitted(detectCommittedFromGCLog());
 124         }
 125     }
 126 
 127     /**
 128      * Check that committed amount is close to expected value (MetaspaceSize)
 129      *
 130      * @param committedAmount - value to check
 131      */
 132     void checkCommitted(long committedAmount) {
 133         if (metaspaceSize > 0) {
 134             // -XX:MetaspaceSize is given
 135             if (Math.abs((int) (metaspaceSize - committedAmount)) < PAGE_SIZE) {
 136                 System.out.println("% GC happened at the right moment");
 137                 return;
 138             }
 139             if (!isMetaspaceGC()) {
 140                 System.out.println("% GC wasn't induced by metaspace, cannot check the moment :(");
 141                 return;
 142             }
 143             System.err.println("%## GC happened at the wrong moment, "
 144                     + "the amount of committed space significantly differs "
 145                     + "from the expected amount");
 146             System.err.println("%## Real    : " + committedAmount);
 147             System.err.println("%## Exepcted: " + metaspaceSize);
 148             throw new Fault("GC happened at the wrong moment");
 149         } else {
 150             // -XX:MetaspaceSize is not given, check for default values
 151             if (11_500_000 < committedAmount && committedAmount < 22_500_000) {
 152                 System.out.println("% GC happened when the committed amout was from 12 MB to about 20 MB.");
 153                 return;
 154             }
 155             if (!isMetaspaceGC()) {
 156                 System.out.println("% GC wasn't induced by metaspace, this is excuse");
 157                 return;
 158             }
 159             System.err.println("%## GC happened at the wrong moment, "
 160                     + "the amount of committed space was expected from 12 MB to about 20 MB");
 161             System.err.println("%## Real    : " + committedAmount);
 162             throw new Fault("It was the wrong moment when GC happened");
 163         }
 164     }
 165 
 166     /**
 167      * Load new classes without keeping references to them trying to provoke GC.
 168      * Stops if GC is detected, or number of attempts exceeds the given limit.
 169      *
 170      * @param times limit of attempts to provoke GC
 171      * @return true if GC has happened, false if limit has exceeded.
 172      */
 173     protected boolean eatMemoryUntilGC(int times) {
 174         System.out.println("%%%% Loading classes");
 175         System.out.println("% iter#  :   used  : commited");
 176         System.out.println("..............................");
 177         for (int i = 1; i < times; i++) {
 178             loadNewClasses(1, false);
 179             if (i % 1000 == 0) {
 180                 printMemoryUsage("%  " + i + "  ");
 181             }
 182             p_used      = used;
 183             p_committed = committed;
 184             used = getUsed();
 185             committed = getCommitted();
 186 
 187             if (used < p_used) {
 188                 return true;
 189             }
 190         }
 191         return false;
 192     }
 193 
 194     /**
 195      * If the first full GC has already happened we will try to detect
 196      * the committed amount from the gc.log file.
 197      *
 198      * @return committed amount detected
 199      * @throws Fault if failed to detect.
 200      */
 201     protected long detectCommittedFromGCLog() {
 202         // parse gc.log to extract the committed value from string like:
 203         //  Metaspace       used 10133K, capacity 10190K, committed 10240K, reserved 10240Kl
 204         System.out.println("%%%% Parsing gc log to detect the moment of the first GC");
 205         String format = ".*Metaspace.* used .*, capacity .*, committed (\\d+)([KMGkmg]), reserved .*";
 206         Pattern p = Pattern.compile(format);
 207         try {
 208             for (String line: readGCLog()) {
 209                 Matcher m = p.matcher(line);
 210                 if (m.matches()) {
 211                     int amount = Integer.parseInt(m.group(1));
 212                     int multi = 1;
 213                     switch (m.group(2).toLowerCase()) {
 214                         case "k": multi = 1024; break;
 215                         case "m": multi = 1024*1024; break;
 216                         case "g": multi = 1024*1024*1024; break;
 217                     }
 218                     long value = amount * multi;
 219                     System.out.println("% Committed detected: " + value);
 220                     return value;
 221                 }
 222             }
 223         } catch (IOException e) {
 224             throw new Fault("Cannot read from the GC log");
 225         }
 226         System.out.println("% String that matches pattern '" + format + "' not found in the GC log file.");
 227         throw new Fault("Unable to detect the moment of GC from log file");
 228     }
 229 
 230 }