--- /dev/null 2018-05-02 09:10:36.097102948 -0700
+++ new/test/hotspot/jtreg/vmTestbase/metaspace/gc/MetaspaceBaseGC.java 2018-05-08 10:20:13.857632157 -0700
@@ -0,0 +1,314 @@
+/*
+ * 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.lang.management.ManagementFactory;
+import java.lang.management.MemoryPoolMXBean;
+import java.lang.management.MemoryUsage;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import jdk.internal.misc.Unsafe;
+
+/**
+ * Test that checks how GC works with Metaspace and "Compared Class Space".
+ *
+ * It comprises 3 test cases:
+ *
+ * - testcase1 - checks that used/committed memory doesn't grow
+ * when gc is invoked
+ * - testcase2 - checks that gc is invoked when the class metadata u
+ * sage reaches MetaspaceSize
+ * - testcase3 - checks used/committed grow, inspite of gc is invoked
+ *
+ *
+ * It's supposed that this class will be executed with various setting of VM
+ * flags. Via execute args it's possible to say which test cases to run and
+ * what space to test: Metaspace or Compared Class Space.
+ */
+public abstract class MetaspaceBaseGC {
+
+ // storage of loaded classes
+ private final Map loadedClasses = new HashMap<>();
+ private static int counter = 0;
+
+ // pool to test
+ protected MemoryPoolMXBean pool = null;
+
+ // memory page size
+ protected static final long PAGE_SIZE = detectPageSize();
+
+ // true when PAGE_SIZE is large and
+ protected boolean useLargepages = false;
+
+ // where the log will be saved
+ protected String gclogFileName = null;
+
+ protected final Set vmArgs = new HashSet<>();
+
+ protected abstract void parseArgs(String args[]);
+ protected abstract String getPoolName();
+ protected abstract void doCheck();
+
+ public final void run(String args[]) {
+ configure(args);
+ if (pool == null) {
+ System.out.println("%%% Cannot pull the pool, most likely 32-bits only");
+ return;
+ }
+ System.out.println("%%% Working with " + getPoolName());
+ for (String vmA: vmArgs) {
+ if (vmA.contains("Metaspace") || vmA.contains("Compressed")) {
+ System.out.println("% " + vmA);
+ }
+ }
+ doCheck();
+ System.out.println("% Test passed.");
+ }
+
+
+ protected void configure(String args[]) {
+ vmArgs.addAll(ManagementFactory.getRuntimeMXBean().getInputArguments());
+ useLargepages = PAGE_SIZE > 1_000_000 && !vmArgs.contains("-XX:-UseLargePagesInMetaspace");
+
+ System.out.println(vmArgs);
+
+ pool = getMemoryPool(getPoolName());
+ if (pool == null) {
+ return; // nothing to check
+ }
+ for (String arg: vmArgs) {
+ if (arg.startsWith("-Xlog:gc") && arg.length() > 8) {
+ gclogFileName = arg.substring(arg.lastIndexOf(':') + 1);
+ }
+ }
+ parseArgs(args);
+ }
+
+
+ /**
+ * Imitates class loading.
+ * Each invocation of this method causes a new class loader object is created
+ * and a new class is loaded by this class loader.
+ * Method throws OOM when run out of memory.
+ *
+ * @param times how many classes to load
+ * @param keepRefs true, if references to created classes should be stored
+ */
+ protected void loadNewClasses(int times, boolean keepRefs) {
+ for (int i = 0; i < times; i++) {
+ try {
+ String jarUrl = "file:" + counter + ".jar";
+ counter++;
+ URL[] urls = new URL[]{new URL(jarUrl)};
+ URLClassLoader cl = new URLClassLoader(urls);
+ MetaspaceBaseGC.Foo foo = (MetaspaceBaseGC.Foo) Proxy.newProxyInstance(cl,
+ new Class[]{MetaspaceBaseGC.Foo.class},
+ new MetaspaceBaseGC.FooInvocationHandler(new MetaspaceBaseGC.FooBar()));
+ if (keepRefs) {
+ loadedClasses.put(jarUrl, foo);
+ }
+ } catch (java.net.MalformedURLException badThing) {
+ // should never occur
+ System.err.println("Unexpeted error: " + badThing);
+ throw new RuntimeException(badThing);
+ }
+ }
+
+ }
+
+ /**
+ * Cleans references to loaded classes.
+ */
+ protected void cleanLoadedClasses() {
+ loadedClasses.clear();
+ }
+
+ /**
+ * Invokes System.gc() and sleeps a little.
+ */
+ protected void gc() {
+ System.gc();
+ try {
+ Thread.currentThread().sleep(500);
+ } catch (Exception whatever) {
+ }
+ }
+
+ /**
+ * Reads gc.log file and returns it as a list of lines.
+ * It's supposed that the test is executed with -Xlog:gc:gc.log option.
+ *
+ * @return List of strings the gc.log file is comprised.
+ * @throws IOException if problem occurred while reading.
+ */
+ protected List readGCLog() throws IOException {
+ return Files.readAllLines(Paths.get(".", gclogFileName));
+ }
+
+ /**
+ * Reads gc.log file and counts GC induced by metaspace.
+ * Note: this method doesn't work for ConcMarkSweep...
+ * @return how many times GC induced by metaspace has occurred.
+ */
+ protected int getMetaspaceGCCount() {
+ int count = 0;
+ try {
+ for (String line: readGCLog()) {
+ if (line.indexOf("Pause Full") > 0 && line.indexOf("Meta") > 0) {
+ count++;
+ }
+ }
+ return count;
+ } catch (Throwable t) {
+ t.printStackTrace(System.err);
+ return -1;
+ }
+ }
+
+ protected String lastGCLogLine() {
+ if (gclogFileName == null) {
+ return "";
+ }
+ try {
+ List list = Files.readAllLines(Paths.get(".", gclogFileName));
+ return list.get(list.size() - 1);
+ } catch (IOException e) {
+ return "File not found";
+ }
+ }
+
+ /**
+ * Does it best to checks if the last GC was caused by metaspace.
+ *
+ * This method looks into gc.log file (if -Xloggc:file is given) and returns
+ * true if the last line in the log contains the "Metadata" word.
+ * It's not very reliable way to check, log might not be flushed yet.
+ *
+ * @return
+ */
+ protected boolean isMetaspaceGC() {
+ return lastGCLogLine().contains("Metadata");
+ }
+
+ /**
+ * Prints amounts of used and committed metaspace preceeded by the message
+ * @param mesg a message to printed prior usages
+ */
+ protected void printMemoryUsage(String mesg) {
+ MemoryUsage mu = pool.getUsage();
+ printMemoryUsage(mesg, mu.getUsed(), mu.getCommitted());
+ }
+ protected void printMemoryUsage(String mesg, long v1, long v2) {
+ System.out.println(mesg + ": " + bytes2k(v1) + " : " + bytes2k(v2));
+ }
+ protected String bytes2k(long v) {
+ return (v / 1024) + "k";
+ }
+
+
+
+ /**
+ * @return amount of used memory
+ */
+ public long getUsed() {
+ return pool.getUsage().getUsed();
+ }
+
+ /**
+ * @return amount of committed memory
+ */
+ public long getCommitted() {
+ return pool.getUsage().getCommitted();
+ }
+
+ private static MemoryPoolMXBean getMemoryPool(String name) {
+ List pools = ManagementFactory.getMemoryPoolMXBeans();
+ for (MemoryPoolMXBean pool : pools) {
+ if (pool.getName().equals(name)) {
+ return pool;
+ }
+ }
+ return null;
+ }
+
+ private static long detectPageSize() {
+ try {
+ Unsafe unsafe = Unsafe.getUnsafe();
+
+ int pageSize = unsafe.pageSize();
+ System.out.println("Page size: " + pageSize);
+ return pageSize;
+ } catch (Exception e) {
+ throw new Fault("Cannot detect page size");
+ }
+ }
+
+
+ long parseValue(String s) {
+ s = s.toLowerCase();
+ int multiplier = 1;
+ switch (s.charAt(s.length() - 1)) {
+ case 'g': multiplier = 1024*1024*1024; break;
+ case 'm': multiplier = 1024*1024; break;
+ case 'k': multiplier = 1024; break;
+ }
+ if (multiplier == 1) {
+ return Long.parseLong(s);
+ } else {
+ return Long.parseLong(s.substring(0, s.length() - 1)) * multiplier;
+ }
+ }
+
+ public static interface Foo {
+ }
+
+ public static class FooBar implements Foo {
+ }
+
+ class FooInvocationHandler implements InvocationHandler {
+ private final Foo foo;
+
+ FooInvocationHandler(MetaspaceBaseGC.Foo foo) {
+ this.foo = foo;
+ }
+
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ return method.invoke(foo, args);
+ }
+ }
+
+}