From c15cf599385351a6329ef6d3b313e64d65f4f0b8 Mon Sep 17 00:00:00 2001 From: Foivos Zakkak Date: Mon, 20 Sep 2021 12:36:05 +0300 Subject: [PATCH 01/17] Change GC policy help text to reflect actual default Fixup of db1b88c7170efd8387d92ed055cfdb2ef6d070a2 --- .../src/com/oracle/svm/core/genscavenge/CollectionPolicy.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/CollectionPolicy.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/CollectionPolicy.java index 38970e76c7b8..344e48bc1d94 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/CollectionPolicy.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/CollectionPolicy.java @@ -38,7 +38,7 @@ /** The interface for a garbage collection policy. All sizes are in bytes. */ public interface CollectionPolicy { final class Options { - @Option(help = "The garbage collection policy, either Adaptive (default) or BySpaceAndTime.")// + @Option(help = "The garbage collection policy, either Adaptive or BySpaceAndTime (default).")// public static final HostedOptionKey InitialCollectionPolicy = new HostedOptionKey<>("BySpaceAndTime"); } From 007c8bcd527183bee6326caedc76b577f82da09b Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Tue, 14 Sep 2021 18:12:37 +0200 Subject: [PATCH 02/17] Align down when adjusting for footprint to avoid getting stuck with regard to the estimator. --- .../oracle/svm/core/genscavenge/AdaptiveCollectionPolicy.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AdaptiveCollectionPolicy.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AdaptiveCollectionPolicy.java index a2746b0f0616..8fd75e70c74d 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AdaptiveCollectionPolicy.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AdaptiveCollectionPolicy.java @@ -299,7 +299,7 @@ private static UnsignedWord adjustEdenForFootprint(UnsignedWord curEden, Unsigne UnsignedWord reducedSize = curEden.subtract(change); assert reducedSize.belowOrEqual(curEden); - return alignUp(reducedSize); + return alignDown(reducedSize); } private static UnsignedWord scaleDown(UnsignedWord change, UnsignedWord part, UnsignedWord total) { @@ -499,7 +499,7 @@ private static UnsignedWord adjustPromoForFootprint(UnsignedWord curPromo, Unsig UnsignedWord reducedSize = curPromo.subtract(change); assert reducedSize.belowOrEqual(curPromo); - return alignUp(reducedSize); + return alignDown(reducedSize); } private static UnsignedWord promoDecrement(UnsignedWord curPromo) { From cad946644927adb74e9d050f71bba161a93409e9 Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Tue, 14 Sep 2021 18:15:07 +0200 Subject: [PATCH 03/17] Consider heap verification part of GC cost and ignore estimator at high GC cost. --- .../genscavenge/AdaptiveCollectionPolicy.java | 19 +++++++++++++++++-- .../oracle/svm/core/genscavenge/GCImpl.java | 7 ++++--- .../oracle/svm/core/genscavenge/Timers.java | 4 ++-- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AdaptiveCollectionPolicy.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AdaptiveCollectionPolicy.java index 8fd75e70c74d..0d30ee5209ed 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AdaptiveCollectionPolicy.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AdaptiveCollectionPolicy.java @@ -99,6 +99,11 @@ final class AdaptiveCollectionPolicy extends AbstractCollectionPolicy { private static final int ADAPTIVE_SIZE_COST_ESTIMATORS_HISTORY_LENGTH = ADAPTIVE_TIME_WEIGHT; /** Threshold for triggering a complete collection after repeated minor collections. */ private static final int CONSECUTIVE_MINOR_TO_MAJOR_COLLECTION_PAUSE_TIME_RATIO = 2; + /** + * When the total GC cost is above this value, the estimator is ignored and spaces are expanded + * to avoid starving the mutator. + */ + private static final double ADAPTIVE_SIZE_COST_ESTIMATOR_GC_COST_LIMIT = 0.5; /* Constants derived from other constants. */ private static final double THROUGHPUT_GOAL = 1.0 - 1.0 / (1.0 + GC_TIME_RATIO); @@ -232,7 +237,7 @@ private void computeSurvivorSpaceSizeAndThreshold(boolean isSurvivorOverflow, Un private void computeEdenSpaceSize() { boolean expansionReducesCost = true; // general assumption - boolean useEstimator = ADAPTIVE_SIZE_USE_COST_ESTIMATORS && youngGenChangeForMinorThroughput > ADAPTIVE_SIZE_POLICY_INITIALIZING_STEPS; + boolean useEstimator = shouldUseEstimator(youngGenChangeForMinorThroughput); if (useEstimator) { expansionReducesCost = minorCostEstimator.getSlope(UnsignedUtils.toDouble(edenSize)) <= 0; } @@ -275,6 +280,10 @@ private void computeEdenSpaceSize() { edenSize = desiredEdenSize; } + private boolean shouldUseEstimator(long genChangeForThroughput) { + return ADAPTIVE_SIZE_USE_COST_ESTIMATORS && genChangeForThroughput > ADAPTIVE_SIZE_POLICY_INITIALIZING_STEPS && gcCost() <= ADAPTIVE_SIZE_COST_ESTIMATOR_GC_COST_LIMIT; + } + private static boolean expansionSignificantlyReducesCost(ReciprocalLeastSquareFit estimator, UnsignedWord size, UnsignedWord delta) { double x0 = UnsignedUtils.toDouble(size); double x0Throughput = 1 - estimator.estimate(x0); @@ -346,6 +355,12 @@ private double majorGcCost() { } private double gcCost() { + /* + * Because we're dealing with averages, gcCost() can be larger than 1.0 if just the sum of + * the minor cost and the major cost is used. Worse than that is the fact that the minor + * cost and the major cost each tend toward 1.0 in the extreme of high GC costs. Limit the + * value of gcCost() to 1.0 so that the mutator cost stays non-negative. + */ double cost = Math.min(1, minorGcCost() + majorGcCost()); assert cost >= 0 : "Both minor and major costs are non-negative"; return cost; @@ -447,7 +462,7 @@ private void computeOldGenSpaceSize(UnsignedWord oldLive) { // compute_old_gen_f promoLimit = alignDown(UnsignedUtils.max(promoSize, promoLimit)); boolean expansionReducesCost = true; // general assumption - boolean useEstimator = ADAPTIVE_SIZE_USE_COST_ESTIMATORS && oldGenChangeForMajorThroughput > ADAPTIVE_SIZE_POLICY_INITIALIZING_STEPS; + boolean useEstimator = shouldUseEstimator(oldGenChangeForMajorThroughput); if (useEstimator) { expansionReducesCost = majorCostEstimator.getSlope(UnsignedUtils.toDouble(promoSize)) <= 0; } diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java index 8d8c7ff1fe6c..0344492a25f2 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java @@ -205,9 +205,7 @@ private boolean collectOperation(GCCause cause, UnsignedWord requestingEpoch, bo private boolean collectImpl(GCCause cause, boolean forceFullGC) { boolean outOfMemory; - precondition(); - verifyBeforeGC(); NoAllocationVerifier nav = noAllocationVerifier.open(); try { @@ -225,7 +223,6 @@ private boolean collectImpl(GCCause cause, boolean forceFullGC) { nav.close(); } - verifyAfterGC(); postcondition(); return outOfMemory; } @@ -260,7 +257,11 @@ private boolean doCollectOnce(GCCause cause, boolean complete, boolean followsIn Timer collectionTimer = timers.collection.open(); try { + if (!followsIncremental) { // we would have verified the heap after the incremental GC + verifyBeforeGC(); + } scavenge(!complete, followsIncremental); + verifyAfterGC(); } finally { collectionTimer.close(); } diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/Timers.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/Timers.java index dc6c29ae14a2..ee7b62f8d607 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/Timers.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/Timers.java @@ -140,8 +140,8 @@ void logAfterCollection(Log log) { if (log.isEnabled()) { log.newline(); log.string(" [GC nanoseconds:"); - logOneTimer(log, " ", verifyBefore); logOneTimer(log, " ", collection); + logOneTimer(log, " ", verifyBefore); logOneTimer(log, " ", rootScan); logOneTimer(log, " ", cheneyScanFromRoots); logOneTimer(log, " ", cheneyScanFromDirtyRoots); @@ -156,7 +156,7 @@ void logAfterCollection(Log log) { logOneTimer(log, " ", cleanCodeCache); logOneTimer(log, " ", referenceObjects); logOneTimer(log, " ", releaseSpaces); - logOneTimer(log, " ", verifyAfter); + logOneTimer(log, " ", verifyAfter); logGCLoad(log, " ", "GCLoad", collection, mutator); log.string("]"); } From d88fe298db509161698d1603ce7e0122e522b719 Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Thu, 16 Sep 2021 19:28:46 +0200 Subject: [PATCH 04/17] Make policies use -R:CollectYoungGenerationSeparately directly. --- .../core/genscavenge/AbstractCollectionPolicy.java | 3 +++ .../core/genscavenge/AdaptiveCollectionPolicy.java | 12 +++++++----- .../core/genscavenge/BasicCollectionPolicies.java | 9 ++++++++- .../svm/core/genscavenge/CollectionPolicy.java | 5 +++++ .../src/com/oracle/svm/core/genscavenge/GCImpl.java | 11 +++++------ .../oracle/svm/core/genscavenge/HeapParameters.java | 2 +- .../core/genscavenge/ProportionateSpacesPolicy.java | 7 +++++++ 7 files changed, 36 insertions(+), 13 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AbstractCollectionPolicy.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AbstractCollectionPolicy.java index eca546000dee..a867f973a034 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AbstractCollectionPolicy.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AbstractCollectionPolicy.java @@ -27,6 +27,8 @@ import java.util.concurrent.locks.ReentrantLock; import org.graalvm.compiler.api.replacements.Fold; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordFactory; @@ -44,6 +46,7 @@ abstract class AbstractCollectionPolicy implements CollectionPolicy { protected static final int MAX_TENURING_THRESHOLD = 15; + @Platforms(Platform.HOSTED_ONLY.class) static int getMaxSurvivorSpaces(Integer userValue) { assert userValue == null || userValue >= 0; return (userValue != null) ? userValue : AbstractCollectionPolicy.MAX_TENURING_THRESHOLD; diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AdaptiveCollectionPolicy.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AdaptiveCollectionPolicy.java index 0d30ee5209ed..a853bd696572 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AdaptiveCollectionPolicy.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AdaptiveCollectionPolicy.java @@ -24,6 +24,8 @@ */ package com.oracle.svm.core.genscavenge; +import static com.oracle.svm.core.genscavenge.CollectionPolicy.shouldCollectYoungGenSeparately; + import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordFactory; @@ -147,15 +149,15 @@ public String getName() { public boolean shouldCollectCompletely(boolean followingIncrementalCollection) { // should_{attempt_scavenge,full_GC} guaranteeSizeParametersInitialized(); - if (!followingIncrementalCollection) { + if (!followingIncrementalCollection && shouldCollectYoungGenSeparately(true)) { /* - * Always do an incremental collection first because we expect most of the objects in - * the young generation to be garbage, and we can reuse their leftover chunks for - * copying the live objects in the old generation with fewer allocations. + * Default to always doing an incremental collection first because we expect most of the + * objects in the young generation to be garbage, and we can reuse their leftover chunks + * for copying the live objects in the old generation with fewer allocations. */ return false; } - if (oldSizeExceededInPreviousCollection) { + if (followingIncrementalCollection && oldSizeExceededInPreviousCollection) { /* * In the preceding incremental collection, we promoted objects to the old generation * beyond its current capacity to avoid a promotion failure, but due to the chunked diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/BasicCollectionPolicies.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/BasicCollectionPolicies.java index 5145441da672..1a3569286cd5 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/BasicCollectionPolicies.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/BasicCollectionPolicies.java @@ -26,8 +26,11 @@ import static com.oracle.svm.core.genscavenge.BasicCollectionPolicies.Options.AllocationBeforePhysicalMemorySize; import static com.oracle.svm.core.genscavenge.BasicCollectionPolicies.Options.PercentTimeInIncrementalCollection; +import static com.oracle.svm.core.genscavenge.CollectionPolicy.shouldCollectYoungGenSeparately; import org.graalvm.compiler.options.Option; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordFactory; @@ -50,6 +53,7 @@ public static class Options { public static final HostedOptionKey AllocationBeforePhysicalMemorySize = new HostedOptionKey<>(1L * 1024L * 1024L); } + @Platforms(Platform.HOSTED_ONLY.class) static int getMaxSurvivorSpaces(Integer userValue) { assert userValue == null || userValue >= 0; return 0; // override option (if set): survivor spaces not supported @@ -191,6 +195,9 @@ public static final class OnlyCompletely extends BasicPolicy { @Override public boolean shouldCollectCompletely(boolean followingIncrementalCollection) { + if (!followingIncrementalCollection && shouldCollectYoungGenSeparately(false)) { + return false; + } return true; } @@ -226,7 +233,7 @@ public static final class BySpaceAndTime extends BasicPolicy { @Override public boolean shouldCollectCompletely(boolean followingIncrementalCollection) { - if (followingIncrementalCollection && !HeapParameters.Options.CollectYoungGenerationSeparately.getValue()) { + if (!followingIncrementalCollection && shouldCollectYoungGenSeparately(false)) { return false; } return estimateUsedHeapAtNextIncrementalCollection().aboveThan(getMaximumHeapSize()) || diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/CollectionPolicy.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/CollectionPolicy.java index 344e48bc1d94..7d9cc45394b1 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/CollectionPolicy.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/CollectionPolicy.java @@ -86,6 +86,11 @@ static int getMaxSurvivorSpaces(Integer userValue) { return BasicCollectionPolicies.getMaxSurvivorSpaces(userValue); } + static boolean shouldCollectYoungGenSeparately(boolean defaultValue) { + Boolean optionValue = HeapParameters.Options.CollectYoungGenerationSeparately.getValue(); + return (optionValue != null) ? optionValue : defaultValue; + } + String getName(); /** diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java index 0344492a25f2..0b8cdfc7e518 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java @@ -209,12 +209,12 @@ private boolean collectImpl(GCCause cause, boolean forceFullGC) { NoAllocationVerifier nav = noAllocationVerifier.open(); try { - outOfMemory = doCollectImpl(cause, forceFullGC); + outOfMemory = doCollectImpl(cause, forceFullGC, false); if (outOfMemory) { // Avoid running out of memory with a full GC that reclaims softly reachable objects ReferenceObjectProcessing.setSoftReferencesAreWeak(true); try { - outOfMemory = doCollectImpl(cause, true); + outOfMemory = doCollectImpl(cause, true, true); } finally { ReferenceObjectProcessing.setSoftReferencesAreWeak(false); } @@ -227,16 +227,15 @@ private boolean collectImpl(GCCause cause, boolean forceFullGC) { return outOfMemory; } - private boolean doCollectImpl(GCCause cause, boolean forceFullGC) { + private boolean doCollectImpl(GCCause cause, boolean forceFullGC, boolean forceNoIncremental) { CommittedMemoryProvider.get().beforeGarbageCollection(); - boolean incremental = HeapParameters.Options.CollectYoungGenerationSeparately.getValue() || - (!forceFullGC && !policy.shouldCollectCompletely(false)); + boolean incremental = !forceNoIncremental && !policy.shouldCollectCompletely(false); boolean outOfMemory = false; if (incremental) { outOfMemory = doCollectOnce(cause, false, false); } - if (!incremental || outOfMemory || forceFullGC || policy.shouldCollectCompletely(true)) { + if (!incremental || outOfMemory || forceFullGC || policy.shouldCollectCompletely(incremental)) { if (incremental) { // uncommit unaligned chunks CommittedMemoryProvider.get().afterGarbageCollection(); } diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapParameters.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapParameters.java index 145cf91a88eb..4c6bfcce6645 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapParameters.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapParameters.java @@ -99,7 +99,7 @@ public Integer getValue(OptionValues values) { }; @Option(help = "Determines if a full GC collects the young generation separately or together with the old generation.") // - public static final RuntimeOptionKey CollectYoungGenerationSeparately = new RuntimeOptionKey<>(false); + public static final RuntimeOptionKey CollectYoungGenerationSeparately = new RuntimeOptionKey<>(null); private Options() { } diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ProportionateSpacesPolicy.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ProportionateSpacesPolicy.java index 76dfc1ecfe7b..2b876f134e3a 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ProportionateSpacesPolicy.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ProportionateSpacesPolicy.java @@ -24,6 +24,8 @@ */ package com.oracle.svm.core.genscavenge; +import static com.oracle.svm.core.genscavenge.CollectionPolicy.shouldCollectYoungGenSeparately; + import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordFactory; @@ -65,6 +67,11 @@ public String getName() { public boolean shouldCollectCompletely(boolean followingIncrementalCollection) { guaranteeSizeParametersInitialized(); + if (!followingIncrementalCollection && shouldCollectYoungGenSeparately(false)) { + // Note that for non-ParallelGC, HotSpot resets the default of ScavengeBeforeFullGC to + // false, see GCArguments::initialize. + return false; + } if (followingIncrementalCollection && oldSizeExceededInPreviousCollection) { /* * We promoted objects to the old generation beyond its current capacity to avoid a From 2a9ecc689eae03263f0119f627e0212bbf9cf785 Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Fri, 17 Sep 2021 22:59:31 +0200 Subject: [PATCH 05/17] Use estimator slope to less hesitantly expand spaces. --- .../genscavenge/AdaptiveCollectionPolicy.java | 65 ++++++++----------- 1 file changed, 27 insertions(+), 38 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AdaptiveCollectionPolicy.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AdaptiveCollectionPolicy.java index a853bd696572..2054bf844a23 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AdaptiveCollectionPolicy.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AdaptiveCollectionPolicy.java @@ -96,7 +96,7 @@ final class AdaptiveCollectionPolicy extends AbstractCollectionPolicy { private static final boolean ADAPTIVE_SIZE_USE_COST_ESTIMATORS = true; private static final int ADAPTIVE_SIZE_POLICY_INITIALIZING_STEPS = ADAPTIVE_SIZE_POLICY_READY_THRESHOLD; /** The minimum increase in throughput in percent for expanding a space by 1% of its size. */ - private static final double ADAPTIVE_SIZE_ESTIMATOR_MIN_SIZE_THROUGHPUT_TRADEOFF = 0.8; + private static final double ADAPTIVE_SIZE_ESTIMATOR_MIN_SIZE_COST_TRADEOFF = 0.8; /** The effective number of most recent data points used by estimator (exponential decay). */ private static final int ADAPTIVE_SIZE_COST_ESTIMATORS_HISTORY_LENGTH = ADAPTIVE_TIME_WEIGHT; /** Threshold for triggering a complete collection after repeated minor collections. */ @@ -239,9 +239,12 @@ private void computeSurvivorSpaceSizeAndThreshold(boolean isSurvivorOverflow, Un private void computeEdenSpaceSize() { boolean expansionReducesCost = true; // general assumption - boolean useEstimator = shouldUseEstimator(youngGenChangeForMinorThroughput); - if (useEstimator) { - expansionReducesCost = minorCostEstimator.getSlope(UnsignedUtils.toDouble(edenSize)) <= 0; + if (shouldUseEstimator(youngGenChangeForMinorThroughput)) { + expansionReducesCost = expansionSignificantlyReducesCost(minorCostEstimator, edenSize); + /* + * Note that if the estimator thinks expanding does not lead to significant improvement, + * shrink so to not get stuck in a supposed optimum and to keep collecting data points. + */ } UnsignedWord desiredEdenSize = edenSize; @@ -252,16 +255,9 @@ private void computeEdenSpaceSize() { assert scaleByRatio >= 0 && scaleByRatio <= 1; UnsignedWord scaledEdenHeapDelta = UnsignedUtils.fromDouble(scaleByRatio * UnsignedUtils.toDouble(edenHeapDelta)); - expansionReducesCost = !useEstimator || expansionSignificantlyReducesCost(minorCostEstimator, edenSize, scaledEdenHeapDelta); - if (expansionReducesCost) { - desiredEdenSize = alignUp(desiredEdenSize.add(scaledEdenHeapDelta)); - desiredEdenSize = UnsignedUtils.max(desiredEdenSize, edenSize); - youngGenChangeForMinorThroughput++; - } - /* - * If the estimator says expanding by delta does not lead to a significant improvement, - * shrink so to not get stuck in a supposed optimum and to keep collecting data points. - */ + desiredEdenSize = alignUp(desiredEdenSize.add(scaledEdenHeapDelta)); + desiredEdenSize = UnsignedUtils.max(desiredEdenSize, edenSize); + youngGenChangeForMinorThroughput++; } if (!expansionReducesCost || (USE_ADAPTIVE_SIZE_POLICY_FOOTPRINT_GOAL && youngGenPolicyIsReady && adjustedMutatorCost() >= THROUGHPUT_GOAL)) { UnsignedWord desiredSum = edenSize.add(promoSize); @@ -286,20 +282,17 @@ private boolean shouldUseEstimator(long genChangeForThroughput) { return ADAPTIVE_SIZE_USE_COST_ESTIMATORS && genChangeForThroughput > ADAPTIVE_SIZE_POLICY_INITIALIZING_STEPS && gcCost() <= ADAPTIVE_SIZE_COST_ESTIMATOR_GC_COST_LIMIT; } - private static boolean expansionSignificantlyReducesCost(ReciprocalLeastSquareFit estimator, UnsignedWord size, UnsignedWord delta) { + private static boolean expansionSignificantlyReducesCost(ReciprocalLeastSquareFit estimator, UnsignedWord size) { double x0 = UnsignedUtils.toDouble(size); - double x0Throughput = 1 - estimator.estimate(x0); - if (x0 == 0 || x0Throughput == 0) { // division by zero below - return false; - } - double x1 = x0 + UnsignedUtils.toDouble(delta); - double x1Throughput = 1 - estimator.estimate(x1); - if (x0 >= x1 || x0Throughput >= x1Throughput) { + double deltax = (1.01 - 1) * x0; + if (deltax == 0) { // division by zero below return false; } - double min = (x1 / x0 - 1) * ADAPTIVE_SIZE_ESTIMATOR_MIN_SIZE_THROUGHPUT_TRADEOFF; - double estimated = x1Throughput / x0Throughput - 1; - return (estimated >= min); + double y0 = estimator.estimate(x0); + double y1 = y0 * (1 - 0.01 * ADAPTIVE_SIZE_ESTIMATOR_MIN_SIZE_COST_TRADEOFF); + double minSlope = (y1 - y0) / deltax; + double estimatedSlope = estimator.getSlope(x0); + return estimatedSlope <= minSlope; } private static UnsignedWord adjustEdenForFootprint(UnsignedWord curEden, UnsignedWord desiredSum) { @@ -464,9 +457,12 @@ private void computeOldGenSpaceSize(UnsignedWord oldLive) { // compute_old_gen_f promoLimit = alignDown(UnsignedUtils.max(promoSize, promoLimit)); boolean expansionReducesCost = true; // general assumption - boolean useEstimator = shouldUseEstimator(oldGenChangeForMajorThroughput); - if (useEstimator) { - expansionReducesCost = majorCostEstimator.getSlope(UnsignedUtils.toDouble(promoSize)) <= 0; + if (shouldUseEstimator(oldGenChangeForMajorThroughput)) { + expansionReducesCost = expansionSignificantlyReducesCost(majorCostEstimator, promoSize); + /* + * Note that if the estimator thinks expanding does not lead to significant improvement, + * shrink so to not get stuck in a supposed optimum and to keep collecting data points. + */ } UnsignedWord desiredPromoSize = promoSize; @@ -477,16 +473,9 @@ private void computeOldGenSpaceSize(UnsignedWord oldLive) { // compute_old_gen_f assert scaleByRatio >= 0 && scaleByRatio <= 1; UnsignedWord scaledPromoHeapDelta = UnsignedUtils.fromDouble(scaleByRatio * UnsignedUtils.toDouble(promoHeapDelta)); - expansionReducesCost = !useEstimator || expansionSignificantlyReducesCost(majorCostEstimator, promoSize, scaledPromoHeapDelta); - if (expansionReducesCost) { - desiredPromoSize = alignUp(promoSize.add(scaledPromoHeapDelta)); - desiredPromoSize = UnsignedUtils.max(desiredPromoSize, promoSize); - oldGenChangeForMajorThroughput++; - } - /* - * If the estimator says expanding by delta does not lead to a significant improvement, - * shrink so to not get stuck in a supposed optimum and to keep collecting data points. - */ + desiredPromoSize = alignUp(promoSize.add(scaledPromoHeapDelta)); + desiredPromoSize = UnsignedUtils.max(desiredPromoSize, promoSize); + oldGenChangeForMajorThroughput++; } if (!expansionReducesCost || (USE_ADAPTIVE_SIZE_POLICY_FOOTPRINT_GOAL && youngGenPolicyIsReady && adjustedMutatorCost() >= THROUGHPUT_GOAL)) { UnsignedWord desiredSum = edenSize.add(promoSize); From 37487125c79387ed8af20df21b394bf759b7407f Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Sat, 18 Sep 2021 00:41:58 +0200 Subject: [PATCH 06/17] Minor improvements. --- .../core/genscavenge/AdaptiveCollectionPolicy.java | 12 ++++++------ .../src/com/oracle/svm/core/genscavenge/GCImpl.java | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AdaptiveCollectionPolicy.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AdaptiveCollectionPolicy.java index 2054bf844a23..e1294459dc80 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AdaptiveCollectionPolicy.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AdaptiveCollectionPolicy.java @@ -118,7 +118,7 @@ final class AdaptiveCollectionPolicy extends AbstractCollectionPolicy { private final AdaptivePaddedAverage avgPromoted = new AdaptivePaddedAverage(ADAPTIVE_SIZE_POLICY_WEIGHT, PROMOTED_PADDING, true); private final ReciprocalLeastSquareFit minorCostEstimator = new ReciprocalLeastSquareFit(ADAPTIVE_SIZE_COST_ESTIMATORS_HISTORY_LENGTH); private long minorCount; - private long latestMinorMutatorIntervalSeconds; + private long latestMinorMutatorIntervalNanos; private boolean youngGenPolicyIsReady; private UnsignedWord youngGenSizeIncrementSupplement = WordFactory.unsigned(YOUNG_GENERATION_SIZE_SUPPLEMENT); private long youngGenChangeForMinorThroughput; @@ -132,7 +132,7 @@ final class AdaptiveCollectionPolicy extends AbstractCollectionPolicy { private final ReciprocalLeastSquareFit majorCostEstimator = new ReciprocalLeastSquareFit(ADAPTIVE_SIZE_COST_ESTIMATORS_HISTORY_LENGTH); private long majorCount; private UnsignedWord oldGenSizeIncrementSupplement = WordFactory.unsigned(TENURED_GENERATION_SIZE_SUPPLEMENT); - private long latestMajorMutatorIntervalSeconds; + private long latestMajorMutatorIntervalNanos; private boolean oldSizeExceededInPreviousCollection; private long oldGenChangeForMajorThroughput; @@ -383,9 +383,9 @@ public void onCollectionBegin(boolean completeCollection) { // {major,minor}_col Timer timer = completeCollection ? majorTimer : minorTimer; timer.close(); if (completeCollection) { - latestMajorMutatorIntervalSeconds = timer.getMeasuredNanos(); + latestMajorMutatorIntervalNanos = timer.getMeasuredNanos(); } else { - latestMinorMutatorIntervalSeconds = timer.getMeasuredNanos(); + latestMinorMutatorIntervalNanos = timer.getMeasuredNanos(); } // Capture the fraction of bytes in aligned chunks at the start to include all allocated @@ -407,13 +407,13 @@ public void onCollectionEnd(boolean completeCollection, GCCause cause) { // {maj if (completeCollection) { updateCollectionEndAverages(avgMajorGcCost, avgMajorPause, majorCostEstimator, avgMajorIntervalSeconds, - cause, latestMajorMutatorIntervalSeconds, timer.getMeasuredNanos(), promoSize); + cause, latestMajorMutatorIntervalNanos, timer.getMeasuredNanos(), promoSize); majorCount++; minorCountSinceMajorCollection = 0; } else { updateCollectionEndAverages(avgMinorGcCost, avgMinorPause, minorCostEstimator, null, - cause, latestMinorMutatorIntervalSeconds, timer.getMeasuredNanos(), edenSize); + cause, latestMinorMutatorIntervalNanos, timer.getMeasuredNanos(), edenSize); minorCount++; minorCountSinceMajorCollection++; diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java index 0b8cdfc7e518..4f3addb45a11 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java @@ -194,7 +194,6 @@ private boolean collectOperation(GCCause cause, UnsignedWord requestingEpoch, bo printGCBefore(cause.getName()); boolean outOfMemory = collectImpl(cause, forceFullGC); - HeapImpl.getHeapImpl().getAccounting().setEdenAndYoungGenBytes(WordFactory.unsigned(0), accounting.getYoungChunkBytesAfter()); printGCAfter(cause.getName()); finishCollection(); @@ -265,6 +264,7 @@ private boolean doCollectOnce(GCCause cause, boolean complete, boolean followsIn collectionTimer.close(); } + HeapImpl.getHeapImpl().getAccounting().setEdenAndYoungGenBytes(WordFactory.zero(), accounting.getYoungChunkBytesAfter()); accounting.afterCollection(completeCollection, collectionTimer); policy.onCollectionEnd(completeCollection, cause); From 7f54b293e0c654f6fa140abfe4e633c452a4c22f Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Sat, 18 Sep 2021 00:53:46 +0200 Subject: [PATCH 07/17] Exclude GC safepoint from mutator time. --- .../genscavenge/AdaptiveCollectionPolicy.java | 11 ++--- .../genscavenge/BasicCollectionPolicies.java | 2 +- .../core/genscavenge/CollectionPolicy.java | 2 +- .../oracle/svm/core/genscavenge/GCImpl.java | 42 +++++++++++-------- .../ProportionateSpacesPolicy.java | 2 +- .../oracle/svm/core/genscavenge/Timers.java | 42 +++++++++++++------ 6 files changed, 60 insertions(+), 41 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AdaptiveCollectionPolicy.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AdaptiveCollectionPolicy.java index e1294459dc80..97781b419e32 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AdaptiveCollectionPolicy.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AdaptiveCollectionPolicy.java @@ -370,18 +370,13 @@ private static UnsignedWord spaceIncrement(UnsignedWord curSize, UnsignedWord pe } private double secondsSinceMajorGc() { // time_since_major_gc - majorTimer.close(); - try { - return TimeUtils.nanosToSecondsDouble(majorTimer.getMeasuredNanos()); - } finally { - majorTimer.open(); - } + return TimeUtils.nanosToSecondsDouble(System.nanoTime() - majorTimer.getOpenedTime()); } @Override - public void onCollectionBegin(boolean completeCollection) { // {major,minor}_collection_begin + public void onCollectionBegin(boolean completeCollection, long requestingNanoTime) { // {major,minor}_collection_begin Timer timer = completeCollection ? majorTimer : minorTimer; - timer.close(); + timer.closeAt(requestingNanoTime); if (completeCollection) { latestMajorMutatorIntervalNanos = timer.getMeasuredNanos(); } else { diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/BasicCollectionPolicies.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/BasicCollectionPolicies.java index 1a3569286cd5..29b6987bd642 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/BasicCollectionPolicies.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/BasicCollectionPolicies.java @@ -170,7 +170,7 @@ public int getTenuringAge() { } @Override - public void onCollectionBegin(boolean completeCollection) { + public void onCollectionBegin(boolean completeCollection, long requestingNanoTime) { } @Override diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/CollectionPolicy.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/CollectionPolicy.java index 7d9cc45394b1..845f212ad979 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/CollectionPolicy.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/CollectionPolicy.java @@ -170,7 +170,7 @@ static boolean shouldCollectYoungGenSeparately(boolean defaultValue) { int getTenuringAge(); /** Called at the beginning of a collection, in the safepoint operation. */ - void onCollectionBegin(boolean completeCollection); + void onCollectionBegin(boolean completeCollection, long requestingNanoTime); /** Called before the end of a collection, in the safepoint operation. */ void onCollectionEnd(boolean completeCollection, GCCause cause); diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java index 4f3addb45a11..883f97f9bb87 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java @@ -168,6 +168,7 @@ boolean collectWithoutAllocating(GCCause cause, boolean forceFullGC) { data.setNativeVMOperation(collectOperation); data.setCauseId(cause.getId()); data.setRequestingEpoch(getCollectionEpoch()); + data.setRequestingNanoTime(System.nanoTime()); data.setForceFullGC(forceFullGC); enqueueCollectOperation(data); return data.getOutOfMemory(); @@ -179,11 +180,11 @@ private void enqueueCollectOperation(CollectionVMOperationData data) { } /** The body of the VMOperation to do the collection. */ - private boolean collectOperation(GCCause cause, UnsignedWord requestingEpoch, boolean forceFullGC) { + private void collectOperation(CollectionVMOperationData data) { assert VMOperation.isGCInProgress() : "Collection should be a VMOperation."; - assert getCollectionEpoch().equal(requestingEpoch); + assert getCollectionEpoch().equal(data.getRequestingEpoch()); - timers.mutator.close(); + timers.mutator.closeAt(data.getRequestingNanoTime()); startCollectionOrExit(); timers.resetAllExceptMutator(); @@ -192,28 +193,29 @@ private boolean collectOperation(GCCause cause, UnsignedWord requestingEpoch, bo /* Flush all TLAB chunks to eden. */ ThreadLocalAllocation.disableAndFlushForAllThreads(); + GCCause cause = GCCause.fromId(data.getCauseId()); printGCBefore(cause.getName()); - boolean outOfMemory = collectImpl(cause, forceFullGC); + boolean outOfMemory = collectImpl(cause, data.getRequestingNanoTime(), data.getForceFullGC()); printGCAfter(cause.getName()); finishCollection(); timers.mutator.open(); - return outOfMemory; + data.setOutOfMemory(outOfMemory); } - private boolean collectImpl(GCCause cause, boolean forceFullGC) { + private boolean collectImpl(GCCause cause, long requestingNanoTime, boolean forceFullGC) { boolean outOfMemory; precondition(); NoAllocationVerifier nav = noAllocationVerifier.open(); try { - outOfMemory = doCollectImpl(cause, forceFullGC, false); + outOfMemory = doCollectImpl(cause, requestingNanoTime, forceFullGC, false); if (outOfMemory) { // Avoid running out of memory with a full GC that reclaims softly reachable objects ReferenceObjectProcessing.setSoftReferencesAreWeak(true); try { - outOfMemory = doCollectImpl(cause, true, true); + outOfMemory = doCollectImpl(cause, requestingNanoTime, true, true); } finally { ReferenceObjectProcessing.setSoftReferencesAreWeak(false); } @@ -226,19 +228,19 @@ private boolean collectImpl(GCCause cause, boolean forceFullGC) { return outOfMemory; } - private boolean doCollectImpl(GCCause cause, boolean forceFullGC, boolean forceNoIncremental) { + private boolean doCollectImpl(GCCause cause, long requestingNanoTime, boolean forceFullGC, boolean forceNoIncremental) { CommittedMemoryProvider.get().beforeGarbageCollection(); boolean incremental = !forceNoIncremental && !policy.shouldCollectCompletely(false); boolean outOfMemory = false; if (incremental) { - outOfMemory = doCollectOnce(cause, false, false); + outOfMemory = doCollectOnce(cause, requestingNanoTime, false, false); } if (!incremental || outOfMemory || forceFullGC || policy.shouldCollectCompletely(incremental)) { if (incremental) { // uncommit unaligned chunks CommittedMemoryProvider.get().afterGarbageCollection(); } - outOfMemory = doCollectOnce(cause, true, incremental); + outOfMemory = doCollectOnce(cause, requestingNanoTime, true, incremental); } HeapImpl.getChunkProvider().freeExcessAlignedChunks(); @@ -246,12 +248,12 @@ private boolean doCollectImpl(GCCause cause, boolean forceFullGC, boolean forceN return outOfMemory; } - private boolean doCollectOnce(GCCause cause, boolean complete, boolean followsIncremental) { + private boolean doCollectOnce(GCCause cause, long requestingNanoTime, boolean complete, boolean followsIncremental) { assert !followsIncremental || complete : "An incremental collection cannot be followed by another incremental collection"; completeCollection = complete; accounting.beforeCollection(); - policy.onCollectionBegin(completeCollection); + policy.onCollectionBegin(completeCollection, requestingNanoTime); Timer collectionTimer = timers.collection.open(); try { @@ -368,7 +370,7 @@ private void printGCAfter(String cause) { UnsignedWord sizeAfter = getChunkBytes(); printGCLog.string("["); if (HeapOptions.PrintGCTimeStamps.getValue()) { - long finishNanos = timers.collection.getFinish(); + long finishNanos = timers.collection.getClosedTime(); printGCLog.unsigned(TimeUtils.roundNanosToMillis(Timer.getTimeSinceFirstAllocation(finishNanos))).string(" msec: "); } printGCLog.string(completeCollection ? "Full GC" : "Incremental GC"); @@ -381,7 +383,7 @@ private void printGCAfter(String cause) { } if (SubstrateGCOptions.VerboseGC.getValue()) { verboseGCLog.string(" ["); - long finishNanos = timers.collection.getFinish(); + long finishNanos = timers.collection.getClosedTime(); if (HeapOptions.PrintGCTimeStamps.getValue()) { verboseGCLog.unsigned(TimeUtils.roundNanosToMillis(Timer.getTimeSinceFirstAllocation(finishNanos))).string(" msec: "); } else { @@ -1175,9 +1177,7 @@ protected void operate(NativeVMOperationData data) { */ ImplicitExceptions.activateImplicitExceptionsAreFatal(); try { - CollectionVMOperationData d = (CollectionVMOperationData) data; - boolean outOfMemory = HeapImpl.getHeapImpl().getGCImpl().collectOperation(GCCause.fromId(d.getCauseId()), d.getRequestingEpoch(), d.getForceFullGC()); - d.setOutOfMemory(outOfMemory); + HeapImpl.getHeapImpl().getGCImpl().collectOperation((CollectionVMOperationData) data); } catch (Throwable t) { throw VMError.shouldNotReachHere(t); } finally { @@ -1206,6 +1206,12 @@ private interface CollectionVMOperationData extends NativeVMOperationData { @RawField void setRequestingEpoch(UnsignedWord value); + @RawField + long getRequestingNanoTime(); + + @RawField + void setRequestingNanoTime(long value); + @RawField boolean getForceFullGC(); diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ProportionateSpacesPolicy.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ProportionateSpacesPolicy.java index 2b876f134e3a..53e0354e0773 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ProportionateSpacesPolicy.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/ProportionateSpacesPolicy.java @@ -85,7 +85,7 @@ public boolean shouldCollectCompletely(boolean followingIncrementalCollection) { } @Override - public void onCollectionBegin(boolean completeCollection) { + public void onCollectionBegin(boolean completeCollection, long requestingNanoTime) { } @Override diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/Timers.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/Timers.java index ee7b62f8d607..bd5b1cd255fd 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/Timers.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/Timers.java @@ -32,7 +32,9 @@ */ final class Timer implements AutoCloseable { private final String name; + private boolean wasOpened; private long openNanos; + private boolean wasClosed; private long closeNanos; private long collectedNanos; @@ -45,29 +47,47 @@ public String getName() { } public Timer open() { - openNanos = System.nanoTime(); + return openAt(System.nanoTime()); + } + + Timer openAt(long nanoTime) { + openNanos = nanoTime; + wasOpened = true; closeNanos = 0L; + wasClosed = false; return this; } @Override public void close() { - /* If a timer was not opened, pretend it was opened at the start of the VM. */ - if (openNanos == 0L) { - openNanos = HeapImpl.getChunkProvider().getFirstAllocationTime(); - } - closeNanos = System.nanoTime(); - collectedNanos += closeNanos - openNanos; + closeAt(System.nanoTime()); + } + + void closeAt(long nanoTime) { + closeNanos = nanoTime; + wasClosed = true; + collectedNanos += closeNanos - getOpenedTime(); } public void reset() { openNanos = 0L; + wasOpened = false; closeNanos = 0L; + wasClosed = false; collectedNanos = 0L; } - public long getFinish() { - assert closeNanos > 0L : "Should have closed timer"; + public long getOpenedTime() { + if (!wasOpened) { + /* If a timer was not opened, pretend it was opened at the start of the VM. */ + assert openNanos == 0; + openNanos = HeapImpl.getChunkProvider().getFirstAllocationTime(); + } + return openNanos; + } + + public long getClosedTime() { + assert wasClosed : "Should have closed timer"; return closeNanos; } @@ -78,9 +98,7 @@ long getMeasuredNanos() { /** Get the nanoseconds collected by the most recent open/close pair. */ long getLastIntervalNanos() { - assert openNanos > 0L : "Should have opened timer"; - assert closeNanos > 0L : "Should have closed timer"; - return closeNanos - openNanos; + return getClosedTime() - getOpenedTime(); } static long getTimeSinceFirstAllocation(long nanos) { From 837b01dfbb389c712d3048b8f4d5ba8d16e10ab6 Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Sat, 18 Sep 2021 23:07:17 +0200 Subject: [PATCH 08/17] Scavenge young generation during a full collection following an incremental collection. --- .../oracle/svm/core/genscavenge/GCImpl.java | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java index 883f97f9bb87..40481fdd9fd2 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java @@ -589,18 +589,17 @@ private void cheneyScanFromRoots(boolean followingIncremental) { if (followingIncremental) { /* - * We just finished an incremental collection, so we will not be able to reclaim any - * young objects and do not need to copy them (and do not want to age or tenure them - * in the process). We still need to scan them for roots into the old generation. + * We just finished an incremental collection, so we could get away with not + * scavenging the young generation again. * - * There is potential trouble with this: if objects in the young generation are - * reachable only from garbage objects in the old generation, the young objects are - * not reclaimed during this collection. If there is a cycle in which the young - * objects in turn keep the old objects alive, none of the objects can be reclaimed - * until the young objects are eventually tenured, or until a single complete - * collection is done before we would run out of memory. + * However, we don't do this because if objects in the young generation are + * reachable only from garbage objects in the old generation, the young objects + * cannot be reclaimed during this collection. Even worse, if there is a cycle in + * which the young objects in turn keep the old objects alive, none of the objects + * can be reclaimed until these young objects are eventually tenured, or until a + * single complete collection is done before we would run out of memory. */ - HeapImpl.getHeapImpl().getYoungGeneration().emptyFromSpacesIntoToSpaces(); + // HeapImpl.getHeapImpl().getYoungGeneration().emptyFromSpacesIntoToSpaces(); } /* From a82982f445ace98b4b016d35fc2ddc7bfd17b1e3 Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Sat, 18 Sep 2021 23:52:26 +0200 Subject: [PATCH 09/17] Start heap at a small size, regardless of physical memory size. --- .../svm/core/genscavenge/AbstractCollectionPolicy.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AbstractCollectionPolicy.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AbstractCollectionPolicy.java index a867f973a034..446ddb1ca20c 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AbstractCollectionPolicy.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AbstractCollectionPolicy.java @@ -69,7 +69,6 @@ static int getMaxSurvivorSpaces(Integer userValue) { protected static final int NEW_RATIO = 2; // HotSpot: -XX:NewRatio protected static final int LARGE_MEMORY_MAX_HEAP_PERCENT = 25; // -XX:MaxRAMPercentage protected static final int SMALL_MEMORY_MAX_HEAP_PERCENT = 50; // -XX:MinRAMPercentage - protected static final double INITIAL_HEAP_MEMORY_PERCENT = 1.5625; // -XX:InitialRAMPercentage protected final AdaptiveWeightedAverage avgYoungGenAlignedChunkFraction = new AdaptiveWeightedAverage(DEFAULT_TIME_WEIGHT); @@ -299,12 +298,7 @@ protected SizeParameters computeSizeParameters() { } minHeap = UnsignedUtils.clamp(alignUp(minHeap), minAllSpaces, maxHeap); - UnsignedWord initialHeap; - if (PhysicalMemory.isInitialized()) { - initialHeap = UnsignedUtils.fromDouble(UnsignedUtils.toDouble(PhysicalMemory.getCachedSize()) / 100 * AbstractCollectionPolicy.INITIAL_HEAP_MEMORY_PERCENT); - } else { - initialHeap = AbstractCollectionPolicy.SMALL_HEAP_SIZE; - } + UnsignedWord initialHeap = AbstractCollectionPolicy.SMALL_HEAP_SIZE; initialHeap = UnsignedUtils.clamp(alignUp(initialHeap), minHeap, maxHeap); UnsignedWord initialYoung; From b035ab009aac50ff1aa0788cb5377e880975e75a Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Sat, 18 Sep 2021 23:56:06 +0200 Subject: [PATCH 10/17] Increase minimum space size. --- .../genscavenge/AbstractCollectionPolicy.java | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AbstractCollectionPolicy.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AbstractCollectionPolicy.java index 446ddb1ca20c..f503ebb90bf2 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AbstractCollectionPolicy.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AbstractCollectionPolicy.java @@ -44,6 +44,7 @@ abstract class AbstractCollectionPolicy implements CollectionPolicy { + protected static final int MIN_SPACE_SIZE_IN_ALIGNED_CHUNKS = 8; protected static final int MAX_TENURING_THRESHOLD = 15; @Platforms(Platform.HOSTED_ONLY.class) @@ -95,23 +96,28 @@ public boolean shouldCollectOnAllocation() { } @Fold - static UnsignedWord minSpaceSize() { + static UnsignedWord getAlignment() { return HeapParameters.getAlignedHeapChunkSize(); } @Uninterruptible(reason = "Used in uninterruptible code.", mayBeInlined = true) static UnsignedWord alignUp(UnsignedWord size) { - return UnsignedUtils.roundUp(size, minSpaceSize()); + return UnsignedUtils.roundUp(size, getAlignment()); } @Uninterruptible(reason = "Used in uninterruptible code.", mayBeInlined = true) static UnsignedWord alignDown(UnsignedWord size) { - return UnsignedUtils.roundDown(size, minSpaceSize()); + return UnsignedUtils.roundDown(size, getAlignment()); } @Uninterruptible(reason = "Used in uninterruptible code.", mayBeInlined = true) static boolean isAligned(UnsignedWord size) { - return UnsignedUtils.isAMultiple(size, minSpaceSize()); + return UnsignedUtils.isAMultiple(size, getAlignment()); + } + + @Fold + static UnsignedWord minSpaceSize() { + return getAlignment().multiply(MIN_SPACE_SIZE_IN_ALIGNED_CHUNKS); } @Uninterruptible(reason = "Used in uninterruptible code.", mayBeInlined = true) @@ -319,7 +325,10 @@ protected SizeParameters computeSizeParameters() { */ initialSurvivor = minSpaceSize(alignUp(initialYoung.unsignedDivide(AbstractCollectionPolicy.INITIAL_SURVIVOR_RATIO))); } - UnsignedWord initialEden = minSpaceSize(alignUp(initialYoung.subtract(initialSurvivor.multiply(2)))); + UnsignedWord initialEden = minSpaceSize(); + if (initialYoung.aboveThan(initialSurvivor.multiply(2))) { + initialEden = minSpaceSize(alignUp(initialYoung.subtract(initialSurvivor.multiply(2)))); + } return new SizeParameters(maxHeap, maxYoung, initialHeap, initialEden, initialSurvivor, minHeap); } From 670b13e0d53a9b9a90b5cb6a44854e356415b553 Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Sun, 19 Sep 2021 22:51:54 +0200 Subject: [PATCH 11/17] Decide whether to use estimator based on young/old cost and not total cost. --- .../core/genscavenge/AdaptiveCollectionPolicy.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AdaptiveCollectionPolicy.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AdaptiveCollectionPolicy.java index 97781b419e32..c0d8411e54c8 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AdaptiveCollectionPolicy.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AdaptiveCollectionPolicy.java @@ -102,8 +102,8 @@ final class AdaptiveCollectionPolicy extends AbstractCollectionPolicy { /** Threshold for triggering a complete collection after repeated minor collections. */ private static final int CONSECUTIVE_MINOR_TO_MAJOR_COLLECTION_PAUSE_TIME_RATIO = 2; /** - * When the total GC cost is above this value, the estimator is ignored and spaces are expanded - * to avoid starving the mutator. + * When the GC cost of a generation is above this value, its estimator is ignored and sizes are + * increased to avoid starving the mutator. */ private static final double ADAPTIVE_SIZE_COST_ESTIMATOR_GC_COST_LIMIT = 0.5; @@ -239,7 +239,7 @@ private void computeSurvivorSpaceSizeAndThreshold(boolean isSurvivorOverflow, Un private void computeEdenSpaceSize() { boolean expansionReducesCost = true; // general assumption - if (shouldUseEstimator(youngGenChangeForMinorThroughput)) { + if (shouldUseEstimator(youngGenChangeForMinorThroughput, minorGcCost())) { expansionReducesCost = expansionSignificantlyReducesCost(minorCostEstimator, edenSize); /* * Note that if the estimator thinks expanding does not lead to significant improvement, @@ -278,8 +278,8 @@ private void computeEdenSpaceSize() { edenSize = desiredEdenSize; } - private boolean shouldUseEstimator(long genChangeForThroughput) { - return ADAPTIVE_SIZE_USE_COST_ESTIMATORS && genChangeForThroughput > ADAPTIVE_SIZE_POLICY_INITIALIZING_STEPS && gcCost() <= ADAPTIVE_SIZE_COST_ESTIMATOR_GC_COST_LIMIT; + private static boolean shouldUseEstimator(long genChangeForThroughput, double cost) { + return ADAPTIVE_SIZE_USE_COST_ESTIMATORS && genChangeForThroughput > ADAPTIVE_SIZE_POLICY_INITIALIZING_STEPS && cost <= ADAPTIVE_SIZE_COST_ESTIMATOR_GC_COST_LIMIT; } private static boolean expansionSignificantlyReducesCost(ReciprocalLeastSquareFit estimator, UnsignedWord size) { @@ -452,7 +452,7 @@ private void computeOldGenSpaceSize(UnsignedWord oldLive) { // compute_old_gen_f promoLimit = alignDown(UnsignedUtils.max(promoSize, promoLimit)); boolean expansionReducesCost = true; // general assumption - if (shouldUseEstimator(oldGenChangeForMajorThroughput)) { + if (shouldUseEstimator(oldGenChangeForMajorThroughput, majorGcCost())) { expansionReducesCost = expansionSignificantlyReducesCost(majorCostEstimator, promoSize); /* * Note that if the estimator thinks expanding does not lead to significant improvement, From 2bc0aee96abce06129e34acbd41bc3155b0b0777 Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Wed, 22 Sep 2021 20:42:36 +0200 Subject: [PATCH 12/17] Introduce option to limit free memory reserved for allocations. --- .../com/oracle/svm/core/genscavenge/HeapChunkProvider.java | 5 +++++ .../src/com/oracle/svm/core/genscavenge/HeapParameters.java | 4 ++++ .../src/com/oracle/svm/core/SubstrateGCOptions.java | 3 +++ 3 files changed, 12 insertions(+) diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapChunkProvider.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapChunkProvider.java index 2f887e646ccb..8b00abcf8196 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapChunkProvider.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapChunkProvider.java @@ -33,6 +33,7 @@ import com.oracle.svm.core.FrameAccess; import com.oracle.svm.core.MemoryWalker; +import com.oracle.svm.core.SubstrateGCOptions; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.annotate.AlwaysInline; import com.oracle.svm.core.annotate.Uninterruptible; @@ -145,6 +146,10 @@ void consumeAlignedChunks(AlignedHeader firstChunk, boolean keepAll) { } else { UnsignedWord freeListBytes = getBytesInUnusedChunks(); UnsignedWord reserveBytes = GCImpl.getPolicy().getMaximumFreeAlignedChunksSize(); + UnsignedWord maxHeapFree = WordFactory.unsigned(SubstrateGCOptions.MaxHeapFree.getValue()); + if (maxHeapFree.aboveThan(0)) { + reserveBytes = UnsignedUtils.min(reserveBytes, maxHeapFree); + } if (freeListBytes.belowThan(reserveBytes)) { maxChunksToKeep = reserveBytes.subtract(freeListBytes).unsignedDivide(HeapParameters.getAlignedHeapChunkSize()); } else { diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapParameters.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapParameters.java index 4c6bfcce6645..b7e40097d68b 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapParameters.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapParameters.java @@ -156,6 +156,10 @@ public static void setMinimumHeapSize(UnsignedWord value) { RuntimeOptionValues.singleton().update(SubstrateGCOptions.MinHeapSize, value.rawValue()); } + public static void setMaximumHeapFree(UnsignedWord bytes) { + RuntimeOptionValues.singleton().update(SubstrateGCOptions.MaxHeapFree, bytes.rawValue()); + } + static int getMaximumYoungGenerationSizePercent() { int result = Options.MaximumYoungGenerationSizePercent.getValue(); VMError.guarantee((result >= 0) && (result <= 100), "MaximumYoungGenerationSizePercent should be in [0 ..100]"); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateGCOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateGCOptions.java index 9f585c3515bd..741a7205b64c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateGCOptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateGCOptions.java @@ -82,4 +82,7 @@ protected void onValueUpdate(EconomicMap, Object> values, Long oldV } } }; + + @Option(help = "The maximum free bytes reserved for allocations, in bytes (0 for automatic according to GC policy).", type = OptionType.User)// + public static final RuntimeOptionKey MaxHeapFree = new RuntimeOptionKey<>(0L); } From 255e22d12b81057b74b84c9008269fb84c9b169d Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Sat, 25 Sep 2021 21:14:05 +0200 Subject: [PATCH 13/17] Adjust adaptive policy parameters. --- .../oracle/svm/core/genscavenge/AdaptiveCollectionPolicy.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AdaptiveCollectionPolicy.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AdaptiveCollectionPolicy.java index c0d8411e54c8..9e76c13cff97 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AdaptiveCollectionPolicy.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AdaptiveCollectionPolicy.java @@ -96,9 +96,9 @@ final class AdaptiveCollectionPolicy extends AbstractCollectionPolicy { private static final boolean ADAPTIVE_SIZE_USE_COST_ESTIMATORS = true; private static final int ADAPTIVE_SIZE_POLICY_INITIALIZING_STEPS = ADAPTIVE_SIZE_POLICY_READY_THRESHOLD; /** The minimum increase in throughput in percent for expanding a space by 1% of its size. */ - private static final double ADAPTIVE_SIZE_ESTIMATOR_MIN_SIZE_COST_TRADEOFF = 0.8; + private static final double ADAPTIVE_SIZE_ESTIMATOR_MIN_SIZE_COST_TRADEOFF = 0.5; /** The effective number of most recent data points used by estimator (exponential decay). */ - private static final int ADAPTIVE_SIZE_COST_ESTIMATORS_HISTORY_LENGTH = ADAPTIVE_TIME_WEIGHT; + private static final int ADAPTIVE_SIZE_COST_ESTIMATORS_HISTORY_LENGTH = 12; /** Threshold for triggering a complete collection after repeated minor collections. */ private static final int CONSECUTIVE_MINOR_TO_MAJOR_COLLECTION_PAUSE_TIME_RATIO = 2; /** From 46db6d5457b198d2cd1f1124b6c647fa6f8b7bde Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Mon, 27 Sep 2021 18:01:10 +0200 Subject: [PATCH 14/17] Ensure that the maximum heap size is strictly less than specified (unless unfeasibly small). --- .../genscavenge/AbstractCollectionPolicy.java | 30 +++++++++---------- .../oracle/svm/core/util/UnsignedUtils.java | 1 + 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AbstractCollectionPolicy.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AbstractCollectionPolicy.java index f503ebb90bf2..307a0ccc992a 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AbstractCollectionPolicy.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AbstractCollectionPolicy.java @@ -248,10 +248,11 @@ public UnsignedWord getMinimumHeapSize() { protected SizeParameters computeSizeParameters() { UnsignedWord addressSpaceSize = ReferenceAccess.singleton().getAddressSpaceSize(); - UnsignedWord minAllSpaces = minSpaceSize().multiply(2); // eden, old + UnsignedWord minYoungSpaces = minSpaceSize(); // eden if (HeapParameters.getMaxSurvivorSpaces() > 0) { - minAllSpaces = minAllSpaces.add(minSpaceSize().multiply(2)); // survivor from and to + minYoungSpaces = minYoungSpaces.add(minSpaceSize().multiply(2)); // survivor from and to } + UnsignedWord minAllSpaces = minYoungSpaces.add(minSpaceSize()); // old UnsignedWord maxHeap; long optionMax = SubstrateGCOptions.MaxHeapSize.getValue(); @@ -275,6 +276,7 @@ protected SizeParameters computeSizeParameters() { maxHeap = reasonableMax; } } + UnsignedWord unadjustedMaxHeap = maxHeap; maxHeap = UnsignedUtils.clamp(alignDown(maxHeap), minAllSpaces, alignDown(addressSpaceSize)); UnsignedWord maxYoung; @@ -286,16 +288,12 @@ protected SizeParameters computeSizeParameters() { } else { maxYoung = maxHeap.unsignedDivide(AbstractCollectionPolicy.NEW_RATIO + 1); } - maxYoung = UnsignedUtils.clamp(alignUp(maxYoung), minSpaceSize(), maxHeap); + maxYoung = UnsignedUtils.clamp(alignDown(maxYoung), minYoungSpaces, maxHeap.subtract(minSpaceSize())); - UnsignedWord maxOld = maxHeap.subtract(maxYoung); - maxOld = minSpaceSize(alignUp(maxOld)); + UnsignedWord maxOld = alignDown(maxHeap.subtract(maxYoung)); maxHeap = maxYoung.add(maxOld); - if (maxHeap.aboveThan(addressSpaceSize)) { - maxYoung = alignDown(maxYoung.subtract(minSpaceSize())); - maxHeap = maxYoung.add(maxOld); - VMError.guarantee(maxHeap.belowOrEqual(addressSpaceSize) && maxYoung.aboveOrEqual(minSpaceSize())); - } + VMError.guarantee(maxOld.aboveOrEqual(minSpaceSize()) && maxHeap.belowOrEqual(addressSpaceSize) && + (maxHeap.belowOrEqual(unadjustedMaxHeap) || unadjustedMaxHeap.belowThan(minAllSpaces))); UnsignedWord minHeap = WordFactory.zero(); long optionMin = SubstrateGCOptions.MinHeapSize.getValue(); @@ -311,7 +309,8 @@ protected SizeParameters computeSizeParameters() { if (initialHeap.equal(maxHeap)) { initialYoung = maxYoung; } else { - initialYoung = UnsignedUtils.clamp(alignUp(initialHeap.unsignedDivide(AbstractCollectionPolicy.NEW_RATIO + 1)), minSpaceSize(), maxYoung); + initialYoung = initialHeap.unsignedDivide(AbstractCollectionPolicy.NEW_RATIO + 1); + initialYoung = UnsignedUtils.clamp(alignUp(initialYoung), minYoungSpaces, maxYoung); } UnsignedWord initialSurvivor = WordFactory.zero(); if (HeapParameters.getMaxSurvivorSpaces() > 0) { @@ -323,12 +322,11 @@ protected SizeParameters computeSizeParameters() { * generation, which we can exceed the (current) old gen size while copying during * collections. */ - initialSurvivor = minSpaceSize(alignUp(initialYoung.unsignedDivide(AbstractCollectionPolicy.INITIAL_SURVIVOR_RATIO))); - } - UnsignedWord initialEden = minSpaceSize(); - if (initialYoung.aboveThan(initialSurvivor.multiply(2))) { - initialEden = minSpaceSize(alignUp(initialYoung.subtract(initialSurvivor.multiply(2)))); + initialSurvivor = initialYoung.unsignedDivide(AbstractCollectionPolicy.INITIAL_SURVIVOR_RATIO); + initialSurvivor = minSpaceSize(alignDown(initialSurvivor)); } + UnsignedWord initialEden = initialYoung.subtract(initialSurvivor.multiply(2)); + initialEden = minSpaceSize(alignDown(initialEden)); return new SizeParameters(maxHeap, maxYoung, initialHeap, initialEden, initialSurvivor, minHeap); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/UnsignedUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/UnsignedUtils.java index 0cd33267c675..1036a923e006 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/UnsignedUtils.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/UnsignedUtils.java @@ -113,6 +113,7 @@ public static int safeToInt(UnsignedWord w) { @Uninterruptible(reason = "Used in uninterruptible code.", mayBeInlined = true) public static UnsignedWord clamp(UnsignedWord value, UnsignedWord min, UnsignedWord max) { + assert min.belowOrEqual(max); return min(max(value, min), max); } From 68b7a07e3ec1185bf1fadc71834c5670c9362dc5 Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Tue, 28 Sep 2021 19:09:58 +0200 Subject: [PATCH 15/17] Ignore threads reporting a fatal error during GC. --- .../src/com/oracle/svm/core/genscavenge/GCImpl.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java index 40481fdd9fd2..83f506087d87 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java @@ -783,6 +783,17 @@ private void blackenStackRoots() { */ continue; } + if (VMThreads.StatusSupport.isStatusIgnoreSafepoints(vmThread)) { + /* + * When a thread is ignoring safepoints, it is reporting a fatal VM error. + * The thread is not paused and we cannot safely walk its stack (we don't + * even know where it starts). We simply ignore it and keep collecting until + * it terminates the VM rather than crashing here. The thread might access + * the heap to print diagnostics, but it must do so with due caution and + * should not crash because of our ongoing GC. + */ + continue; + } if (JavaStackWalker.initWalk(walk, vmThread)) { walkStack(walk); } From 289b35656ae265f06f30ea784e6e3fc9f253aaab Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Thu, 30 Sep 2021 16:37:28 +0200 Subject: [PATCH 16/17] Update heap sizes after a relevant runtime option value changes. --- .../svm/core/genscavenge/CollectionPolicy.java | 6 +++--- .../oracle/svm/core/genscavenge/HeapImpl.java | 7 ++++++- .../svm/core/genscavenge/HeapParameters.java | 18 ++++++++++++++++-- .../oracle/svm/core/SubstrateGCOptions.java | 4 ++++ .../src/com/oracle/svm/core/heap/Heap.java | 6 ++++++ 5 files changed, 35 insertions(+), 6 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/CollectionPolicy.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/CollectionPolicy.java index 845f212ad979..feb0b4680fdd 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/CollectionPolicy.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/CollectionPolicy.java @@ -102,9 +102,9 @@ static boolean shouldCollectYoungGenSeparately(boolean defaultValue) { /** * (Re)computes minimum/maximum/initial sizes of space based on the available - * {@linkplain PhysicalMemory physical memory} and current runtime option values. This method is - * called after slow-path allocation (of a TLAB or a large object) and so allocation is allowed, - * but can trigger a collection. + * {@linkplain PhysicalMemory physical memory} and current runtime option values. This method + * can be called directly or after a slow-path allocation (of a TLAB or a large object) and so + * allocation is allowed, but may trigger a collection. */ void updateSizeParameters(); diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapImpl.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapImpl.java index 31803a5b8e61..129d35bcd0e8 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapImpl.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapImpl.java @@ -638,6 +638,11 @@ public boolean printLocationInfo(Log log, UnsignedWord value, boolean allowJavaH return false; } + @Override + public void updateSizeParameters() { + GCImpl.getPolicy().updateSizeParameters(); + } + static Pointer getImageHeapStart() { int imageHeapOffsetInAddressSpace = Heap.getHeap().getImageHeapOffsetInAddressSpace(); if (imageHeapOffsetInAddressSpace > 0) { @@ -846,7 +851,7 @@ private long totalMemory() { @Substitute private long maxMemory() { PhysicalMemory.size(); // ensure physical memory size is set correctly and not estimated - GCImpl.getPolicy().updateSizeParameters(); + Heap.getHeap().updateSizeParameters(); return GCImpl.getPolicy().getMaximumHeapSize().rawValue(); } diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapParameters.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapParameters.java index b7e40097d68b..d6af7c626da5 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapParameters.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapParameters.java @@ -39,6 +39,7 @@ import com.oracle.svm.core.SubstrateGCOptions; import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.annotate.Uninterruptible; +import com.oracle.svm.core.heap.Heap; import com.oracle.svm.core.option.HostedOptionKey; import com.oracle.svm.core.option.RuntimeOptionKey; import com.oracle.svm.core.option.RuntimeOptionValues; @@ -48,11 +49,24 @@ /** Constants and variables for the size and layout of the heap and behavior of the collector. */ public final class HeapParameters { public static final class Options { + static final class HeapSizeOptionKey extends RuntimeOptionKey { + HeapSizeOptionKey(T defaultValue) { + super(defaultValue); + } + + @Override + protected void onValueUpdate(EconomicMap, Object> values, T oldValue, T newValue) { + if (!SubstrateUtil.HOSTED) { + Heap.getHeap().updateSizeParameters(); + } + } + } + @Option(help = "The maximum heap size as percent of physical memory") // - public static final RuntimeOptionKey MaximumHeapSizePercent = new RuntimeOptionKey<>(80); + public static final RuntimeOptionKey MaximumHeapSizePercent = new HeapSizeOptionKey<>(80); @Option(help = "The maximum size of the young generation as a percentage of the maximum heap size") // - public static final RuntimeOptionKey MaximumYoungGenerationSizePercent = new RuntimeOptionKey<>(10); + public static final RuntimeOptionKey MaximumYoungGenerationSizePercent = new HeapSizeOptionKey<>(10); @Option(help = "The size of an aligned chunk.") // public static final HostedOptionKey AlignedHeapChunkSize = new HostedOptionKey(1L * 1024L * 1024L) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateGCOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateGCOptions.java index 741a7205b64c..f5a020d20cf2 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateGCOptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateGCOptions.java @@ -30,6 +30,7 @@ import org.graalvm.compiler.options.OptionType; import org.graalvm.word.WordFactory; +import com.oracle.svm.core.heap.Heap; import com.oracle.svm.core.heap.HeapSizeVerifier; import com.oracle.svm.core.option.HostedOptionKey; import com.oracle.svm.core.option.RuntimeOptionKey; @@ -59,6 +60,7 @@ public class SubstrateGCOptions { protected void onValueUpdate(EconomicMap, Object> values, Long oldValue, Long newValue) { if (!SubstrateUtil.HOSTED) { HeapSizeVerifier.verifyMinHeapSizeAgainstAddressSpace(WordFactory.unsigned(newValue)); + Heap.getHeap().updateSizeParameters(); } } }; @@ -69,6 +71,7 @@ protected void onValueUpdate(EconomicMap, Object> values, Long oldV protected void onValueUpdate(EconomicMap, Object> values, Long oldValue, Long newValue) { if (!SubstrateUtil.HOSTED) { HeapSizeVerifier.verifyMaxHeapSizeAgainstAddressSpace(WordFactory.unsigned(newValue)); + Heap.getHeap().updateSizeParameters(); } } }; @@ -79,6 +82,7 @@ protected void onValueUpdate(EconomicMap, Object> values, Long oldV protected void onValueUpdate(EconomicMap, Object> values, Long oldValue, Long newValue) { if (!SubstrateUtil.HOSTED) { HeapSizeVerifier.verifyMaxNewSizeAgainstAddressSpace(WordFactory.unsigned(newValue)); + Heap.getHeap().updateSizeParameters(); } } }; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/Heap.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/Heap.java index 59a2cdb4828a..b7a938af6eca 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/Heap.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/Heap.java @@ -217,4 +217,10 @@ public List> getLoadedClasses() { * value and returns true. Otherwise, the method returns false. */ public abstract boolean printLocationInfo(Log log, UnsignedWord value, boolean allowJavaHeapAccess, boolean allowUnsafeOperations); + + /** + * (Re)computes minimum/maximum/initial sizes of space based on the available + * {@linkplain PhysicalMemory physical memory} and current runtime option values. + */ + public abstract void updateSizeParameters(); } From 7af0e63f5a6a9a3d3b61ffd497d65e5bbd7e976d Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Mon, 13 Sep 2021 14:38:52 +0200 Subject: [PATCH 17/17] Default to adaptive GC policy. --- .../src/com/oracle/svm/core/genscavenge/CollectionPolicy.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/CollectionPolicy.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/CollectionPolicy.java index feb0b4680fdd..581ea291de19 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/CollectionPolicy.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/CollectionPolicy.java @@ -38,8 +38,8 @@ /** The interface for a garbage collection policy. All sizes are in bytes. */ public interface CollectionPolicy { final class Options { - @Option(help = "The garbage collection policy, either Adaptive or BySpaceAndTime (default).")// - public static final HostedOptionKey InitialCollectionPolicy = new HostedOptionKey<>("BySpaceAndTime"); + @Option(help = "The garbage collection policy, either Adaptive (default) or BySpaceAndTime.")// + public static final HostedOptionKey InitialCollectionPolicy = new HostedOptionKey<>("Adaptive"); } @Platforms(Platform.HOSTED_ONLY.class)