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 00da84e47c56..4925ebad1976 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 @@ -31,6 +31,9 @@ import java.lang.ref.Reference; +import com.oracle.svm.core.config.ConfigurationValues; +import com.oracle.svm.core.handles.ObjectHandlesImpl; +import com.oracle.svm.core.jni.JNIObjectHandles; import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.Platform; @@ -39,6 +42,7 @@ import org.graalvm.nativeimage.c.struct.RawField; import org.graalvm.nativeimage.c.struct.RawStructure; import org.graalvm.nativeimage.c.struct.SizeOf; +import org.graalvm.nativeimage.c.type.WordPointer; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; @@ -86,7 +90,6 @@ import com.oracle.svm.core.heap.UninterruptibleObjectReferenceVisitor; import com.oracle.svm.core.heap.UninterruptibleObjectVisitor; import com.oracle.svm.core.heap.VMOperationInfos; -import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport; import com.oracle.svm.core.interpreter.InterpreterSupport; import com.oracle.svm.core.jdk.RuntimeSupport; import com.oracle.svm.core.jfr.JfrGCWhen; @@ -108,22 +111,15 @@ import com.oracle.svm.core.thread.VMOperation; import com.oracle.svm.core.thread.VMThreads; import com.oracle.svm.core.threadlocal.VMThreadLocalSupport; -import com.oracle.svm.core.traits.BuiltinTraits.AllAccess; -import com.oracle.svm.core.traits.BuiltinTraits.NoLayeredCallbacks; -import com.oracle.svm.core.traits.BuiltinTraits.PartiallyLayerAware; -import com.oracle.svm.core.traits.SingletonLayeredInstallationKind.Independent; -import com.oracle.svm.core.traits.SingletonTraits; import com.oracle.svm.core.util.TimeUtils; import com.oracle.svm.core.util.Timer; import com.oracle.svm.core.util.VMError; - import jdk.graal.compiler.api.replacements.Fold; import jdk.graal.compiler.word.Word; /** * Garbage collector (incremental or complete) for {@link HeapImpl}. */ -@SingletonTraits(access = AllAccess.class, layeredCallbacks = NoLayeredCallbacks.class, layeredInstallationKind = Independent.class, other = PartiallyLayerAware.class) public final class GCImpl implements GC { private static final long K = 1024; static final long M = K * K; @@ -141,16 +137,15 @@ public final class GCImpl implements GC { private final CollectionPolicy policy; private boolean completeCollection = false; - private boolean outOfMemoryCollection = false; private UnsignedWord collectionEpoch = Word.zero(); private long lastWholeHeapExaminedNanos = -1; + private static final int WORD_SIZE = ConfigurationValues.getTarget().wordSize; + private final GlobalHandlesBucketCallback GLOBAL_CALLBACK = new GlobalHandlesBucketCallback(greyToBlackObjRefVisitor); @Platforms(Platform.HOSTED_ONLY.class) GCImpl() { this.policy = CollectionPolicy.getInitialPolicy(); - if (ImageLayerBuildingSupport.firstImageBuild()) { - RuntimeSupport.getRuntimeSupport().addShutdownHook(_ -> printGCSummary()); - } + RuntimeSupport.getRuntimeSupport().addShutdownHook(_ -> printGCSummary()); } @Override @@ -309,12 +304,13 @@ private boolean collectImpl(GCCause cause, long beginNanoTime, boolean forceFull try { outOfMemory = doCollectImpl(cause, beginNanoTime, forceFullGC, false); if (outOfMemory) { - outOfMemoryCollection = true; // increase eagerness to free memory + // Avoid running out of memory with a full GC that reclaims softly reachable objects + ReferenceObjectProcessing.setSoftReferencesAreWeak(true); try { verifyHeap(During); outOfMemory = doCollectImpl(cause, System.nanoTime(), true, true); } finally { - outOfMemoryCollection = false; + ReferenceObjectProcessing.setSoftReferencesAreWeak(false); } } } finally { @@ -328,7 +324,7 @@ private boolean doCollectImpl(GCCause cause, long initialBeginNanoTime, boolean ChunkBasedCommittedMemoryProvider.get().beforeGarbageCollection(); - boolean incremental = !forceNoIncremental && !policy.shouldCollectCompletely(false, forceFullGC); + boolean incremental = !forceNoIncremental && !policy.shouldCollectCompletely(false); boolean outOfMemory = false; if (incremental) { @@ -339,7 +335,7 @@ private boolean doCollectImpl(GCCause cause, long initialBeginNanoTime, boolean JfrGCEvents.emitGCPhasePauseEvent(getCollectionEpoch(), "Incremental GC", startTicks); } } - if (!incremental || outOfMemory || forceFullGC || policy.shouldCollectCompletely(incremental, forceFullGC)) { + if (!incremental || outOfMemory || forceFullGC || policy.shouldCollectCompletely(incremental)) { long beginNanoTime = initialBeginNanoTime; if (incremental) { beginNanoTime = System.nanoTime(); @@ -369,7 +365,7 @@ private boolean doCollectOnce(GCCause cause, long beginNanoTime, boolean complet accounting.beforeCollectOnce(completeCollection); policy.onCollectionBegin(completeCollection, beginNanoTime); - doCollectCore(); + doCollectCore(!complete); if (complete) { lastWholeHeapExaminedNanos = System.nanoTime(); } @@ -434,7 +430,7 @@ public static UnsignedWord getChunkBytes() { } private static void resizeAllTlabs() { - if (SubstrateGCOptions.ResizeTLAB.getValue()) { + if (SubstrateGCOptions.TlabOptions.ResizeTLAB.getValue()) { for (IsolateThread thread = VMThreads.firstThread(); thread.isNonNull(); thread = VMThreads.nextThread(thread)) { TlabSupport.resize(thread); } @@ -526,24 +522,13 @@ public void collectCompletely(GCCause cause) { collect(cause, true); } - @AlwaysInline("GC performance") @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public boolean isCompleteCollection() { return completeCollection; } - /** - * Whether the current collection is intended to be more aggressive as a last resort to avoid an - * out of memory condition. - */ - @AlwaysInline("GC performance") - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - public boolean isOutOfMemoryCollection() { - return outOfMemoryCollection; - } - /** Collect, either incrementally or completely, and process discovered references. */ - private void doCollectCore() { + private void doCollectCore(boolean incremental) { GreyToBlackObjRefVisitor.Counters counters = greyToBlackObjRefVisitor.openCounters(); long startTicks; try { @@ -552,15 +537,15 @@ private void doCollectCore() { startTicks = JfrGCEvents.startGCPhasePause(); try { /* Scan reachable objects and potentially already copy them once discovered. */ - scan(); + scan(incremental); } finally { - JfrGCEvents.emitGCPhasePauseEvent(getCollectionEpoch(), completeCollection ? "Scan" : "Incremental Scan", startTicks); + JfrGCEvents.emitGCPhasePauseEvent(getCollectionEpoch(), incremental ? "Incremental Scan" : "Scan", startTicks); } } finally { rootScanTimer.stop(); } - if (completeCollection) { + if (!incremental) { /* Sweep or compact objects in the old generation unless already done by copying. */ HeapImpl.getHeapImpl().getOldGeneration().sweepAndCompact(timers, chunkReleaser); } @@ -610,7 +595,7 @@ private void doCollectCore() { * chunks for copying live old objects with fewer chunk allocations. In either * case, excess chunks are released later. */ - boolean keepAllAlignedChunks = !SerialGCOptions.useCompactingOldGen() && !completeCollection; + boolean keepAllAlignedChunks = !SerialGCOptions.useCompactingOldGen() && incremental; chunkReleaser.release(keepAllAlignedChunks); } finally { JfrGCEvents.emitGCPhasePauseEvent(getCollectionEpoch(), "Release Spaces", startTicks); @@ -655,70 +640,156 @@ private void cleanRuntimeCodeCache() { } @Uninterruptible(reason = "We don't want any safepoint checks in the core part of the GC.") - private void scan() { - Timer timer = completeCollection ? timers.scanFromRoots : timers.scanFromDirtyRoots; - timer.start(); - try { + private void scan(boolean incremental) { + if (incremental) { + scanFromDirtyRoots(); + } else { scanFromRoots(); - } finally { - timer.stop(); } } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private void scanFromRoots() { - long startTicks = JfrGCEvents.startGCPhasePause(); + static final class GlobalHandlesBucketCallback implements ObjectHandlesImpl.BucketCallback { + GreyToBlackObjRefVisitor v; - try { - /* Snapshot the heap so that objects that are promoted afterwards can be visited. */ - beginPromotion(); - } finally { - JfrGCEvents.emitGCPhasePauseEvent(getCollectionEpoch(), "Snapshot Heap", startTicks); + private GlobalHandlesBucketCallback(GreyToBlackObjRefVisitor v) { + this.v = v; } - startTicks = JfrGCEvents.startGCPhasePause(); + @Override + @Uninterruptible(reason = "Construct callback with visitor") + public void visitBucket(Pointer bucketAddress, int count) { + v.visitObjectReferences( + bucketAddress, + false, + WORD_SIZE, + null, + count + ); + } + } + + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private void scanFromRoots() { + Timer scanFromRootsTimer = timers.scanFromRoots.start(); try { - /* - * Make sure all aligned chunks with pinned objects are in To spaces so that pinned - * objects stay alive and cannot move. - */ - promoteChunksWithPinnedObjects(); + long startTicks = JfrGCEvents.startGCPhasePause(); + try { + /* + * Snapshot the heap so that objects that are promoted afterwards can be visited. + * When using a compacting old generation, it absorbs all chunks from the young + * generation at this point. + */ + beginPromotion(false); + } finally { + JfrGCEvents.emitGCPhasePauseEvent(getCollectionEpoch(), "Snapshot Heap", startTicks); + } + + startTicks = JfrGCEvents.startGCPhasePause(); + try { + /* + * Make sure all chunks with pinned objects are in toSpace, and any formerly pinned + * objects are in fromSpace. + */ + promoteChunksWithPinnedObjects(); + } finally { + JfrGCEvents.emitGCPhasePauseEvent(getCollectionEpoch(), "Promote Pinned Objects", startTicks); + } + + startTicks = JfrGCEvents.startGCPhasePause(); + try { + blackenStackRoots(); + blackenThreadLocals(); + JNIObjectHandles.scanGlobalHandleBuckets(GLOBAL_CALLBACK); + blackenImageHeapRoots(); + blackenMetaspace(); + } finally { + JfrGCEvents.emitGCPhasePauseEvent(getCollectionEpoch(), "Scan Roots", startTicks); + } + + startTicks = JfrGCEvents.startGCPhasePause(); + try { + /* Visit all the Objects promoted since the snapshot. */ + scanGreyObjects(false); + + if (RuntimeCompilation.isEnabled()) { + /* + * Visit the runtime compiled code, now that we know all the reachable objects. + */ + walkRuntimeCodeCache(); + + /* Visit all objects that became reachable because of the compiled code. */ + scanGreyObjects(false); + } + } finally { + JfrGCEvents.emitGCPhasePauseEvent(getCollectionEpoch(), "Scan From Roots", startTicks); + } } finally { - JfrGCEvents.emitGCPhasePauseEvent(getCollectionEpoch(), "Promote Pinned Objects", startTicks); + scanFromRootsTimer.stop(); } + } - startTicks = JfrGCEvents.startGCPhasePause(); + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private void scanFromDirtyRoots() { + Timer scanFromDirtyRootsTimer = timers.scanFromDirtyRoots.start(); try { - if (!completeCollection) { + long startTicks = JfrGCEvents.startGCPhasePause(); + + try { + /* Snapshot the heap so that objects that are promoted afterwards can be visited. */ + beginPromotion(true); + } finally { + JfrGCEvents.emitGCPhasePauseEvent(getCollectionEpoch(), "Snapshot Heap", startTicks); + } + + startTicks = JfrGCEvents.startGCPhasePause(); + try { + /* + * Make sure any released objects are in toSpace (because this is an incremental + * collection). I do this before blackening any roots to make sure the chunks with + * pinned objects are moved entirely, as opposed to promoting the objects + * individually by roots. This makes the objects in those chunks grey. + */ + promoteChunksWithPinnedObjects(); + } finally { + JfrGCEvents.emitGCPhasePauseEvent(getCollectionEpoch(), "Promote Pinned Objects", startTicks); + } + + startTicks = JfrGCEvents.startGCPhasePause(); + try { /* * Blacken Objects that are dirty roots. There are dirty cards in ToSpace. Do this * early so I don't have to walk the cards of individually promoted objects, which * will be visited by the grey object scanner. */ blackenDirtyCardRoots(); + blackenStackRoots(); + blackenThreadLocals(); + JNIObjectHandles.scanGlobalHandleBuckets(GLOBAL_CALLBACK); + blackenDirtyImageHeapRoots(); + blackenMetaspace(); + } finally { + JfrGCEvents.emitGCPhasePauseEvent(getCollectionEpoch(), "Scan Roots", startTicks); } - blackenStackRoots(); - blackenThreadLocals(); - blackenImageHeapRoots(); - blackenMetaspace(); - } finally { - JfrGCEvents.emitGCPhasePauseEvent(getCollectionEpoch(), "Scan Roots", startTicks); - } - startTicks = JfrGCEvents.startGCPhasePause(); - try { - /* Visit all the Objects promoted since the snapshot. */ - scanGreyObjects(); + startTicks = JfrGCEvents.startGCPhasePause(); + try { + /* Visit all the Objects promoted since the snapshot, transitively. */ + scanGreyObjects(true); - if (RuntimeCompilation.isEnabled()) { - /* Visit the runtime compiled code, now that we know all the reachable objects. */ - walkRuntimeCodeCache(); + if (RuntimeCompilation.isEnabled()) { + /* + * Visit the runtime compiled code, now that we know all the reachable objects. + */ + walkRuntimeCodeCache(); - /* Visit all objects that became reachable because of the compiled code. */ - scanGreyObjects(); + /* Visit all objects that became reachable because of the compiled code. */ + scanGreyObjects(true); + } + } finally { + JfrGCEvents.emitGCPhasePauseEvent(getCollectionEpoch(), "Scan From Roots", startTicks); } } finally { - JfrGCEvents.emitGCPhasePauseEvent(getCollectionEpoch(), "Scan From Roots", startTicks); + scanFromDirtyRootsTimer.stop(); } } @@ -845,31 +916,22 @@ private void blackenThreadLocals() { } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private void blackenImageHeapRoots() { + private void blackenDirtyImageHeapRoots() { + if (!HeapImpl.usesImageHeapCardMarking()) { + blackenImageHeapRoots(); + return; + } + Timer blackenImageHeapRootsTimer = timers.blackenImageHeapRoots.start(); try { - /* - * Avoid scanning the entire image heap even for complete collections: its remembered - * set contains references into both the runtime heap's old and young generations. - */ - boolean onlyDirty = HeapImpl.usesImageHeapCardMarking(); - for (ImageHeapInfo info : HeapImpl.getImageHeapInfos()) { - if (onlyDirty) { - blackenDirtyImageHeapChunkRoots(info); - } else { - blackenImageHeapRoots(info); - } + blackenDirtyImageHeapChunkRoots(info); } if (AuxiliaryImageHeap.isPresent()) { ImageHeapInfo auxInfo = AuxiliaryImageHeap.singleton().getImageHeapInfo(); if (auxInfo != null) { - if (onlyDirty) { - blackenDirtyImageHeapChunkRoots(auxInfo); - } else { - blackenImageHeapRoots(auxInfo); - } + blackenDirtyImageHeapChunkRoots(auxInfo); } } } finally { @@ -890,10 +952,35 @@ private void blackenDirtyImageHeapChunkRoots(ImageHeapInfo info) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) static void walkDirtyImageHeapChunkRoots(ImageHeapInfo info, UninterruptibleObjectVisitor visitor, UninterruptibleObjectReferenceVisitor refVisitor, boolean clean) { - assert HeapImpl.usesImageHeapCardMarking(); RememberedSet.get().walkDirtyObjects(info.getFirstWritableAlignedChunk(), info.getFirstWritableUnalignedChunk(), info.getLastWritableUnalignedChunk(), visitor, refVisitor, clean); } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private void blackenImageHeapRoots() { + if (HeapImpl.usesImageHeapCardMarking()) { + // Avoid scanning the entire image heap even for complete collections: its remembered + // set contains references into both the runtime heap's old and young generations. + blackenDirtyImageHeapRoots(); + return; + } + + Timer blackenImageHeapRootsTimer = timers.blackenImageHeapRoots.start(); + try { + for (ImageHeapInfo info : HeapImpl.getImageHeapInfos()) { + blackenImageHeapRoots(info); + } + + if (AuxiliaryImageHeap.isPresent()) { + ImageHeapInfo auxImageHeapInfo = AuxiliaryImageHeap.singleton().getImageHeapInfo(); + if (auxImageHeapInfo != null) { + blackenImageHeapRoots(auxImageHeapInfo); + } + } + } finally { + blackenImageHeapRootsTimer.stop(); + } + } + @Uninterruptible(reason = "Forced inlining (StoredContinuation objects must not move).") private void blackenImageHeapRoots(ImageHeapInfo imageHeapInfo) { walkImageHeapRoots(imageHeapInfo, greyToBlackObjectVisitor); @@ -902,13 +989,12 @@ private void blackenImageHeapRoots(ImageHeapInfo imageHeapInfo) { @AlwaysInline("GC Performance") @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) static void walkImageHeapRoots(ImageHeapInfo imageHeapInfo, ObjectVisitor visitor) { - ImageHeapWalker.walkPartitionInline(imageHeapInfo.firstAlignedWritableObject, imageHeapInfo.lastAlignedWritableObject, visitor, true); - ImageHeapWalker.walkPartitionInline(imageHeapInfo.firstUnalignedWritableObject, imageHeapInfo.lastUnalignedWritableObject, visitor, false); + ImageHeapWalker.walkPartitionInline(imageHeapInfo.firstWritableRegularObject, imageHeapInfo.lastWritableRegularObject, visitor, true); + ImageHeapWalker.walkPartitionInline(imageHeapInfo.firstWritableHugeObject, imageHeapInfo.lastWritableHugeObject, visitor, false); } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private void blackenDirtyCardRoots() { - assert !completeCollection : "only call for incremental collections"; Timer blackenDirtyCardRootsTimer = timers.blackenDirtyCardRoots.start(); try { /* @@ -941,22 +1027,22 @@ private void blackenMetaspace() { } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private void beginPromotion() { + private static void beginPromotion(boolean isIncremental) { HeapImpl heap = HeapImpl.getHeapImpl(); - heap.getOldGeneration().beginPromotion(completeCollection); - if (!completeCollection) { + heap.getOldGeneration().beginPromotion(isIncremental); + if (isIncremental) { heap.getYoungGeneration().beginPromotion(); } } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private void scanGreyObjects() { + private void scanGreyObjects(boolean isIncremental) { Timer scanGreyObjectsTimer = timers.scanGreyObjects.start(); try { - if (completeCollection) { - HeapImpl.getHeapImpl().getOldGeneration().scanGreyObjects(true); - } else { + if (isIncremental) { incrementalScanGreyObjectsLoop(); + } else { + HeapImpl.getHeapImpl().getOldGeneration().scanGreyObjects(false); } } finally { scanGreyObjectsTimer.stop(); @@ -964,15 +1050,14 @@ private void scanGreyObjects() { } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private void incrementalScanGreyObjectsLoop() { - assert !completeCollection; + private static void incrementalScanGreyObjectsLoop() { HeapImpl heap = HeapImpl.getHeapImpl(); YoungGeneration youngGen = heap.getYoungGeneration(); OldGeneration oldGen = heap.getOldGeneration(); boolean hasGrey; do { hasGrey = youngGen.scanGreyObjects(); - hasGrey |= oldGen.scanGreyObjects(false); + hasGrey |= oldGen.scanGreyObjects(true); } while (hasGrey); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/handles/ObjectHandlesImpl.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/handles/ObjectHandlesImpl.java index 6c5d6417dd45..2d73327746b4 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/handles/ObjectHandlesImpl.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/handles/ObjectHandlesImpl.java @@ -24,24 +24,24 @@ */ package com.oracle.svm.core.handles; -import java.lang.ref.WeakReference; - -import jdk.graal.compiler.word.Word; +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.memory.NullableNativeMemory; +import com.oracle.svm.core.nmt.NmtCategory; import org.graalvm.nativeimage.ObjectHandle; import org.graalvm.nativeimage.ObjectHandles; +import org.graalvm.word.LocationIdentity; +import org.graalvm.word.Pointer; import org.graalvm.word.SignedWord; import org.graalvm.word.WordBase; - -import jdk.internal.misc.Unsafe; +import com.oracle.svm.core.config.ConfigurationValues; +import jdk.graal.compiler.word.Word; +import org.graalvm.word.WordFactory; /** * This class implements {@link ObjectHandle word}-sized integer handles that refer to Java objects. * {@link #create(Object) Creating}, {@link #get(ObjectHandle) dereferencing} and * {@link #destroy(ObjectHandle) destroying} handles is thread-safe and the handles themselves are - * valid across threads. This class also supports weak handles, with which the referenced object may - * be garbage-collected, after which {@link #get(ObjectHandle)} returns {@code null}. Still, weak - * handles must also be {@link #destroyWeak(ObjectHandle) explicitly destroyed} to reclaim their - * handle value. + * valid across threads. *
* The implementation uses a variable number of object arrays, in which each array element
* represents a handle. The array element's index determines the handle's integer value, and the
@@ -53,14 +53,8 @@
*/
public final class ObjectHandlesImpl implements ObjectHandles {
- /** Private subclass to distinguish from regular handles to {@link WeakReference} objects. */
- private static final class HandleWeakReference