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.util.Arrays;
  27 import vm.share.VMRuntimeEnvUtils;
  28 
  29 /**
  30  * Test metaspace ergonomic.
  31  *
  32  * <ul>
  33  * <li>MetaspaceSize
  34  * <li>MaxMetaspaceSize
  35  * <li>MinMetaspaceFreeRatio
  36  * <li>MaxMetaspaceFreeRatio
  37  * </ul>
  38  *
  39  * The test loads classes until the committed metaspace achieves the certain
  40  * level between MetaspaceSize and MaxMetaspaceSize.
  41  * Then it counts how many times GC has been induced.
  42  * Test verifies that MinMetaspaceFreeRatio/MaxMetaspaceFreeRatio settings
  43  * affect the frequency of GC. (High-water mark)
  44  *
  45  * Note: The test doesn't check the GC count if CMS is used.
  46  *
  47  * Quoting: Java SE 8 HotSpot[tm] Virtual Machine Garbage Collection Tuning
  48  * <pre>
  49  * Class metadata is deallocated when the corresponding Java class is unloaded.
  50  * Java classes are unloaded as a results of garbage collection and garbage
  51  * collections may be induced in order to unload classes and deallocate class
  52  * metadata. When the space used for class metadata reaches a certain level
  53  * (call it a high-water mark), a garbage collection is induced.
  54  * After the garbage collection the high-water mark may be raised or lowered
  55  * depending on the amount of space freed from class metadata. The high-water
  56  * mark would be raised so as not to induce another garbage collection too soon.
  57  * The high-water mark is initially set to the value of the command-line
  58  * flag MetaspaceSize . It is raised or lowered based on the flags
  59  * MaxMetaspaceFreeRatio and MinMetaspaceFreeRatio.
  60  * If the committed space available for class metadata as a percentage of
  61  * the total committed space for class metadata is greater than
  62  * MaxMetaspaceFreeRatio, the high-water mark will be lowered.
  63  * If it is less than MinMetaspaceFreeRatio, the high-water mark will be raised.
  64  * </pre>
  65  */
  66 public class HighWaterMarkTest extends FirstGCTest {
  67 
  68     public static void main(String... args) {
  69         new HighWaterMarkTest().run(args);
  70     }
  71 
  72     // value given in -XX:MetaspaceSize=<value>
  73     private long metaspaceSize = -1;
  74 
  75     // value given in -XX:MaxMetaspaceSize=<value>
  76     private long maxMetaspaceSize = -1;
  77 
  78     // value given in -XX:MinMetaspaceFreeRatio=<value>
  79     private long minMetaspaceFreeRatio = -1;
  80 
  81     // value given in -XX:MaxMetaspaceFreeRatio=<value>
  82     private long maxMetaspaceFreeRatio = -1;
  83 
  84     /**
  85      * Parses arguments and vm options.
  86      * Throws Fault in cases of wrong values or missed parameters.
  87      *
  88      * @param args command line options
  89      */
  90     @Override
  91     protected void parseArgs(String[] args) {
  92         if (args.length > 0) {
  93             printUsage();
  94             throw new Fault("Illegal arguments: " + Arrays.asList(args));
  95         }
  96 
  97         if (gclogFileName == null) {
  98             printUsage();
  99             throw new Fault("Log file name is not given");
 100         }
 101 
 102         final String metaSize    = "-XX:MetaspaceSize=";
 103         final String maxMetaSize = "-XX:MaxMetaspaceSize=";
 104         final String minRatio    = "-XX:MinMetaspaceFreeRatio=";
 105         final String maxRatio    = "-XX:MaxMetaspaceFreeRatio=";
 106 
 107         for (String va: vmArgs) {
 108             if (va.startsWith(metaSize)) {
 109                 metaspaceSize = parseValue(va.substring(metaSize.length()));
 110             } else if (va.startsWith(maxMetaSize)) {
 111                 maxMetaspaceSize = parseValue(va.substring(maxMetaSize.length()));
 112             } else if (va.startsWith(minRatio)) {
 113                 minMetaspaceFreeRatio = parseValue(va.substring(minRatio.length()));
 114             } else if (va.startsWith(maxRatio)) {
 115                 maxMetaspaceFreeRatio = parseValue(va.substring(maxRatio.length()));
 116             }
 117         }
 118 
 119         if (metaspaceSize < 0) {
 120             printUsage();
 121             throw new Fault("-XX:MetaspaceSize is not specified");
 122         } else if (maxMetaspaceSize < 0) {
 123             printUsage();
 124             throw new Fault("-XX:MaxMetaspaceSize is not specified");
 125         } else if (minMetaspaceFreeRatio < 0) {
 126             printUsage();
 127             throw new Fault("-XX:MinMetaspaceFreeRatio is not specified");
 128         } else if (maxMetaspaceFreeRatio < 0) {
 129             printUsage();
 130             throw new Fault("-XX:MaxMetaspaceFreeRatio is not specified");
 131         }
 132 
 133     }
 134 
 135     private void printUsage() {
 136         System.err.println("Usage: ");
 137         System.err.println("java [-Xlog:gc:<filename>] [-XX:MetaspaceSize=..] [-XX:MaxMetaspaceSize=..] [-XX:MinMetaspaceFreeRatio=..] [-XX:MaxMetaspaceFreeRatio=..] \\");
 138         System.err.println("    " + HighWaterMarkTest.class.getCanonicalName());
 139     }
 140 
 141     /**
 142      * Check that MinMetaspaceFreeRatio/MaxMetaspaceFreeRatio settings
 143      * affects the moment of the next GC.
 144      *
 145      * Eats memory until amount of committed metaspace achieves a certain level
 146      * (between MetaspaceSize and MaxMetaspaceSize).
 147      * Then checks how many times GC has been invoked.
 148      *
 149      */
 150     @Override
 151     public void doCheck() {
 152 
 153         // to avoid timeouts we limit the number of attempts
 154         int attempts = 0;
 155         int maxAttempts = 10_000;
 156 
 157         // in between metaspaceSize and maxMetaspaceSize
 158         // no OOM is exepcted.
 159         long committedLevel = (metaspaceSize + maxMetaspaceSize) / 2;
 160 
 161         while (getCommitted() < committedLevel && attempts < maxAttempts) {
 162             attempts++;
 163             loadNewClasses(9, true);  // load classes and keep references
 164             loadNewClasses(1, false); // load classes without keeping references
 165         }
 166 
 167 
 168         System.out.println("% Classes loaded: " + attempts*10);
 169         System.out.println("% Used metaspace    : " + bytes2k(getUsed()));
 170         System.out.println("% Committed metaspce: " + bytes2k(getCommitted()));
 171 
 172         cleanLoadedClasses();
 173 
 174         if (attempts == maxAttempts) {
 175             throw new Fault("Committed amount hasn't achieved " + bytes2k(committedLevel));
 176         }
 177 
 178         if (VMRuntimeEnvUtils.isVMOptionEnabled("UseConcMarkSweepGC")) {
 179             System.out.println("ConcMarkSweep is used, cannot count GC");
 180             return;
 181         }
 182 
 183         int gcCount = getMetaspaceGCCount();
 184         if (gcCount < 0) {
 185             // perhpas, it's better to silently pass here... Let's see.
 186             throw new Fault ("Unable to count full collections, could be an env issue");
 187         }
 188         System.out.println("% GC has been invoked: " + gcCount + " times");
 189 
 190         if (VMRuntimeEnvUtils.isVMOptionEnabled("UseG1GC") &&
 191               VMRuntimeEnvUtils.isVMOptionEnabled("ClassUnloadingWithConcurrentMark")) {
 192             System.out.println("% isG1ClassUnloading: true");
 193             if (gcCount != 0) {
 194                 throw new Fault ("G1 should unload classes, full GC is not expected");
 195             }
 196         } else {
 197             if (maxMetaspaceFreeRatio <= 1) {
 198                 // min/max = 0/1  boundary value
 199                 // GC should happen very often
 200                 checkGCCount(gcCount, 20, -1);
 201             } else if (minMetaspaceFreeRatio >= 99) {
 202                 // min/max = 99/100  boundary value
 203                 // GC should happen very rare
 204                 checkGCCount(gcCount, -1, 2);
 205             } else if (minMetaspaceFreeRatio >= 10  && maxMetaspaceFreeRatio <= 20) {
 206                 // GC should happen quite often
 207                 checkGCCount(gcCount, 3, 30);
 208             } else if (minMetaspaceFreeRatio >= 70  && maxMetaspaceFreeRatio <= 80) {
 209                 // GC should happen quite often
 210                 checkGCCount(gcCount, 1, 3);
 211             } else {
 212                 // hard to estimate
 213             }
 214         }
 215 
 216     }
 217     /**
 218      * Checks that count of GC fits the expected range.
 219      * Throws Fault if count is unexpected.
 220      *
 221      * @param count how many times GC has happened
 222      * @param min   expected minimum, if under zero - undefined
 223      * @param max   expected maximum, if under zero - undefined
 224      */
 225     void checkGCCount(int count, int min, int max) {
 226         if (min < 0) {
 227             if(count > max) {
 228                 throw new Fault("GC has happened too often: " + count + " times, " +
 229                         "expected count: less than " + max);
 230             }
 231         } else if (max < 0) {
 232             if(count < min) {
 233                 throw new Fault("GC has happened too rare: " + count + " times, " +
 234                         "expected count greater than " + min);
 235             }
 236         } else if (count < min || count > max ) {
 237             throw new Fault ("GC has happened " + count + " times, " +
 238                     "approximate count is " + min + " to " + max);
 239         }
 240     }
 241 
 242 }