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