package com.oracle.svm.core.genscavenge;
import static com.oracle.svm.core.genscavenge.CollectionPolicy.Options.PercentTimeInIncrementalCollection;
import static com.oracle.svm.core.genscavenge.HeapPolicy.getMaximumHeapSize;
import static com.oracle.svm.core.genscavenge.HeapPolicy.getMinimumHeapSize;
import org.graalvm.compiler.options.Option;
import org.graalvm.nativeimage.Platform;
import org.graalvm.nativeimage.Platforms;
import org.graalvm.nativeimage.hosted.Feature.FeatureAccess;
import org.graalvm.word.UnsignedWord;
import com.oracle.svm.core.log.Log;
import com.oracle.svm.core.option.HostedOptionKey;
import com.oracle.svm.core.option.RuntimeOptionKey;
import com.oracle.svm.core.util.TimeUtils;
import com.oracle.svm.core.util.UserError;
public abstract class CollectionPolicy {
public static class Options {
@Option(help = "The initial garbage collection policy, as a fully-qualified class name (might require quotes or escaping).")
public static final HostedOptionKey<String> InitialCollectionPolicy = new HostedOptionKey<>(BySpaceAndTime.class.getName());
@Option(help = "Percentage of total collection time that should be spent on young generation collections.")
public static final RuntimeOptionKey<Integer> PercentTimeInIncrementalCollection = new RuntimeOptionKey<>(50);
}
@Platforms(Platform.HOSTED_ONLY.class)
static CollectionPolicy getInitialPolicy(FeatureAccess access) {
return instantiatePolicy(access, CollectionPolicy.class, Options.InitialCollectionPolicy.getValue());
}
@Platforms(Platform.HOSTED_ONLY.class)
private static <T> T instantiatePolicy(FeatureAccess access, Class<T> policyClass, String className) {
Class<?> policy = access.findClassByName(className);
if (policy == null) {
throw UserError.abort("Policy %s does not exist. It must be a fully qualified class name.", className);
}
Object result;
try {
result = policy.getDeclaredConstructor().newInstance();
} catch (Exception ex) {
throw UserError.abort("Policy %s cannot be instantiated.", className);
}
if (!policyClass.isInstance(result)) {
throw UserError.abort("Policy %s does not extend %s.", className, policyClass.getTypeName());
}
return policyClass.cast(result);
}
public abstract boolean collectIncrementally();
public abstract boolean collectCompletely();
CollectionPolicy() {
}
public abstract void nameToLog(Log log);
public abstract String getName();
static GCAccounting getAccounting() {
return GCImpl.getGCImpl().getAccounting();
}
public static class OnlyIncrementally extends CollectionPolicy {
@Override
public boolean collectIncrementally() {
return true;
}
@Override
public boolean collectCompletely() {
return false;
}
@Override
public void nameToLog(Log log) {
log.string(getName());
}
@Override
public String getName() {
return "only incrementally";
}
}
public static class OnlyCompletely extends CollectionPolicy {
@Override
public boolean collectIncrementally() {
return false;
}
@Override
public boolean collectCompletely() {
return true;
}
@Override
public void nameToLog(Log log) {
log.string(getName());
}
@Override
public String getName() {
return "only completely";
}
}
public static class NeverCollect extends CollectionPolicy {
@Override
public boolean collectIncrementally() {
return false;
}
@Override
public boolean collectCompletely() {
return false;
}
@Override
public void nameToLog(Log log) {
log.string(getName());
}
@Override
public String getName() {
return "never collect";
}
}
public static class BySpaceAndTime extends CollectionPolicy {
@Override
public boolean collectIncrementally() {
return true;
}
@Override
public boolean collectCompletely() {
return estimateUsedHeapAtNextIncrementalCollection().aboveThan(getMaximumHeapSize()) ||
GCImpl.getChunkBytes().aboveThan(getMinimumHeapSize()) && enoughTimeSpentOnIncrementalGCs();
}
private static UnsignedWord estimateUsedHeapAtNextIncrementalCollection() {
UnsignedWord currentYoungBytes = HeapImpl.getHeapImpl().getYoungGeneration().getChunkBytes();
UnsignedWord maxYoungBytes = HeapPolicy.getMaximumYoungGenerationSize();
UnsignedWord oldBytes = getAccounting().getOldGenerationAfterChunkBytes();
return currentYoungBytes.add(maxYoungBytes).add(oldBytes);
}
private static boolean enoughTimeSpentOnIncrementalGCs() {
int incrementalWeight = PercentTimeInIncrementalCollection.getValue();
assert incrementalWeight >= 0 && incrementalWeight <= 100 : "BySpaceAndTimePercentTimeInIncrementalCollection should be in the range [0..100].";
long actualIncrementalNanos = getAccounting().getIncrementalCollectionTotalNanos();
long completeNanos = getAccounting().getCompleteCollectionTotalNanos();
long totalNanos = actualIncrementalNanos + completeNanos;
long expectedIncrementalNanos = TimeUtils.weightedNanos(incrementalWeight, totalNanos);
return TimeUtils.nanoTimeLessThan(expectedIncrementalNanos, actualIncrementalNanos);
}
@Override
public void nameToLog(Log log) {
log.string(getName()).string(": ").signed(Options.PercentTimeInIncrementalCollection.getValue()).string("% in incremental collections");
}
@Override
public String getName() {
return "by space and time";
}
}
}