Skip to content

Commit b3d7ca9

Browse files
author
Christian Wimmer
committed
[GR-43620] Improve OutOfMemoryError handling.
PullRequest: graal/13640
2 parents 412f738 + baa0089 commit b3d7ca9

File tree

5 files changed

+95
-5
lines changed

5 files changed

+95
-5
lines changed

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/ExitStatus.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,11 @@ public enum ExitStatus {
2929
OK(0),
3030
BUILDER_ERROR(1),
3131
FALLBACK_IMAGE(2),
32-
BUILDER_INTERRUPT_WITHOUT_REASON(3),
32+
33+
// 3 used by `-XX:+ExitOnOutOfMemoryError` (see src/hotspot/share/utilities/debug.cpp)
34+
OUT_OF_MEMORY(3),
35+
36+
BUILDER_INTERRUPT_WITHOUT_REASON(4),
3337
DRIVER_ERROR(20),
3438
DRIVER_TO_BUILDER_ERROR(21),
3539
WATCHDOG_EXIT(30),

substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2016, 2023, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -804,7 +804,11 @@ private void prepareImageBuildArgs() {
804804
if (!"0".equals(xmxVal)) {
805805
addImageBuilderJavaArgs(oXmx + xmxVal);
806806
}
807-
/* Prevent JVM that runs the image builder to steal focus */
807+
808+
/* Let builder exit on first OutOfMemoryError. */
809+
addImageBuilderJavaArgs("-XX:+ExitOnOutOfMemoryError");
810+
811+
/* Prevent JVM that runs the image builder to steal focus. */
808812
addImageBuilderJavaArgs("-Djava.awt.headless=true");
809813
addImageBuilderJavaArgs("-Dorg.graalvm.version=" + graalvmVersion);
810814
addImageBuilderJavaArgs("-Dorg.graalvm.vendor=" + graalvmVendor);
@@ -1515,7 +1519,7 @@ protected static void build(BuildConfiguration config, Function<BuildConfigurati
15151519
break;
15161520
case BUILDER_ERROR:
15171521
/* Exit, builder has handled error reporting. */
1518-
System.exit(ExitStatus.BUILDER_ERROR.getValue());
1522+
System.exit(exitStatusCode);
15191523
break;
15201524
case FALLBACK_IMAGE:
15211525
nativeImage.showMessage("Generating fallback image...");
@@ -1525,6 +1529,10 @@ protected static void build(BuildConfiguration config, Function<BuildConfigurati
15251529
"(use --" + SubstrateOptions.OptionNameNoFallback +
15261530
" to suppress fallback image generation and to print more detailed information why a fallback image was necessary).");
15271531
break;
1532+
case OUT_OF_MEMORY:
1533+
nativeImage.showOutOfMemoryWarning();
1534+
System.exit(exitStatusCode);
1535+
break;
15281536
default:
15291537
String message = String.format("Image build request for '%s' (pid: %d, path: %s) failed with exit status %d",
15301538
nativeImage.imageName, nativeImage.imageBuilderPid, nativeImage.imagePath, exitStatusCode);
@@ -1838,6 +1846,19 @@ void showMessagePart(String message) {
18381846
}, message);
18391847
}
18401848

1849+
void showOutOfMemoryWarning() {
1850+
String lastMaxHeapValue = null;
1851+
for (String arg : imageBuilderJavaArgs) {
1852+
if (arg.startsWith(oXmx)) {
1853+
lastMaxHeapValue = arg.substring(oXmx.length());
1854+
}
1855+
}
1856+
String maxHeapText = lastMaxHeapValue == null ? "" : " (The maximum heap size of the process was set to '" + lastMaxHeapValue + "'.)";
1857+
String additionalAction = lastMaxHeapValue == null ? "" : " or increase the maximum heap size using the '" + oXmx + "' option";
1858+
showMessage(String.format("The Native Image build process ran out of memory.%s%nPlease make sure your build system has more memory available%s.",
1859+
maxHeapText, additionalAction));
1860+
}
1861+
18411862
public static void showWarning(String message) {
18421863
show(System.err::println, "Warning: " + message);
18431864
}

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/EarlyClassInitializerAnalysis.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,17 @@
5151
import org.graalvm.compiler.nodes.graphbuilderconf.InlineInvokePlugin;
5252
import org.graalvm.compiler.nodes.graphbuilderconf.InvocationPlugins;
5353
import org.graalvm.compiler.nodes.java.AccessFieldNode;
54+
import org.graalvm.compiler.nodes.java.NewArrayNode;
55+
import org.graalvm.compiler.nodes.java.NewMultiArrayNode;
5456
import org.graalvm.compiler.options.OptionValues;
5557
import org.graalvm.compiler.phases.OptimisticOptimizations;
5658
import org.graalvm.compiler.phases.tiers.HighTierContext;
5759
import org.graalvm.compiler.phases.util.Providers;
5860
import org.graalvm.compiler.serviceprovider.JavaVersionUtil;
61+
import org.graalvm.nativeimage.ImageSingletons;
62+
import org.graalvm.nativeimage.impl.RuntimeClassInitializationSupport;
5963

64+
import com.oracle.graal.pointsto.infrastructure.OriginalClassProvider;
6065
import com.oracle.graal.pointsto.phases.NoClassInitializationPlugin;
6166
import com.oracle.graal.pointsto.util.GraalAccess;
6267
import com.oracle.svm.core.ParsingReason;
@@ -68,6 +73,7 @@
6873
import com.oracle.svm.hosted.snippets.ReflectionPlugins;
6974
import com.oracle.svm.hosted.snippets.SubstrateGraphBuilderPlugins;
7075

76+
import jdk.vm.ci.meta.JavaConstant;
7177
import jdk.vm.ci.meta.ResolvedJavaField;
7278
import jdk.vm.ci.meta.ResolvedJavaMethod;
7379
import jdk.vm.ci.meta.ResolvedJavaType;
@@ -276,6 +282,31 @@ public void nodeAdded(Node node) {
276282
throw new ClassInitializerHasSideEffectsException("Access of thread-local value");
277283
} else if (node instanceof UnsafeAccessNode) {
278284
throw VMError.shouldNotReachHere("Intrinsification of Unsafe methods is not enabled during bytecode parsing");
285+
286+
} else if (node instanceof NewArrayNode) {
287+
checkArrayAllocationLength(((NewArrayNode) node).length());
288+
} else if (node instanceof NewMultiArrayNode) {
289+
var dimensions = ((NewMultiArrayNode) node).dimensions();
290+
for (var dimension : dimensions) {
291+
checkArrayAllocationLength(dimension);
292+
}
293+
}
294+
}
295+
296+
private static void checkArrayAllocationLength(ValueNode lengthNode) {
297+
JavaConstant lengthConstant = lengthNode.asJavaConstant();
298+
if (lengthConstant != null) {
299+
int length = lengthConstant.asInt();
300+
if (length < 0 || length > 100_000) {
301+
/*
302+
* Ensure that also the late class initialization after static analysis does not
303+
* attempt to initialize.
304+
*/
305+
Class<?> clazz = OriginalClassProvider.getJavaClass(lengthNode.graph().method().getDeclaringClass());
306+
((ProvenSafeClassInitializationSupport) ImageSingletons.lookup(RuntimeClassInitializationSupport.class)).mustNotBeProvenSafe.add(clazz);
307+
308+
throw new ClassInitializerHasSideEffectsException("Allocation of too large array in class initializer");
309+
}
279310
}
280311
}
281312
}

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ProvenSafeClassInitializationSupport.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ class ProvenSafeClassInitializationSupport extends ClassInitializationSupport {
6464
private final EarlyClassInitializerAnalysis earlyClassInitializerAnalysis;
6565
private final Set<Class<?>> provenSafeEarly = ConcurrentHashMap.newKeySet();
6666
private Set<Class<?>> provenSafeLate = ConcurrentHashMap.newKeySet();
67+
final Set<Class<?>> mustNotBeProvenSafe = ConcurrentHashMap.newKeySet();
6768

6869
ProvenSafeClassInitializationSupport(MetaAccessProvider metaAccess, ImageClassLoader loader) {
6970
super(metaAccess, loader);
@@ -76,6 +77,9 @@ InitKind computeInitKindAndMaybeInitializeClass(Class<?> clazz) {
7677
}
7778

7879
boolean canBeProvenSafe(Class<?> clazz) {
80+
if (mustNotBeProvenSafe.contains(clazz)) {
81+
return false;
82+
}
7983
InitKind initKind = specifiedInitKindFor(clazz);
8084
return initKind == null || (initKind.isRunTime() && !isStrictlyDefined(clazz));
8185
}

substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/clinit/TestClassInitializationMustBeSafeEarly.java

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,26 @@ private static DevirtualizedCallSuperMustBeSafeEarly createProvider() {
447447
}
448448
}
449449

450+
class LargeAllocation1MustBeDelayed {
451+
static final Object value = computeValue();
452+
453+
private static Object computeValue() {
454+
Object[] result = new Object[1_000_000];
455+
for (int i = 0; i < result.length; i++) {
456+
result[i] = new int[1_000_000];
457+
}
458+
return result;
459+
}
460+
}
461+
462+
class LargeAllocation2MustBeDelayed {
463+
static final Object value = computeValue();
464+
465+
private static Object computeValue() {
466+
return new int[Integer.MAX_VALUE][Integer.MAX_VALUE];
467+
}
468+
}
469+
450470
class TestClassInitializationMustBeSafeEarlyFeature implements Feature {
451471

452472
static final Class<?>[] checkedClasses = new Class<?>[]{
@@ -479,7 +499,8 @@ class TestClassInitializationMustBeSafeEarlyFeature implements Feature {
479499
ReferencesOtherPureClassMustBeSafeEarly.class, HelperClassMustBeSafeEarly.class,
480500
CycleMustBeSafeLate.class, HelperClassMustBeSafeLate.class,
481501
ReflectionMustBeSafeEarly.class, ForNameMustBeSafeEarly.class, ForNameMustBeDelayed.class,
482-
DevirtualizedCallMustBeDelayed.class, DevirtualizedCallSuperMustBeSafeEarly.class, DevirtualizedCallSubMustBeSafeEarly.class, DevirtualizedCallUsageMustBeDelayed.class
502+
DevirtualizedCallMustBeDelayed.class, DevirtualizedCallSuperMustBeSafeEarly.class, DevirtualizedCallSubMustBeSafeEarly.class, DevirtualizedCallUsageMustBeDelayed.class,
503+
LargeAllocation1MustBeDelayed.class, LargeAllocation2MustBeDelayed.class,
483504
};
484505

485506
private static void checkClasses(boolean checkSafeEarly, boolean checkSafeLate) {
@@ -660,6 +681,15 @@ public static void main(String[] args) {
660681
assertSame("field", ReflectionMustBeSafeEarly.f2.getName());
661682

662683
System.out.println(DevirtualizedCallUsageMustBeDelayed.value);
684+
685+
if (System.currentTimeMillis() == 0) {
686+
/*
687+
* Make the class initializers reachable at run time, but do not actually execute them
688+
* because they will allocate a lot of memory before throwing an OutOfMemoryError.
689+
*/
690+
System.out.println(LargeAllocation1MustBeDelayed.value);
691+
System.out.println(LargeAllocation2MustBeDelayed.value);
692+
}
663693
}
664694

665695
private static void assertSame(Object expected, Object actual) {

0 commit comments

Comments
 (0)