--- /dev/null 2018-05-02 09:10:36.097102948 -0700 +++ new/test/hotspot/jtreg/vmTestbase/metaspace/gc/HighWaterMarkTest.java 2018-05-08 10:20:13.233632176 -0700 @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package metaspace.gc; + +import java.util.Arrays; +import vm.share.VMRuntimeEnvUtils; + +/** + * Test metaspace ergonomic. + * + * + * + * The test loads classes until the committed metaspace achieves the certain + * level between MetaspaceSize and MaxMetaspaceSize. + * Then it counts how many times GC has been induced. + * Test verifies that MinMetaspaceFreeRatio/MaxMetaspaceFreeRatio settings + * affect the frequency of GC. (High-water mark) + * + * Note: The test doesn't check the GC count if CMS is used. + * + * Quoting: Java SE 8 HotSpot[tm] Virtual Machine Garbage Collection Tuning + *
+ * Class metadata is deallocated when the corresponding Java class is unloaded.
+ * Java classes are unloaded as a results of garbage collection and garbage
+ * collections may be induced in order to unload classes and deallocate class
+ * metadata. When the space used for class metadata reaches a certain level
+ * (call it a high-water mark), a garbage collection is induced.
+ * After the garbage collection the high-water mark may be raised or lowered
+ * depending on the amount of space freed from class metadata. The high-water
+ * mark would be raised so as not to induce another garbage collection too soon.
+ * The high-water mark is initially set to the value of the command-line
+ * flag MetaspaceSize . It is raised or lowered based on the flags
+ * MaxMetaspaceFreeRatio and MinMetaspaceFreeRatio.
+ * If the committed space available for class metadata as a percentage of
+ * the total committed space for class metadata is greater than
+ * MaxMetaspaceFreeRatio, the high-water mark will be lowered.
+ * If it is less than MinMetaspaceFreeRatio, the high-water mark will be raised.
+ * 
+ */ +public class HighWaterMarkTest extends FirstGCTest { + + public static void main(String... args) { + new HighWaterMarkTest().run(args); + } + + // value given in -XX:MetaspaceSize= + private long metaspaceSize = -1; + + // value given in -XX:MaxMetaspaceSize= + private long maxMetaspaceSize = -1; + + // value given in -XX:MinMetaspaceFreeRatio= + private long minMetaspaceFreeRatio = -1; + + // value given in -XX:MaxMetaspaceFreeRatio= + private long maxMetaspaceFreeRatio = -1; + + /** + * Parses arguments and vm options. + * Throws Fault in cases of wrong values or missed parameters. + * + * @param args command line options + */ + @Override + protected void parseArgs(String[] args) { + if (args.length > 0) { + printUsage(); + throw new Fault("Illegal arguments: " + Arrays.asList(args)); + } + + if (gclogFileName == null) { + printUsage(); + throw new Fault("Log file name is not given"); + } + + final String metaSize = "-XX:MetaspaceSize="; + final String maxMetaSize = "-XX:MaxMetaspaceSize="; + final String minRatio = "-XX:MinMetaspaceFreeRatio="; + final String maxRatio = "-XX:MaxMetaspaceFreeRatio="; + + for (String va: vmArgs) { + if (va.startsWith(metaSize)) { + metaspaceSize = parseValue(va.substring(metaSize.length())); + } else if (va.startsWith(maxMetaSize)) { + maxMetaspaceSize = parseValue(va.substring(maxMetaSize.length())); + } else if (va.startsWith(minRatio)) { + minMetaspaceFreeRatio = parseValue(va.substring(minRatio.length())); + } else if (va.startsWith(maxRatio)) { + maxMetaspaceFreeRatio = parseValue(va.substring(maxRatio.length())); + } + } + + if (metaspaceSize < 0) { + printUsage(); + throw new Fault("-XX:MetaspaceSize is not specified"); + } else if (maxMetaspaceSize < 0) { + printUsage(); + throw new Fault("-XX:MaxMetaspaceSize is not specified"); + } else if (minMetaspaceFreeRatio < 0) { + printUsage(); + throw new Fault("-XX:MinMetaspaceFreeRatio is not specified"); + } else if (maxMetaspaceFreeRatio < 0) { + printUsage(); + throw new Fault("-XX:MaxMetaspaceFreeRatio is not specified"); + } + + } + + private void printUsage() { + System.err.println("Usage: "); + System.err.println("java [-Xlog:gc:] [-XX:MetaspaceSize=..] [-XX:MaxMetaspaceSize=..] [-XX:MinMetaspaceFreeRatio=..] [-XX:MaxMetaspaceFreeRatio=..] \\"); + System.err.println(" " + HighWaterMarkTest.class.getCanonicalName()); + } + + /** + * Check that MinMetaspaceFreeRatio/MaxMetaspaceFreeRatio settings + * affects the moment of the next GC. + * + * Eats memory until amount of committed metaspace achieves a certain level + * (between MetaspaceSize and MaxMetaspaceSize). + * Then checks how many times GC has been invoked. + * + */ + @Override + public void doCheck() { + + // to avoid timeouts we limit the number of attempts + int attempts = 0; + int maxAttempts = 10_000; + + // in between metaspaceSize and maxMetaspaceSize + // no OOM is exepcted. + long committedLevel = (metaspaceSize + maxMetaspaceSize) / 2; + + while (getCommitted() < committedLevel && attempts < maxAttempts) { + attempts++; + loadNewClasses(9, true); // load classes and keep references + loadNewClasses(1, false); // load classes without keeping references + } + + + System.out.println("% Classes loaded: " + attempts*10); + System.out.println("% Used metaspace : " + bytes2k(getUsed())); + System.out.println("% Committed metaspce: " + bytes2k(getCommitted())); + + cleanLoadedClasses(); + + if (attempts == maxAttempts) { + throw new Fault("Committed amount hasn't achieved " + bytes2k(committedLevel)); + } + + if (VMRuntimeEnvUtils.isVMOptionEnabled("UseConcMarkSweepGC")) { + System.out.println("ConcMarkSweep is used, cannot count GC"); + return; + } + + int gcCount = getMetaspaceGCCount(); + if (gcCount < 0) { + // perhpas, it's better to silently pass here... Let's see. + throw new Fault ("Unable to count full collections, could be an env issue"); + } + System.out.println("% GC has been invoked: " + gcCount + " times"); + + if (VMRuntimeEnvUtils.isVMOptionEnabled("UseG1GC") && + VMRuntimeEnvUtils.isVMOptionEnabled("ClassUnloadingWithConcurrentMark")) { + System.out.println("% isG1ClassUnloading: true"); + if (gcCount != 0) { + throw new Fault ("G1 should unload classes, full GC is not expected"); + } + } else { + if (maxMetaspaceFreeRatio <= 1) { + // min/max = 0/1 boundary value + // GC should happen very often + checkGCCount(gcCount, 20, -1); + } else if (minMetaspaceFreeRatio >= 99) { + // min/max = 99/100 boundary value + // GC should happen very rare + checkGCCount(gcCount, -1, 2); + } else if (minMetaspaceFreeRatio >= 10 && maxMetaspaceFreeRatio <= 20) { + // GC should happen quite often + checkGCCount(gcCount, 3, 30); + } else if (minMetaspaceFreeRatio >= 70 && maxMetaspaceFreeRatio <= 80) { + // GC should happen quite often + checkGCCount(gcCount, 1, 3); + } else { + // hard to estimate + } + } + + } + /** + * Checks that count of GC fits the expected range. + * Throws Fault if count is unexpected. + * + * @param count how many times GC has happened + * @param min expected minimum, if under zero - undefined + * @param max expected maximum, if under zero - undefined + */ + void checkGCCount(int count, int min, int max) { + if (min < 0) { + if(count > max) { + throw new Fault("GC has happened too often: " + count + " times, " + + "expected count: less than " + max); + } + } else if (max < 0) { + if(count < min) { + throw new Fault("GC has happened too rare: " + count + " times, " + + "expected count greater than " + min); + } + } else if (count < min || count > max ) { + throw new Fault ("GC has happened " + count + " times, " + + "approximate count is " + min + " to " + max); + } + } + +}