Skip to content

Commit 24c49d2

Browse files
author
Eric Wu
committed
[GR-62080] Prevent out-of-memory errors during deoptimization
PullRequest: graal/20025
2 parents 804dab2 + 9d21c6b commit 24c49d2

File tree

4 files changed

+190
-75
lines changed

4 files changed

+190
-75
lines changed

substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
import com.oracle.svm.core.heap.ReferenceMapIndex;
8383
import com.oracle.svm.core.heap.RestrictHeapAccess;
8484
import com.oracle.svm.core.heap.RuntimeCodeCacheCleaner;
85+
import com.oracle.svm.core.heap.SuspendSerialGCMaxHeapSize;
8586
import com.oracle.svm.core.heap.VMOperationInfos;
8687
import com.oracle.svm.core.interpreter.InterpreterSupport;
8788
import com.oracle.svm.core.jdk.RuntimeSupport;
@@ -211,7 +212,7 @@ boolean collectWithoutAllocating(GCCause cause, boolean forceFullGC) {
211212

212213
@Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true)
213214
public static boolean shouldIgnoreOutOfMemory() {
214-
return SerialGCOptions.IgnoreMaxHeapSizeWhileInVMInternalCode.getValue() && inVMInternalCode();
215+
return SerialGCOptions.IgnoreMaxHeapSizeWhileInVMInternalCode.getValue() && (inVMInternalCode() || SuspendSerialGCMaxHeapSize.isSuspended());
215216
}
216217

217218
@Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true)

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/deopt/Deoptimizer.java

Lines changed: 53 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
import com.oracle.svm.core.heap.GCCause;
6262
import com.oracle.svm.core.heap.Heap;
6363
import com.oracle.svm.core.heap.ReferenceAccess;
64+
import com.oracle.svm.core.heap.SuspendSerialGCMaxHeapSize;
6465
import com.oracle.svm.core.heap.VMOperationInfos;
6566
import com.oracle.svm.core.log.Log;
6667
import com.oracle.svm.core.log.StringBuilderLog;
@@ -85,6 +86,7 @@
8586
import jdk.graal.compiler.api.replacements.Fold;
8687
import jdk.graal.compiler.core.common.NumUtil;
8788
import jdk.graal.compiler.core.common.util.TypeConversion;
89+
import jdk.graal.compiler.nodes.UnreachableNode;
8890
import jdk.graal.compiler.options.Option;
8991
import jdk.graal.compiler.word.BarrieredAccess;
9092
import jdk.graal.compiler.word.Word;
@@ -797,53 +799,51 @@ public static UnsignedWord lazyDeoptStubPrimitiveReturn(Pointer framePointer, Un
797799
@Uninterruptible(reason = "frame will hold objects in unmanaged storage")
798800
private static UnsignedWord lazyDeoptStubCore(Pointer framePointer, UnsignedWord gpReturnValue, UnsignedWord fpReturnValue, boolean hasException, Object gpReturnValueObject) {
799801
DeoptimizedFrame deoptFrame;
800-
Pointer newSp;
801802

802-
StackOverflowCheck.singleton().makeYellowZoneAvailable();
803-
try {
804-
/* The original return address is at offset 0 from the stack pointer */
805-
CodePointer originalReturnAddress = framePointer.readWord(0);
806-
assert originalReturnAddress.isNonNull();
803+
/* The original return address is at offset 0 from the stack pointer */
804+
CodePointer originalReturnAddress = framePointer.readWord(0);
805+
VMError.guarantee(originalReturnAddress.isNonNull());
806+
807+
/* Clear the deoptimization slot. */
808+
framePointer.writeWord(0, Word.nullPointer());
807809

808-
/* Clear the deoptimization slot. */
809-
framePointer.writeWord(0, Word.nullPointer());
810+
/*
811+
* Write the old return address to the return address slot, so that stack walks see a
812+
* consistent stack.
813+
*/
814+
FrameAccess.singleton().writeReturnAddress(CurrentIsolate.getCurrentThread(), framePointer, originalReturnAddress);
810815

816+
try {
817+
deoptFrame = constructLazilyDeoptimizedFrameInterruptibly(framePointer, originalReturnAddress, hasException);
818+
} catch (OutOfMemoryError ex) {
811819
/*
812-
* Write the old return address to the return address slot, so that stack walks see a
813-
* consistent stack.
820+
* If a OutOfMemoryError occurs during lazy deoptimization, we cannot let the frame
821+
* being deoptimized handle the exception, because it might have been invalidated due to
822+
* incorrect assumptions. Note that since unwindExceptionSkippingCaller does not return,
823+
* this try...catch must not have a finally block, as it will not be executed.
814824
*/
815-
FrameAccess.singleton().writeReturnAddress(CurrentIsolate.getCurrentThread(), framePointer, originalReturnAddress);
825+
ExceptionUnwind.unwindExceptionSkippingCaller(ex, framePointer);
826+
throw UnreachableNode.unreachable();
827+
}
816828

817-
UntetheredCodeInfo untetheredInfo = CodeInfoTable.lookupCodeInfo(originalReturnAddress);
818-
Object tether = CodeInfoAccess.acquireTether(untetheredInfo);
819-
try {
820-
CodeInfo info = CodeInfoAccess.convert(untetheredInfo, tether);
821-
deoptFrame = constructLazilyDeoptimizedFrameInterruptibly(framePointer, info, originalReturnAddress, hasException);
822-
} finally {
823-
CodeInfoAccess.releaseTether(untetheredInfo, tether);
824-
}
829+
DeoptimizationCounters.counters().deoptCount.inc();
830+
VMError.guarantee(deoptFrame != null, "was not able to lazily construct a deoptimized frame");
825831

826-
DeoptimizationCounters.counters().deoptCount.inc();
827-
assert deoptFrame != null : "was not able to lazily construct a deoptimized frame";
832+
Pointer newSp = computeNewFramePointer(framePointer, deoptFrame);
828833

829-
newSp = computeNewFramePointer(framePointer, deoptFrame);
834+
/* Build the content of the deopt target stack frames. */
835+
deoptFrame.buildContent(newSp);
830836

831-
/* Build the content of the deopt target stack frames. */
832-
deoptFrame.buildContent(newSp);
837+
/*
838+
* We fail fatally if eager deoptimization is invoked when the lazy deopt stub is executing,
839+
* because eager deoptimization should only be invoked through stack introspection, which
840+
* can only be called from the current thread. Thus, there is no use case for eager
841+
* deoptimization to happen if the current thread is executing the lazy deopt stub.
842+
*/
843+
VMError.guarantee(framePointer.readWord(0) == Word.nullPointer(), "Eager deoptimization should not occur when lazy deoptimization is in progress");
833844

834-
/*
835-
* We fail fatally if eager deoptimization is invoked when the lazy deopt stub is
836-
* executing, because eager deoptimization should only be invoked through stack
837-
* introspection, which can only be called from the current thread. Thus, there is no
838-
* use case for eager deoptimization to happen if the current thread is executing the
839-
* lazy deopt stub.
840-
*/
841-
VMError.guarantee(framePointer.readWord(0) == Word.nullPointer(), "Eager deoptimization should not occur when lazy deoptimization is in progress");
845+
recentDeoptimizationEvents.append(deoptFrame.getCompletedMessage());
842846

843-
recentDeoptimizationEvents.append(deoptFrame.getCompletedMessage());
844-
} finally {
845-
StackOverflowCheck.singleton().protectYellowZone();
846-
}
847847
// From this point on, only uninterruptible code may be executed.
848848
UnsignedWord updatedGpReturnValue = gpReturnValue;
849849
if (gpReturnValueObject != null) {
@@ -855,8 +855,23 @@ private static UnsignedWord lazyDeoptStubCore(Pointer framePointer, UnsignedWord
855855
}
856856

857857
@Uninterruptible(reason = "Wrapper to call interruptible methods", calleeMustBe = false)
858-
private static DeoptimizedFrame constructLazilyDeoptimizedFrameInterruptibly(Pointer sourceSp, CodeInfo info, CodePointer ip, boolean hasException) {
859-
return constructLazilyDeoptimizedFrameInterruptibly0(sourceSp, info, ip, hasException);
858+
private static DeoptimizedFrame constructLazilyDeoptimizedFrameInterruptibly(Pointer sourceSp, CodePointer ip, boolean hasException) {
859+
StackOverflowCheck.singleton().makeYellowZoneAvailable();
860+
SuspendSerialGCMaxHeapSize.suspendInCurrentThread();
861+
862+
try {
863+
UntetheredCodeInfo untetheredInfo = CodeInfoTable.lookupCodeInfo(ip);
864+
Object tether = CodeInfoAccess.acquireTether(untetheredInfo);
865+
try {
866+
CodeInfo info = CodeInfoAccess.convert(untetheredInfo, tether);
867+
return constructLazilyDeoptimizedFrameInterruptibly0(sourceSp, info, ip, hasException);
868+
} finally {
869+
CodeInfoAccess.releaseTether(untetheredInfo, tether);
870+
}
871+
} finally {
872+
SuspendSerialGCMaxHeapSize.resumeInCurrentThread();
873+
StackOverflowCheck.singleton().protectYellowZone();
874+
}
860875
}
861876

862877
private static DeoptimizedFrame constructLazilyDeoptimizedFrameInterruptibly0(Pointer sourceSp, CodeInfo info, CodePointer ip, boolean hasException) {
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
package com.oracle.svm.core.heap;
26+
27+
import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE;
28+
29+
import com.oracle.svm.core.Uninterruptible;
30+
import com.oracle.svm.core.threadlocal.FastThreadLocalFactory;
31+
import com.oracle.svm.core.threadlocal.FastThreadLocalInt;
32+
33+
/**
34+
* Allows the max heap size restriction to be temporarily suspended, in order to avoid running out
35+
* of memory during critical VM operations. Note calling {@link #suspendInCurrentThread()} will have
36+
* effect only for the current thread, so other threads may still attempt to allocate and throw an
37+
* {@link OutOfMemoryError}.
38+
*
39+
* This option will only take effect if the SerialGC is used, and the option
40+
* SerialGCOptions.IgnoreMaxHeapSizeWhileInVMInternalCode is enabled.
41+
*/
42+
public class SuspendSerialGCMaxHeapSize {
43+
private static final FastThreadLocalInt nestingDepth = FastThreadLocalFactory.createInt("SuspendSerialGCMaxHeapSize.nestingDepth");
44+
45+
/**
46+
* Temporarily suspend the heap limit for the current thread. Must be paired with a call to
47+
* {@link #resumeInCurrentThread}, best placed in a {@code finally} block. This method may be
48+
* called multiple times in a nested fashion.
49+
*/
50+
@Uninterruptible(reason = "Called from code that must not allocate before suspending the heap limit.", callerMustBe = true)
51+
public static void suspendInCurrentThread() {
52+
int oldValue = nestingDepth.get();
53+
int newValue = oldValue + 1;
54+
assert oldValue >= 0;
55+
nestingDepth.set(newValue);
56+
}
57+
58+
/**
59+
* Undoes suspending the heap limit for the current thread. This may only be called after a call
60+
* to {@link #suspendInCurrentThread}.
61+
*/
62+
@Uninterruptible(reason = "Called from code that must not allocate after resuming the heap limit.", callerMustBe = true)
63+
public static void resumeInCurrentThread() {
64+
int oldValue = nestingDepth.get();
65+
int newValue = oldValue - 1;
66+
assert newValue >= 0;
67+
nestingDepth.set(newValue);
68+
}
69+
70+
/**
71+
* Returns true if the heap limit is currently suspended.
72+
*/
73+
@Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true)
74+
public static boolean isSuspended() {
75+
return nestingDepth.get() > 0;
76+
}
77+
}

0 commit comments

Comments
 (0)