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