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