/* * Copyright (c) 2013, 2022, 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.io.IOException; import java.util.regex.Matcher; import java.util.regex.Pattern; import static metaspace.gc.MetaspaceBaseGC.PAGE_SIZE; /** * Test for metaspace GC * * * Test checks that the first GC happens when metaspace committed is next to * MetaspaceSize value. * * Based on actual events (JDK 8 GC tuning document) * * Quating: 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.
 *
 * The flag MetaspaceSize can be set higher to avoid early garbage collections
 * induced for class metadata. The amount of class metadata allocated for
 * an application is application dependent and general guidelines do not
 * exist for the selection of MetaspaceSize. The default size of MetaspaceSize
 * is platform dependent and ranges from 12 MB to about 20 MB.
 * 
*/ public class FirstGCTest extends MetaspaceBaseGC { /** * Current amount of the used metaspace */ protected long used = 0; /** * Current amount of the committed metaspace */ protected long committed = 0; /** * Previous amount of the used metaspace */ protected long p_used = 0 ; /** * Previous amount of the committed metaspace */ protected long p_committed = 0; public static void main(String... args) { new FirstGCTest().run(args); } // value given in -XX:metaspaceSize= private long metaspaceSize = -1; @Override protected void parseArgs(String[] args) { final String XXSize = "-XX:MetaspaceSize="; for (String va: vmArgs) { if (va.startsWith(XXSize)) { metaspaceSize = parseValue(va.substring(XXSize.length())); } } } @Override protected String getPoolName() { return "Metaspace"; } /** * Check for the first GC moment. * * Eats memory until GC is invoked (amount of used metaspace became less); * Checks that committed memory is close to MemaspaceSize. * Eats memory until the second GC to check min/max ratio options have effect. */ @Override public void doCheck() { int gcCount = super.getMetaspaceGCCount(); if (gcCount == 0) { // gc hasn't happened yet. Start loading classes. boolean gcHappened = this.eatMemoryUntilGC(50000); if (!gcHappened) { throw new Fault("GC hasn't happened"); } System.out.println("% GC: " + super.lastGCLogLine()); System.out.println("% used : " + p_used + " --> " + used); System.out.println("% committed: " + p_committed + " --> " + committed); checkCommitted(p_committed); } else { // everything has happened before checkCommitted(detectCommittedFromGCLog()); } } /** * Check that committed amount is close to expected value (MetaspaceSize) * * @param committedAmount - value to check */ void checkCommitted(long committedAmount) { if (metaspaceSize > 0) { // -XX:MetaspaceSize is given if (Math.abs((int) (metaspaceSize - committedAmount)) < PAGE_SIZE) { System.out.println("% GC happened at the right moment"); return; } if (!isMetaspaceGC()) { System.out.println("% GC wasn't induced by metaspace, cannot check the moment :("); return; } System.err.println("%## GC happened at the wrong moment, " + "the amount of committed space significantly differs " + "from the expected amount"); System.err.println("%## Real : " + committedAmount); System.err.println("%## Expected: " + metaspaceSize); throw new Fault("GC happened at the wrong moment"); } else { // -XX:MetaspaceSize is not given, check for default values if (11_500_000 < committedAmount && committedAmount < 22_500_000) { System.out.println("% GC happened when the committed amout was from 12 MB to about 20 MB."); return; } if (!isMetaspaceGC()) { System.out.println("% GC wasn't induced by metaspace, this is excuse"); return; } System.err.println("%## GC happened at the wrong moment, " + "the amount of committed space was expected from 12 MB to about 20 MB"); System.err.println("%## Real : " + committedAmount); throw new Fault("It was the wrong moment when GC happened"); } } /** * Load new classes without keeping references to them trying to provoke GC. * Stops if GC is detected, or number of attempts exceeds the given limit. * * @param times limit of attempts to provoke GC * @return true if GC has happened, false if limit has exceeded. */ protected boolean eatMemoryUntilGC(int times) { System.out.println("%%%% Loading classes"); System.out.println("% iter# : used : commited"); System.out.println(".............................."); for (int i = 1; i < times; i++) { loadNewClasses(1, false); if (i % 1000 == 0) { printMemoryUsage("% " + i + " "); } p_used = used; p_committed = committed; used = getUsed(); committed = getCommitted(); if (used < p_used) { return true; } } return false; } /** * If the first full GC has already happened we will try to detect * the committed amount from the gc.log file. * * @return committed amount detected * @throws Fault if failed to detect. */ protected long detectCommittedFromGCLog() { // parse gc.log to extract the committed value from string like: // Metaspace used 10133K, capacity 10190K, committed 10240K, reserved 10240Kl System.out.println("%%%% Parsing gc log to detect the moment of the first GC"); String format = ".*Metaspace.* used .*, capacity .*, committed (\\d+)([KMGkmg]), reserved .*"; Pattern p = Pattern.compile(format); try { for (String line: readGCLog()) { Matcher m = p.matcher(line); if (m.matches()) { int amount = Integer.parseInt(m.group(1)); int multi = 1; switch (m.group(2).toLowerCase()) { case "k": multi = 1024; break; case "m": multi = 1024*1024; break; case "g": multi = 1024*1024*1024; break; } long value = amount * multi; System.out.println("% Committed detected: " + value); return value; } } } catch (IOException e) { throw new Fault("Cannot read from the GC log"); } System.out.println("% String that matches pattern '" + format + "' not found in the GC log file."); throw new Fault("Unable to detect the moment of GC from log file"); } }