1 /*
   2  * Copyright (c) 2013, 2019, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 
  25 /*
  26  * @test
  27  *
  28  * @bug 8217432
  29  * @summary converted from VM Testbase metaspace/shrink_grow/ShrinkGrowTest.
  30  *
  31  * @requires vm.opt.final.ClassUnloading
  32  * @library /vmTestbase /test/lib
  33  * @run driver jdk.test.lib.FileInstaller . .
  34  * @run main/othervm
  35  *      -XX:MetaspaceSize=10m
  36  *      -XX:MaxMetaspaceSize=20m
  37  *      -Xlog:gc*:gc.log
  38  *      metaspace.shrink_grow.ShrinkGrowTest.ShrinkGrowTest
  39  */
  40 
  41 package metaspace.shrink_grow.ShrinkGrowTest;
  42 
  43 import java.lang.reflect.InvocationHandler;
  44 import java.lang.reflect.Method;
  45 import java.lang.reflect.Proxy;
  46 import java.lang.management.ManagementFactory;
  47 import java.lang.management.MemoryPoolMXBean;
  48 import java.net.URL;
  49 import java.net.URLClassLoader;
  50 import java.util.HashMap;
  51 import java.util.Map;
  52 
  53 /**
  54  * This is the main test in the metaspace shrink/grow series.
  55  *
  56  * It tries to allocate all available metespace (loads new classes and keeps
  57  * them in map), then checks that loading new classes causes OOM.
  58  * After that it does cleanup loaded classes and then expect the new classes
  59  * could be loaded again.
  60  *
  61  * <b>Note</b>: Don't forget to limit the metaspace size by giving
  62  * -XX:MaxMetaspaceSize=100k vm option.
  63  */
  64 public class ShrinkGrowTest {
  65 
  66     /**
  67      * Dead classes storage.
  68      */
  69     private final Map<String, ShrinkGrowTest.Foo> loadedClasses = new HashMap<>();
  70 
  71     private static int counter = 0;
  72 
  73     private String errorMessage = "not completed";
  74 
  75      // thread id to distinguish threads in output
  76     private final String whoAmI;
  77 
  78     // the limit of classes to load expecting OOM
  79     private final int maxClassesToLoad;
  80 
  81     public static void main(String[] args) {
  82         String name = args.length > 0 ? args[0] : "singleTest" ;
  83         new ShrinkGrowTest(name, 20000).run();
  84     }
  85 
  86     /**
  87      * @param name - thread id used in logging
  88      * @param classesToLoad - the limit of classes to load expecting OOM
  89      */
  90     public ShrinkGrowTest(String name, int classesToLoad) {
  91         whoAmI = name;
  92         maxClassesToLoad = classesToLoad;
  93 
  94     }
  95 
  96     /**
  97      * Just outputs given message preceeded with the thread identifier
  98      *
  99      * @param message text to print out
 100      */
 101     void log(String message) {
 102         System.out.println("%" + whoAmI + "% " + message);
 103     }
 104 
 105     void throwFault(String message) {
 106         throw new TestFault("%" + whoAmI + "% " + message);
 107     }
 108 
 109     void throwFault(String message, Throwable t) {
 110         throw new TestFault("%" + whoAmI + "% " + message, t);
 111     }
 112 
 113     /**
 114      * Entry to the test.
 115      * Just exits if passes or throws an Error if failed.
 116      */
 117     public void run() {
 118         if (System.getProperty("requiresCompressedClassSpace") != null &&
 119                    !isCompressedClassSpaceAvailable()) {
 120                 System.out.println("Not applicalbe, Compressed Class Space is required");
 121             return;
 122         }
 123 
 124         try {
 125             log("Bootstrapping string concatenation for " + whoAmI );
 126             go();
 127             // The quest completed! Yahoo!
 128             setErrorMessage(null);
 129             log("passed");
 130         } catch (TestFault failure) {
 131             failure.printStackTrace(System.err);
 132             setErrorMessage(failure.getMessage());
 133             log("failed :" + errorMessage);
 134             throw failure;
 135         } catch (Throwable badThing) {
 136             setErrorMessage(badThing.toString());
 137             throw new TestFault(badThing);
 138         }
 139     }
 140 
 141     private void go() {
 142         // step 1: eat all metaspace
 143         log("eating metaspace");
 144         runOutOfMetaspace(maxClassesToLoad);
 145 
 146         // step 2: try to load one more class
 147         // it should be impossible
 148         try {
 149             eatALittleMemory();
 150             throwFault("We haven't cleaned metaspace yet!");
 151         } catch (OutOfMemoryError error) {
 152             if (!isMetaspaceError(error)) {
 153                 throwFault("Hmm, we ran out metaspace. Metaspace error is still excpected here " + error, error);
 154             }
 155         }
 156 
 157         // step 3: clean up metaspace and try loading a class again.
 158         log("washing hands before meal");
 159         loadedClasses.clear();
 160         System.gc();
 161         try {
 162             log("one more try to eat");
 163             eatALittleMemory();
 164         } catch (OutOfMemoryError error) {
 165             throwFault("we already should be able to consume metaspace " + error, error);
 166         }
 167     }
 168 
 169     /**
 170      * @return true if the test has successfully passed.
 171      */
 172     public boolean isPassed() {
 173         return errorMessage == null;
 174     }
 175 
 176     /**
 177      * @return message describing the reason of failure, or null if passes
 178      */
 179     public String getErrorMessage() {
 180         return errorMessage;
 181     }
 182 
 183     /**
 184      * Sets the message describing why test failed, or null if test passed
 185      */
 186     void setErrorMessage(String msg) {
 187         errorMessage = msg;
 188     }
 189 
 190     /**
 191      * Loads new classes until OOM.
 192      * Checks that OOM is caused by metaspace and throws an Error if not.
 193      *
 194      * @param times - maximum limit of classes to load.
 195      */
 196     private void runOutOfMetaspace(int times) {
 197         try {
 198             for (int i = 0; i < times; i++) {
 199                 eatALittleMemory();
 200             }
 201         } catch (OutOfMemoryError error) {
 202             if (isMetaspaceError(error)) {
 203                 return;
 204             }
 205             throwFault("We ran out of another space, not metaspace: " + error, error);
 206         }
 207         throwFault("OOM hasn't happened after " + times + " iterations. Might be too much space?..");
 208     }
 209 
 210     /**
 211      * Imitates class loading.
 212      * Each invocation of this method causes a new class loader object is created
 213      * and a new class is loaded by this class loader.
 214      * Method throws OOM when run out of memory.
 215      */
 216     private void eatALittleMemory() {
 217         try {
 218             String jarUrl = "file:" + counter + ".jar";
 219             counter++;
 220             URL[] urls = new URL[]{new URL(jarUrl)};
 221             URLClassLoader cl = new URLClassLoader(urls);
 222             ShrinkGrowTest.Foo foo = (ShrinkGrowTest.Foo) Proxy.newProxyInstance(cl,
 223                     new Class[]{ShrinkGrowTest.Foo.class},
 224                     new ShrinkGrowTest.FooInvocationHandler(new ShrinkGrowTest.FooBar()));
 225             loadedClasses.put(jarUrl, foo);
 226         } catch (java.net.MalformedURLException badThing) {
 227             // should never occur
 228             throwFault("Unexpeted error: " + badThing, badThing);
 229         }
 230 
 231     }
 232 
 233     /**
 234      * Checks if given OOM is about metaspace
 235      * @param error OOM
 236      * @return true if message contains 'metaspace' word, false otherwise.
 237      */
 238     boolean isMetaspaceError(OutOfMemoryError error) {
 239             String message = error.getMessage();
 240         return message != null && (message.contains("Metaspace") ||
 241                         message.contains("Compressed class space"));
 242     }
 243 
 244     boolean isCompressedClassSpaceAvailable() {
 245         for (MemoryPoolMXBean pool : ManagementFactory.getMemoryPoolMXBeans()) {
 246             if (pool.getName().equalsIgnoreCase("Compressed class space")) {
 247                 return true;
 248             }
 249         }
 250         return false;
 251     }
 252 
 253     /**
 254      * Runtime exception signaling test failure.
 255      */
 256     public static class TestFault extends RuntimeException {
 257         public TestFault(String message) {
 258             super(message);
 259         }
 260         public TestFault(Throwable t) {
 261             super(t);
 262         }
 263         public TestFault(String message, Throwable t) {
 264             super(message, t);
 265         }
 266     }
 267 
 268     public static interface Foo {
 269     }
 270 
 271     public static class FooBar implements ShrinkGrowTest.Foo {
 272     }
 273 
 274     class FooInvocationHandler implements InvocationHandler {
 275         private final ShrinkGrowTest.Foo foo;
 276 
 277         FooInvocationHandler(ShrinkGrowTest.Foo foo) {
 278             this.foo = foo;
 279         }
 280 
 281         @Override
 282         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 283             return method.invoke(foo, args);
 284         }
 285     }
 286 }