blob: 86dfed99bc6f945088583ac59f0cb62399ee1e14 [file] [log] [blame]
/*
* 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.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static metaspace.gc.MetaspaceBaseGC.PAGE_SIZE;
/**
* Test for metaspace GC
*
* <ul>
* <li>MetaspaceSize
* </ul>
* 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
* <pre>
* 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.
* </pre>
*/
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=<value>
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("%## Exepcted: " + 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");
}
}