/* * 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: * * * 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()); 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. * @return how many times GC induced by metaspace has occurred. */ protected int getMetaspaceGCCount() { int count = 0; try { for (String line: readGCLog()) { if (line.indexOf("Metadata GC ") > 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); } } }