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 }