blob: dc5bc5e8b8890f47f7da511e0d80c71c78e924f3 [file] [log] [blame]
/*
* Copyright (c) 2007, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package nsk.share.gc.gp;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.invoke.*;
import java.util.*;
import jdk.test.whitebox.WhiteBox;
import nsk.share.gc.gp.array.*;
import nsk.share.gc.gp.string.*;
import nsk.share.gc.gp.list.*;
import nsk.share.gc.gp.tree.*;
import nsk.share.gc.gp.misc.*;
import nsk.share.gc.gp.classload.*;
import nsk.share.gc.Memory;
import nsk.share.TestBug;
import nsk.share.test.*;
/**
* Utility methods for garbage producers.
*/
public final class GarbageUtils {
private static final int ALLOCATION_LIMIT = 50000000; //50 Mb
private static GarbageProducers garbageProducers;
private static List<GarbageProducer> primitiveArrayProducers;
private static List<GarbageProducer> arrayProducers;
private static final GarbageProducer byteArrayProducer = new ByteArrayProducer();
public static enum OOM_TYPE {
ANY (),
HEAP("Java heap space"),
METASPACE("Metaspace", "Compressed class space");
private final String[] expectedStrings;
OOM_TYPE(String... expectedStrings) {
this.expectedStrings = expectedStrings;
}
/**
* Returns true if the given error message matches
* one of expected strings.
*/
public boolean accept(String errorMessage) {
if (expectedStrings == null || expectedStrings.length == 0 || errorMessage == null) {
return true;
}
for (String s: expectedStrings) {
if (errorMessage.indexOf(s) != -1) {
return true;
}
}
return false;
}
};
// Force loading of OOM_TYPE and calling of enum constructors when loading GarbageUtils class.
public static final Object[] thisIsGarbageArray_theOnlyPurposeForCreatingItAndDeclaringItPublicIsToInitializeIntancesOfOOMEnumberation = new Object[] { OOM_TYPE.ANY, OOM_TYPE.HEAP, OOM_TYPE.METASPACE };
// Force early loading of classes that might otherwise unexpectedly fail
// class loading during testing due to high memory pressure.
public static final StringWriter preloadStringWriter = new StringWriter(1);
public static final PrintWriter preloadPrintWriter = new PrintWriter(preloadStringWriter);
public static final Throwable preloadThrowable = new Throwable("preload");
private GarbageUtils() {
}
/**
* engages GC by allocating memory chunks and triggering youngGC.
* Allocations are done for a total of YOUNG_GC_ITERATIONS times.
* Each iteration, we allocate a memory chunk and trigger youngGC.
* Finally fullGC is run once.
* This way the objects get to travel to various GC regions.
* @param testMemory - memory size to be operated on
*/
public static void engageGC(long testMemory) {
final int YOUNG_GC_ITERATIONS = 100;
final long memChunk = testMemory / YOUNG_GC_ITERATIONS;
int iteration = 0;
Object referenceArray[] = new Object[YOUNG_GC_ITERATIONS];
while (iteration < YOUNG_GC_ITERATIONS) {
referenceArray[iteration++] = byteArrayProducer.create(memChunk);
WhiteBox.getWhiteBox().youngGC();
}
WhiteBox.getWhiteBox().fullGC();
}
/**
* Eat memory using execution controller that waits for 2 minutes.
* @return number of OOME occured
*/
public static int eatMemory() {
return eatMemory(2 * 60 * 1000);
}
/**
* Eat memory using execution controller that waits for timeout.
* @return number of OOME occured
*/
public static int eatMemory(final long timeout) {
return eatMemory(new ExecutionController() {
final long initialTime = System.currentTimeMillis();
@Override
public void start(long stdIterations) {}
@Override
public boolean iteration() {return false;}
@Override
public boolean continueExecution() {
return System.currentTimeMillis() - initialTime < timeout;
}
@Override
public long getIteration() {return 0;}
@Override
public void finish() {}
});
}
/**
* Eat memory using given execution controller and garbage producer.
*
* @param stresser execution controller
* @param gp garbage producer
* @return number of OOME occured
*/
public static int eatMemory(ExecutionController stresser) {
return eatMemory(stresser, byteArrayProducer, 50, 100, 2, OOM_TYPE.ANY);
}
/**
* Eat memory using given execution controller and garbage producer.
*
* @param stresser execution controller
* @param gp garbage producer
* @return number of OOME occured
*/
public static int eatMemory(ExecutionController stresser, GarbageProducer gp) {
return eatMemory(stresser, gp, 50, 100, 2, OOM_TYPE.ANY);
}
/**
* Eat memory using given garbage producer and given factor.
*
* @param gp garbage producer
* @param factor factor to divide the array size by
* @return number of OOME occured
*/
public static int eatMemory(ExecutionController stresser, GarbageProducer gp, long factor) {
return eatMemory(stresser, gp, 50, 100, factor, OOM_TYPE.ANY);
}
/**
* Eat memory using default(byte[]) garbage producer.
*
* Note that this method can throw Failure if any exception
* is thrown while eating memory. To avoid OOM while allocating
* exception we preallocate it before the lunch starts. It means
* that exception stack trace does not correspond to the place
* where exception is thrown, but points at start of the method.
*
* @param stresser stresser
* @param initialFactor determines which portion of initial memory initial chunk will be
* @param minMemoryChunk determines when to stop
* @param factor factor to divide the array size by
* @return number of OOME occured
*/
public static int eatMemory(ExecutionController stresser,long initialFactor, long minMemoryChunk, long factor) {
return eatMemory(stresser, byteArrayProducer, initialFactor, minMemoryChunk, factor, OOM_TYPE.ANY);
}
/**
* Eat memory using given garbage producer.
*
* Note that this method can throw Failure if any exception
* is thrown while eating memory. To avoid OOM while allocating
* exception we preallocate it before the lunch starts. It means
* that exception stack trace does not correspond to the place
* where exception is thrown, but points at start of the method.
*
* @param stresser stresser to use
* @param gp garbage producer
* @param initialFactor determines which portion of initial memory initial chunk will be
* @param minMemoryChunk determines when to stop
* @param factor factor to divide the array size by. A value of 0 means that method returns after first OOME
* @param type of OutOfMemory Exception: Java heap space or Metadata space
* @return number of OOME occured
*/
public static int eatMemory(ExecutionController stresser, GarbageProducer gp, long initialFactor, long minMemoryChunk, long factor) {
return eatMemory(stresser, gp, initialFactor, minMemoryChunk, factor, OOM_TYPE.ANY);
}
static int numberOfOOMEs = 0;
/**
* Minimal wrapper of the main implementation. Catches any OOM
* that might be thrown when rematerializing Objects when deoptimizing.
*
* It is Important that the impl is not inlined.
*/
private static MethodType mt = MethodType.methodType(
int.class,
ExecutionController.class,
GarbageProducer.class,
long.class,
long.class,
long.class,
OOM_TYPE.class);
public static int eatMemory(ExecutionController stresser, GarbageProducer gp, long initialFactor, long minMemoryChunk, long factor, OOM_TYPE type) {
try {
// Using a methodhandle invoke of eatMemoryImpl to prevent inlining of it
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle eat = lookup.findStatic(GarbageUtils.class, "eatMemoryImpl", mt);
return (int) eat.invoke(stresser, gp, initialFactor, minMemoryChunk, factor, type);
} catch (OutOfMemoryError e) {
return numberOfOOMEs++;
} catch (Throwable t) {
throw new RuntimeException(t);
}
}
/**
* Eat memory using given garbage producer.
*
* Note that this method can throw Failure if any exception
* is thrown while eating memory. To avoid OOM while allocating
* exception we preallocate it before the lunch starts. It means
* that exception stack trace does not correspond to the place
* where exception is thrown, but points at start of the method.
*
* @param stresser stresser to use
* @param gp garbage producer
* @param initialFactor determines which portion of initial memory initial chunk will be
* @param minMemoryChunk determines when to stop
* @param factor factor to divide the array size by. A value of 0 means that method returns after first OOME
* @param type of OutOfMemory Exception: Java heap space or Metadata space
* @return number of OOME occured
*/
public static int eatMemoryImpl(ExecutionController stresser, GarbageProducer gp, long initialFactor, long minMemoryChunk, long factor, OOM_TYPE type) {
numberOfOOMEs = 0;
try {
byte[] someMemory = new byte[200000]; //200 Kb
try {
Runtime runtime = Runtime.getRuntime();
long maxMemory = runtime.maxMemory();
long maxMemoryChunk = maxMemory / initialFactor;
long chunk = maxMemoryChunk;
chunk = chunk > ALLOCATION_LIMIT ? ALLOCATION_LIMIT : chunk;
int allocations = 0;
List<Object> storage = new ArrayList<Object>();
while (chunk > minMemoryChunk && stresser.continueExecution()) {
try {
storage.add(gp.create(chunk));
if (Thread.currentThread().isInterrupted()) {
return numberOfOOMEs;
}
// if we are able to eat chunk*factor let
// try to increase size of chunk
if (chunk * factor < maxMemoryChunk
&& factor != 0 && allocations++ == factor + 1) {
chunk = chunk * factor;
allocations = 0;
}
} catch (OutOfMemoryError e) {
someMemory = null;
if (type != OOM_TYPE.ANY) {
if (type.accept(e.toString())) {
numberOfOOMEs++;
} else {
// Trying to catch situation when Java generates OOM different type that test trying to catch
throw new TestBug("Test throw OOM of unexpected type." + e.toString());
}
} else {
numberOfOOMEs++;
}
allocations = 0;
if (factor == 0) {
return numberOfOOMEs;
} else {
chunk = chunk / factor;
}
}
}
} catch (OutOfMemoryError e) {
someMemory = null;
if (type != OOM_TYPE.ANY) {
if (type.accept(e.toString())) {
numberOfOOMEs++;
} else {
// Trying to catch situation when Java generates OOM different type that test trying to catch
throw new TestBug("Test throw OOM of unexpected type." + e.toString());
}
} else {
numberOfOOMEs++;
}
// all memory is eaten now even before we start, just return
}
} catch (OutOfMemoryError e) {
numberOfOOMEs++;
}
return numberOfOOMEs;
}
/**
* Get all primitive array producers.
*/
public static List<GarbageProducer> getPrimitiveArrayProducers() {
return getGarbageProducers().getPrimitiveArrayProducers();
}
/**
* Get all array producers.
*/
public static List<GarbageProducer> getArrayProducers() {
return getGarbageProducers().getArrayProducers();
}
/**
* Determine size of each object in array which will occupy given
* memory with distribution determined by given memory strategy.
*/
public static long getArraySize(long memory, MemoryStrategy memoryStrategy) {
return memoryStrategy.getSize(memory - Memory.getArrayExtraSize(), Memory.getReferenceSize());
}
/**
* Determine object count in array which will occupy given
* memory with distribution determined by given memory strategy.
*/
public static int getArrayCount(long memory, MemoryStrategy memoryStrategy) {
return memoryStrategy.getCount(memory - Memory.getArrayExtraSize(), Memory.getReferenceSize());
}
/**
* Get garbage producer by identifier.
*
* @param id identifier
* @return garbage producer for this identifier
*/
public static GarbageProducer getGarbageProducer(String id) {
if (id == null || id.equals("byteArr"))
return new ByteArrayProducer();
else if (id.equals("booleanArr"))
return new BooleanArrayProducer();
else if (id.equals("shortArr"))
return new ShortArrayProducer();
else if (id.equals("charArr"))
return new CharArrayProducer();
else if (id.equals("intArr"))
return new IntArrayProducer();
else if (id.equals("longArr"))
return new LongArrayProducer();
else if (id.equals("floatArr"))
return new FloatArrayProducer();
else if (id.equals("doubleArr"))
return new DoubleArrayProducer();
else if (id.equals("objectArr"))
return new ObjectArrayProducer();
else if (id.equals("randomString"))
return new RandomStringProducer();
else if (id.equals("simpleString"))
return new SimpleStringProducer();
else if (id.startsWith("interned("))
return new InternedStringProducer(getGarbageProducer(getInBrackets(id)));
else if (id.startsWith("linearList("))
return new LinearListProducer(MemoryStrategy.fromString(getInBrackets(id)));
else if (id.startsWith("circularList("))
return new CircularListProducer(MemoryStrategy.fromString(getInBrackets(id)));
else if (id.startsWith("nonbranchyTree("))
return new NonbranchyTreeProducer(MemoryStrategy.fromString(getInBrackets(id)));
else if (id.equals("class"))
return new GeneratedClassProducer();
else if (id.startsWith("hashed("))
return new HashedGarbageProducer(getGarbageProducer(getInBrackets(id)));
else if (id.startsWith("random("))
return new RandomProducer(getGarbageProducerList(getInBrackets(id)));
else if (id.startsWith("twofields("))
return new TwoFieldsObjectProducer(getGarbageProducer(getInBrackets(id)));
else if (id.startsWith("arrayof("))
return new ArrayOfProducer(getGarbageProducer(getInBrackets(id)));
else if (id.startsWith("trace("))
return new TraceProducer(getGarbageProducer(getInBrackets(id)));
else
throw new TestBug("Invalid garbage producer identifier: " + id);
}
private static String getInBrackets(String s) {
int n1 = s.indexOf('(');
if (n1 == -1)
throw new TestBug("Opening bracket not found: " + s);
int n2 = s.lastIndexOf(')');
if (n2 == -1)
throw new TestBug("Closing bracket not found: " + s);
return s.substring(n1 + 1, n2);
}
private static List<GarbageProducer> getGarbageProducerList(String s) {
if (s.equals("primitiveArrays"))
return getPrimitiveArrayProducers();
else if (s.equals("arrays"))
return getArrayProducers();
else {
String[] ids = s.split(",");
List<GarbageProducer> garbageProducers = new ArrayList<GarbageProducer>(ids.length);
for (int i = 0; i < ids.length; ++i)
garbageProducers.add(getGarbageProducer(ids[i]));
return garbageProducers;
//throw new TestBug("Invalid id for list of garbage producers: " + id);
}
}
public static GarbageProducers getGarbageProducers() {
if (garbageProducers == null)
garbageProducers = new GarbageProducers();
return garbageProducers;
}
}