1 /*
   2  * Copyright (c) 2007, 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 package nsk.share.gc.gp;
  25 
  26 import java.io.IOException;
  27 import java.io.PrintWriter;
  28 import java.io.StringWriter;
  29 import java.util.*;
  30 import nsk.share.gc.gp.array.*;
  31 import nsk.share.gc.gp.string.*;
  32 import nsk.share.gc.gp.list.*;
  33 import nsk.share.gc.gp.tree.*;
  34 import nsk.share.gc.gp.misc.*;
  35 import nsk.share.gc.gp.classload.*;
  36 import nsk.share.gc.Memory;
  37 import nsk.share.TestBug;
  38 import nsk.share.test.*;
  39 
  40 /**
  41  * Utility methods for garbage producers.
  42  */
  43 public final class GarbageUtils {
  44         private static final int ALLOCATION_LIMIT = 50000000; //50 Mb
  45         private static GarbageProducers garbageProducers;
  46         private static List<GarbageProducer> primitiveArrayProducers;
  47         private static List<GarbageProducer> arrayProducers;
  48         private static final GarbageProducer  byteArrayProducer = new ByteArrayProducer();
  49         public static enum OOM_TYPE {
  50             ANY (),
  51             HEAP("Java heap space"),
  52             METASPACE("Metaspace", "Compressed class space");
  53 
  54             private final String[] expectedStrings;
  55             OOM_TYPE(String... expectedStrings) {
  56                 this.expectedStrings = expectedStrings;
  57             }
  58 
  59             /**
  60                          * Returns true if the given error message matches
  61                          * one of expected strings.
  62                          */
  63                         public boolean accept(String errorMessage) {
  64                 if (expectedStrings == null || expectedStrings.length == 0 || errorMessage == null) {
  65                     return true;
  66                 }
  67                 for (String s: expectedStrings) {
  68                     if (errorMessage.indexOf(s) != -1) {
  69                         return true;
  70                     }
  71                 }
  72                 return false;
  73             }
  74         };
  75 
  76         // Force loading of OOM_TYPE and calling of enum constructors when loading GarbageUtils class.
  77         public static final Object[] thisIsGarbageArray_theOnlyPurposeForCreatingItAndDeclaringItPublicIsToInitializeIntancesOfOOMEnumberation = new Object[] { OOM_TYPE.ANY, OOM_TYPE.HEAP, OOM_TYPE.METASPACE };
  78 
  79         // Force early loading of classes that might otherwise unexpectedly fail
  80         // class loading during testing due to high memory pressure.
  81         public static final StringWriter preloadStringWriter = new StringWriter(1);
  82         public static final PrintWriter preloadPrintWriter = new PrintWriter(preloadStringWriter);
  83         public static final Throwable preloadThrowable = new Throwable("preload");
  84         public static final StackTraceElement[] preloadStackTraceElement = preloadThrowable.getStackTrace();
  85 
  86         private GarbageUtils() {
  87         }
  88 
  89         /**
  90          * Eat memory using execution controller that waits for 2 minutes.
  91          * @return number of OOME occured
  92          */
  93         public static int eatMemory() {
  94                 return eatMemory(2 * 60 * 1000);
  95         }
  96 
  97         /**
  98          * Eat memory using execution controller that waits for timeout.
  99          * @return number of OOME occured
 100          */
 101         public static int eatMemory(final long timeout) {
 102                 return eatMemory(new ExecutionController() {
 103                         final long initialTime = System.currentTimeMillis();
 104 
 105                                 @Override
 106                                 public void start(long stdIterations) {}
 107 
 108                                 @Override
 109                                 public boolean iteration() {return false;}
 110 
 111                                 @Override
 112                                 public boolean continueExecution() {
 113                                         return System.currentTimeMillis() - initialTime < timeout;
 114                                 }
 115 
 116                                 @Override
 117                                 public long getIteration() {return 0;}
 118 
 119                                 @Override
 120                                 public void finish() {}
 121                 });
 122         }
 123 
 124 
 125         /**
 126          * Eat memory using given execution controller and garbage producer.
 127          *
 128          * @param stresser execution controller
 129          * @param gp garbage producer
 130          * @return number of OOME occured
 131          */
 132         public static int eatMemory(ExecutionController stresser) {
 133             return eatMemory(stresser, byteArrayProducer, 50, 100, 2, OOM_TYPE.ANY);
 134         }
 135 
 136         /**
 137          * Eat memory using given execution controller and garbage producer.
 138          *
 139          * @param stresser execution controller
 140          * @param gp garbage producer
 141          * @return number of OOME occured
 142          */
 143         public static int eatMemory(ExecutionController stresser, GarbageProducer gp) {
 144             return eatMemory(stresser, gp, 50, 100, 2, OOM_TYPE.ANY);
 145         }
 146 
 147         /**
 148          * Eat memory using given garbage producer and given factor.
 149          *
 150          * @param gp garbage producer
 151          * @param factor factor to divide the array size by
 152          * @return number of OOME occured
 153          */
 154         public static int eatMemory(ExecutionController stresser, GarbageProducer gp, long factor) {
 155             return eatMemory(stresser, gp, 50, 100, factor, OOM_TYPE.ANY);
 156         }
 157 
 158         /**
 159          * Eat memory using default(byte[]) garbage producer.
 160          *
 161          * Note that this method can throw Failure if any exception
 162          * is thrown while eating memory. To avoid OOM while allocating
 163          * exception we preallocate it before the lunch starts. It means
 164          * that exception stack trace does not correspond to the place
 165          * where exception is thrown, but points at start of the method.
 166          *
 167          * @param stresser stresser
 168          * @param initialFactor determines which portion of initial memory initial chunk will be
 169          * @param minMemoryChunk determines when to stop
 170          * @param factor factor to divide the array size by
 171          * @return number of OOME occured
 172          */
 173         public static int eatMemory(ExecutionController stresser,long initialFactor, long minMemoryChunk, long factor) {
 174             return eatMemory(stresser, byteArrayProducer, initialFactor, minMemoryChunk, factor, OOM_TYPE.ANY);
 175         }
 176 
 177         /**
 178          * Eat memory using given garbage producer.
 179          *
 180          * Note that this method can throw Failure if any exception
 181          * is thrown while eating memory. To avoid OOM while allocating
 182          * exception we preallocate it before the lunch starts. It means
 183          * that exception stack trace does not correspond to the place
 184          * where exception is thrown, but points at start of the method.
 185          *
 186          * @param stresser stresser to use
 187          * @param gp garbage producer
 188          * @param initialFactor determines which portion of initial memory initial chunk will be
 189          * @param minMemoryChunk determines when to stop
 190          * @param factor factor to divide the array size by. A value of 0 means that method returns after first  OOME
 191          * @param type of OutOfMemory Exception: Java heap space or Metadata space
 192          * @return number of OOME occured
 193          */
 194         public static int eatMemory(ExecutionController stresser, GarbageProducer gp, long initialFactor, long minMemoryChunk, long factor) {
 195             return eatMemory(stresser, gp, initialFactor, minMemoryChunk, factor, OOM_TYPE.ANY);
 196         }
 197 
 198         /**
 199          * Eat memory using given garbage producer.
 200          *
 201          * Note that this method can throw Failure if any exception
 202          * is thrown while eating memory. To avoid OOM while allocating
 203          * exception we preallocate it before the lunch starts. It means
 204          * that exception stack trace does not correspond to the place
 205          * where exception is thrown, but points at start of the method.
 206          *
 207          * @param stresser stresser to use
 208          * @param gp garbage producer
 209          * @param initialFactor determines which portion of initial memory initial chunk will be
 210          * @param minMemoryChunk determines when to stop
 211          * @param factor factor to divide the array size by. A value of 0 means that method returns after first  OOME
 212          * @param type of OutOfMemory Exception: Java heap space or Metadata space
 213          * @return number of OOME occured
 214          */
 215         public static int eatMemory(ExecutionController stresser, GarbageProducer gp, long initialFactor, long minMemoryChunk, long factor, OOM_TYPE type) {
 216                 int numberOfOOMEs = 0;
 217                 try {
 218                         StringWriter sw = new StringWriter(10000);
 219                         PrintWriter pw = new PrintWriter(sw);
 220                         byte[] someMemory = new byte[200000]; //200 Kb
 221                         try {
 222                                 Runtime runtime = Runtime.getRuntime();
 223                                 long maxMemory = runtime.maxMemory();
 224                                 long maxMemoryChunk = maxMemory / initialFactor;
 225                                 long chunk = maxMemoryChunk;
 226                                 chunk = chunk > ALLOCATION_LIMIT ? ALLOCATION_LIMIT : chunk;
 227                                 int allocations = 0;
 228                                 List<Object> storage = new ArrayList<Object>();
 229 
 230                                 while (chunk > minMemoryChunk && stresser.continueExecution()) {
 231                                         try {
 232                                                 storage.add(gp.create(chunk));
 233                                                 if (Thread.currentThread().isInterrupted()) {
 234                                                         return numberOfOOMEs;
 235                                                 }
 236                                                 // if we are able to eat chunk*factor let
 237                                                 // try to increase size of chunk
 238                                                 if (chunk * factor < maxMemoryChunk
 239                                                         && factor != 0 && allocations++ == factor + 1) {
 240                                                     chunk = chunk * factor;
 241                                                     allocations = 0;
 242                                                 }
 243                                         } catch (OutOfMemoryError e) {
 244                                             someMemory = null;
 245                                             if (type != OOM_TYPE.ANY) {
 246                                                 e.printStackTrace(pw);
 247                                                 pw.close();
 248                                                 if (type.accept(sw.toString())) {
 249                                                     numberOfOOMEs++;
 250                                                 } else {
 251                                                     // Trying to catch situation when Java generates OOM different type that test trying to catch
 252                                                     throw new TestBug("Test throw OOM of unexpected type." + sw.toString());
 253                                                 }
 254                                             } else {
 255                                                numberOfOOMEs++;
 256                                             }
 257                                             allocations = 0;
 258                                             if (factor == 0) {
 259                                                 return numberOfOOMEs;
 260                                             } else {
 261                                                 chunk = chunk / factor;
 262                                             }
 263                                         }
 264                                 }
 265                         } catch (OutOfMemoryError e) {
 266                             someMemory = null;
 267                             if (type != OOM_TYPE.ANY) {
 268                                 e.printStackTrace(pw);
 269                                 pw.close();
 270                                 if (type.accept(sw.toString())) {
 271                                     numberOfOOMEs++;
 272                                 } else {
 273                                     // Trying to catch situation when Java generates OOM different type that test trying to catch
 274                                     throw new TestBug("Test throw OOM of unexpected type." + sw.toString());
 275                                 }
 276                             } else {
 277                                 numberOfOOMEs++;
 278                             }
 279                          // all memory is eaten now even before we start, just return
 280                         }
 281                 } catch (OutOfMemoryError e) {
 282                         numberOfOOMEs++;
 283                 }
 284                 return numberOfOOMEs;
 285         }
 286 
 287         /**
 288          * Get all primitive array producers.
 289          */
 290         public static List<GarbageProducer> getPrimitiveArrayProducers() {
 291                 return getGarbageProducers().getPrimitiveArrayProducers();
 292         }
 293 
 294         /**
 295          * Get all array producers.
 296          */
 297         public static List<GarbageProducer> getArrayProducers() {
 298                 return getGarbageProducers().getArrayProducers();
 299         }
 300 
 301         /**
 302          * Determine size of each object in array which will occupy given
 303          * memory with distribution determined by given memory strategy.
 304          */
 305         public static long getArraySize(long memory, MemoryStrategy memoryStrategy) {
 306                 return memoryStrategy.getSize(memory - Memory.getArrayExtraSize(), Memory.getReferenceSize());
 307         }
 308 
 309         /**
 310          * Determine object count in array which will occupy given
 311          * memory with distribution determined by given memory strategy.
 312          */
 313         public static int getArrayCount(long memory, MemoryStrategy memoryStrategy) {
 314                 return memoryStrategy.getCount(memory - Memory.getArrayExtraSize(), Memory.getReferenceSize());
 315         }
 316 
 317         /**
 318          * Get garbage producer by identifier.
 319          *
 320          * @param id identifier
 321          * @return garbage producer for this identifier
 322          */
 323         public static GarbageProducer getGarbageProducer(String id) {
 324                 if (id == null || id.equals("byteArr"))
 325                         return new ByteArrayProducer();
 326                 else if (id.equals("booleanArr"))
 327                         return new BooleanArrayProducer();
 328                 else if (id.equals("shortArr"))
 329                         return new ShortArrayProducer();
 330                 else if (id.equals("charArr"))
 331                         return new CharArrayProducer();
 332                 else if (id.equals("intArr"))
 333                         return new IntArrayProducer();
 334                 else if (id.equals("longArr"))
 335                         return new LongArrayProducer();
 336                 else if (id.equals("floatArr"))
 337                         return new FloatArrayProducer();
 338                 else if (id.equals("doubleArr"))
 339                         return new DoubleArrayProducer();
 340                 else if (id.equals("objectArr"))
 341                         return new ObjectArrayProducer();
 342                 else if (id.equals("randomString"))
 343                         return new RandomStringProducer();
 344                 else if (id.equals("simpleString"))
 345                         return new SimpleStringProducer();
 346                 else if (id.startsWith("interned("))
 347                         return new InternedStringProducer(getGarbageProducer(getInBrackets(id)));
 348                 else if (id.startsWith("linearList("))
 349                         return new LinearListProducer(MemoryStrategy.fromString(getInBrackets(id)));
 350                 else if (id.startsWith("circularList("))
 351                         return new CircularListProducer(MemoryStrategy.fromString(getInBrackets(id)));
 352                 else if (id.startsWith("nonbranchyTree("))
 353                         return new NonbranchyTreeProducer(MemoryStrategy.fromString(getInBrackets(id)));
 354                 else if (id.equals("class"))
 355                         return new GeneratedClassProducer();
 356                 else if (id.startsWith("hashed("))
 357                         return new HashedGarbageProducer(getGarbageProducer(getInBrackets(id)));
 358                 else if (id.startsWith("random("))
 359                         return new RandomProducer(getGarbageProducerList(getInBrackets(id)));
 360                 else if (id.startsWith("twofields("))
 361                         return new TwoFieldsObjectProducer(getGarbageProducer(getInBrackets(id)));
 362                 else if (id.startsWith("arrayof("))
 363                         return new ArrayOfProducer(getGarbageProducer(getInBrackets(id)));
 364                 else if (id.startsWith("trace("))
 365                         return new TraceProducer(getGarbageProducer(getInBrackets(id)));
 366                 else
 367                         throw new TestBug("Invalid garbage producer identifier: " + id);
 368         }
 369 
 370         private static String getInBrackets(String s) {
 371                 int n1 = s.indexOf('(');
 372                 if (n1 == -1)
 373                         throw new TestBug("Opening bracket not found: " + s);
 374                 int n2 = s.lastIndexOf(')');
 375                 if (n2 == -1)
 376                         throw new TestBug("Closing bracket not found: " + s);
 377                 return s.substring(n1 + 1, n2);
 378         }
 379 
 380         private static List<GarbageProducer> getGarbageProducerList(String s) {
 381                 if (s.equals("primitiveArrays"))
 382                         return getPrimitiveArrayProducers();
 383                 else if (s.equals("arrays"))
 384                         return getArrayProducers();
 385                 else {
 386                         String[] ids = s.split(",");
 387                         List<GarbageProducer> garbageProducers = new ArrayList<GarbageProducer>(ids.length);
 388                         for (int i = 0; i < ids.length; ++i)
 389                                 garbageProducers.add(getGarbageProducer(ids[i]));
 390                         return garbageProducers;
 391                         //throw new TestBug("Invalid id for list of garbage producers: " + id);
 392                 }
 393         }
 394 
 395         public static GarbageProducers getGarbageProducers() {
 396                 if (garbageProducers == null)
 397                         garbageProducers = new GarbageProducers();
 398                 return garbageProducers;
 399         }
 400 }