/* * 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. */ /* * @test * * @summary converted from VM Testbase metaspace/shrink_grow/ShrinkGrowTest. * * @requires vm.opt.final.ClassUnloading * @library /vmTestbase /test/lib * @run driver jdk.test.lib.FileInstaller . . * @run main/othervm * -XX:MetaspaceSize=10m * -XX:MaxMetaspaceSize=20m * -Xlog:gc*:gc.log * metaspace.shrink_grow.ShrinkGrowTest.ShrinkGrowTest */ package metaspace.shrink_grow.ShrinkGrowTest; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.management.ManagementFactory; import java.lang.management.MemoryPoolMXBean; import java.net.URL; import java.net.URLClassLoader; import java.util.HashMap; import java.util.Map; /** * This is the main test in the metaspace shrink/grow series. * * It tries to allocate all available metespace (loads new classes and keeps * them in map), then checks that loading new classes causes OOM. * After that it does cleanup loaded classes and then expect the new classes * could be loaded again. * * Note: Don't forget to limit the metaspace size by giving * -XX:MaxMetaspaceSize=100k vm option. */ public class ShrinkGrowTest { /** * Dead classes storage. */ private final Map loadedClasses = new HashMap<>(); private static int counter = 0; private String errorMessage = "not completed"; // thread id to distinguish threads in output private final String whoAmI; // the limit of classes to load expecting OOM private final int maxClassesToLoad; public static void main(String[] args) { String name = args.length > 0 ? args[0] : "singleTest" ; new ShrinkGrowTest(name, 20000).run(); } /** * @param name - thread id used in logging * @param classesToLoad - the limit of classes to load expecting OOM */ public ShrinkGrowTest(String name, int classesToLoad) { whoAmI = name; maxClassesToLoad = classesToLoad; } /** * Just outputs given message preceeded with the thread identifier * * @param message text to print out */ void log(String message) { System.out.println("%" + whoAmI + "% " + message); } void throwFault(String message) { throw new TestFault("%" + whoAmI + "% " + message); } void throwFault(String message, Throwable t) { throw new TestFault("%" + whoAmI + "% " + message, t); } /** * Entry to the test. * Just exits if passes or throws an Error if failed. */ public void run() { if (System.getProperty("requiresCompressedClassSpace") != null && !isCompressedClassSpaceAvailable()) { System.out.println("Not applicalbe, Compressed Class Space is required"); return; } try { log("Bootstrapping string concatenation for " + whoAmI ); go(); // The quest completed! Yahoo! setErrorMessage(null); log("passed"); } catch (TestFault failure) { failure.printStackTrace(System.err); setErrorMessage(failure.getMessage()); log("failed :" + errorMessage); throw failure; } catch (Throwable badThing) { setErrorMessage(badThing.toString()); throw new TestFault(badThing); } } private void go() { // step 1: eat all metaspace log("eating metaspace"); runOutOfMetaspace(maxClassesToLoad); // step 2: try to load one more class // it should be impossible try { eatALittleMemory(); throwFault("We haven't cleaned metaspace yet!"); } catch (OutOfMemoryError error) { if (!isMetaspaceError(error)) { throwFault("Hmm, we ran out metaspace. Metaspace error is still excpected here " + error, error); } } // step 3: clean up metaspace and try loading a class again. log("washing hands before meal"); loadedClasses.clear(); System.gc(); try { log("one more try to eat"); eatALittleMemory(); } catch (OutOfMemoryError error) { throwFault("we already should be able to consume metaspace " + error, error); } } /** * @return true if the test has successfully passed. */ public boolean isPassed() { return errorMessage == null; } /** * @return message describing the reason of failure, or null if passes */ public String getErrorMessage() { return errorMessage; } /** * Sets the message describing why test failed, or null if test passed */ void setErrorMessage(String msg) { errorMessage = msg; } /** * Loads new classes until OOM. * Checks that OOM is caused by metaspace and throws an Error if not. * * @param times - maximum limit of classes to load. */ private void runOutOfMetaspace(int times) { try { for (int i = 0; i < times; i++) { eatALittleMemory(); } } catch (OutOfMemoryError error) { if (isMetaspaceError(error)) { return; } throwFault("We ran out of another space, not metaspace: " + error, error); } throwFault("OOM hasn't happened after " + times + " iterations. Might be too much space?.."); } /** * 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. */ private void eatALittleMemory() { try { String jarUrl = "file:" + counter + ".jar"; counter++; URL[] urls = new URL[]{new URL(jarUrl)}; URLClassLoader cl = new URLClassLoader(urls); ShrinkGrowTest.Foo foo = (ShrinkGrowTest.Foo) Proxy.newProxyInstance(cl, new Class[]{ShrinkGrowTest.Foo.class}, new ShrinkGrowTest.FooInvocationHandler(new ShrinkGrowTest.FooBar())); loadedClasses.put(jarUrl, foo); } catch (java.net.MalformedURLException badThing) { // should never occur throwFault("Unexpeted error: " + badThing, badThing); } } /** * Checks if given OOM is about metaspace * @param error OOM * @return true if message contains 'metaspace' word, false otherwise. */ boolean isMetaspaceError(OutOfMemoryError error) { String message = error.getMessage(); return message != null && (message.contains("Metaspace") || message.contains("Compressed class space")); } boolean isCompressedClassSpaceAvailable() { for (MemoryPoolMXBean pool : ManagementFactory.getMemoryPoolMXBeans()) { if (pool.getName().equalsIgnoreCase("Compressed class space")) { return true; } } return false; } /** * Runtime exception signaling test failure. */ public static class TestFault extends RuntimeException { public TestFault(String message) { super(message); } public TestFault(Throwable t) { super(t); } public TestFault(String message, Throwable t) { super(message, t); } } public static interface Foo { } public static class FooBar implements ShrinkGrowTest.Foo { } class FooInvocationHandler implements InvocationHandler { private final ShrinkGrowTest.Foo foo; FooInvocationHandler(ShrinkGrowTest.Foo foo) { this.foo = foo; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return method.invoke(foo, args); } } }