Skip to content

Commit a78f70c

Browse files
[GR-38994] Add support for -XX:HeapDumpOnOutOfMemoryError.
PullRequest: graal/15133
2 parents 21c0112 + 1cba523 commit a78f70c

File tree

13 files changed

+292
-67
lines changed

13 files changed

+292
-67
lines changed

docs/reference-manual/native-image/guides/create-heap-dump-from-native-executable.md

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,16 @@ permalink: /reference-manual/native-image/guides/create-heap-dump/
77

88
# Create a Heap Dump from a Native Executable
99

10-
You can create a heap dump of a running executable to monitor its execution. Just like any other Java heap dump, it can be opened with the [VisualVM](../../../tools/visualvm.md) tool.
10+
You can create a heap dump of a running executable to monitor its execution.
11+
Just like any other Java heap dump, it can be opened with the [VisualVM](../../../tools/visualvm.md) tool.
1112

12-
To enable heap dump support, native executables must be built with the `--enable-monitoring=heapdump` option. Heap dumps can then be created in three different ways:
13+
To enable heap dump support, native executables must be built with the `--enable-monitoring=heapdump` option. Heap dumps can then be created in different ways:
1314

1415
1. Create heap dumps with VisualVM.
15-
2. Dump the initial heap of a native executable using the `-XX:+DumpHeapAndExit` command-line option.
16-
3. Create heap dumps sending a `SIGUSR1` signal at run time.
17-
4. Create heap dumps programmatically using the [`org.graalvm.nativeimage.VMRuntime#dumpHeap`](https://github.com/oracle/graal/blob/master/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/VMInspectionOptions.java) API.
16+
2. The command-line option `-XX:+HeapDumpOnOutOfMemoryError` can be used to create a heap dump when the native executable runs out of Java heap memory.
17+
3. Dump the initial heap of a native executable using the `-XX:+DumpHeapAndExit` command-line option.
18+
4. Create heap dumps sending a `SIGUSR1` signal at run time.
19+
5. Create heap dumps programmatically using the [`org.graalvm.nativeimage.VMRuntime#dumpHeap`](https://github.com/oracle/graal/blob/master/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/VMInspectionOptions.java) API.
1820

1921
All approaches are described below.
2022

@@ -30,6 +32,19 @@ For this, you need to add `jvmstat` to the `--enable-monitoring` option (for exa
3032
This will allow VisualVM to pick up and list running Native Image processes.
3133
You can then request heap dumps in the same way you can request them when your application runs on the JVM (for example, right-click on the process, then select "Heap Dump").
3234

35+
## Create a Heap Dump on `OutOfMemoryError`
36+
37+
Start the application with the option `-XX:+HeapDumpOnOutOfMemoryError` to get a heap dump when the native executable throws an `OutOfMemoryError` because it ran out of Java heap memory.
38+
The heap dump is created in a file named `svm-heapdump-<PID>-OOME.hprof`.
39+
For example:
40+
41+
```shell
42+
./mem-leak-example -XX:+HeapDumpOnOutOfMemoryError
43+
Dumping heap to svm-heapdump-67799-OOME.hprof ...
44+
Heap dump file created [10046752 bytes in 0.49 secs]
45+
Exception in thread "main" java.lang.OutOfMemoryError: Garbage-collected heap size exceeded.
46+
```
47+
3348
## Dump the Initial Heap of a Native Executable
3449

3550
Use the `-XX:+DumpHeapAndExit` command-line option to dump the initial heap of a native executable.
@@ -95,6 +110,7 @@ For other installation options, visit the [Downloads section](https://www.graalv
95110
for (int i = 0; i < 1000; i++) {
96111
CROWD.add(new Person());
97112
}
113+
98114
long pid = ProcessProperties.getProcessID();
99115
StringBuffer sb1 = new StringBuffer(100);
100116
sb1.append(DATE_FORMATTER.format(new Date()));
@@ -105,6 +121,7 @@ For other installation options, visit the [Downloads section](https://www.graalv
105121
sb1.append("to dump the heap into the working directory.\n");
106122
sb1.append("Starting thread!");
107123
System.out.println(sb1);
124+
108125
SVMHeapDump t = new SVMHeapDump();
109126
t.start();
110127
while (t.isAlive()) {
@@ -120,17 +137,17 @@ For other installation options, visit the [Downloads section](https://www.graalv
120137
}
121138

122139
class Person {
123-
private static Random R = new Random();
124-
private String name;
125-
private int age;
140+
private static Random R = new Random();
141+
private String name;
142+
private int age;
126143

127-
public Person() {
128-
byte[] array = new byte[7];
129-
R.nextBytes(array);
130-
name = new String(array, Charset.forName("UTF-8"));
131-
age = R.nextInt(100);
132-
}
144+
public Person() {
145+
byte[] array = new byte[7];
146+
R.nextBytes(array);
147+
name = new String(array, Charset.forName("UTF-8"));
148+
age = R.nextInt(100);
133149
}
150+
}
134151
```
135152

136153
3. Build a native executable:

substratevm/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ This changelog summarizes major changes to GraalVM Native Image.
1010
* (GR-39406) All classes can now be used at image build time, even when they are not explicitly configured as `--initialize-at-build-time`. Note, however, that still only classes configured as `--initialize-at-build-time` are allowed in the image heap.
1111
* (GR-46392) Add `--parallelism` option to control how many threads are used by the build process.
1212
* (GR-46392) Add build resources section to the build output that shows the memory and thread limits of the build process.
13+
* (GR-38994) Together with Red Hat, we added support for `-XX:+HeapDumpOnOutOfMemoryError`.
1314
* (GR-47365) Throw `MissingReflectionRegistrationError` when attempting to create a proxy class without having it registered at build-time, instead of a `VMError`.
1415

1516
## Version 23.0.0

substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixRawFileOperationSupport.java

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@
3030
import org.graalvm.nativeimage.ImageSingletons;
3131
import org.graalvm.nativeimage.Platform;
3232
import org.graalvm.nativeimage.Platforms;
33+
import org.graalvm.nativeimage.c.type.CCharPointer;
3334
import org.graalvm.nativeimage.c.type.CTypeConversion;
35+
import org.graalvm.nativeimage.impl.UnmanagedMemorySupport;
3436
import org.graalvm.word.Pointer;
3537
import org.graalvm.word.SignedWord;
3638
import org.graalvm.word.UnsignedWord;
@@ -53,27 +55,61 @@ public PosixRawFileOperationSupport(boolean useNativeByteOrder) {
5355
super(useNativeByteOrder);
5456
}
5557

58+
@Override
59+
public CCharPointer allocateCPath(String path) {
60+
byte[] data = path.getBytes();
61+
CCharPointer filename = ImageSingletons.lookup(UnmanagedMemorySupport.class).malloc(WordFactory.unsigned(data.length + 1));
62+
if (filename.isNull()) {
63+
return WordFactory.nullPointer();
64+
}
65+
66+
for (int i = 0; i < data.length; i++) {
67+
filename.write(i, data[i]);
68+
}
69+
filename.write(data.length, (byte) 0);
70+
return filename;
71+
}
72+
5673
@Override
5774
public RawFileDescriptor create(File file, FileCreationMode creationMode, FileAccessMode accessMode) {
5875
String path = file.getPath();
5976
int flags = parseMode(creationMode) | parseMode(accessMode);
6077
return open0(path, flags);
6178
}
6279

80+
@Override
81+
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
82+
public RawFileDescriptor create(CCharPointer cPath, FileCreationMode creationMode, FileAccessMode accessMode) {
83+
int flags = parseMode(creationMode) | parseMode(accessMode);
84+
return open0(cPath, flags);
85+
}
86+
6387
@Override
6488
public RawFileDescriptor open(File file, FileAccessMode mode) {
6589
String path = file.getPath();
6690
int flags = parseMode(mode);
6791
return open0(path, flags);
6892
}
6993

94+
@Override
95+
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
96+
public RawFileDescriptor open(CCharPointer cPath, FileAccessMode mode) {
97+
int flags = parseMode(mode);
98+
return open0(cPath, flags);
99+
}
100+
70101
private static RawFileDescriptor open0(String path, int flags) {
71-
int permissions = PosixStat.S_IRUSR() | PosixStat.S_IWUSR();
72102
try (CTypeConversion.CCharPointerHolder cPath = CTypeConversion.toCString(path)) {
73-
return WordFactory.signed(Fcntl.NoTransitions.open(cPath.get(), flags, permissions));
103+
return open0(cPath.get(), flags);
74104
}
75105
}
76106

107+
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
108+
private static RawFileDescriptor open0(CCharPointer cPath, int flags) {
109+
int permissions = PosixStat.S_IRUSR() | PosixStat.S_IWUSR();
110+
return WordFactory.signed(Fcntl.NoTransitions.open(cPath, flags, permissions));
111+
}
112+
77113
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
78114
@Override
79115
public boolean isValid(RawFileDescriptor fd) {
@@ -154,6 +190,7 @@ private static int getPosixFileDescriptor(RawFileDescriptor fd) {
154190
return result;
155191
}
156192

193+
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
157194
private static int parseMode(FileCreationMode mode) {
158195
switch (mode) {
159196
case CREATE:
@@ -165,6 +202,7 @@ private static int parseMode(FileCreationMode mode) {
165202
}
166203
}
167204

205+
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
168206
private static int parseMode(FileAccessMode mode) {
169207
switch (mode) {
170208
case READ:

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/DumpHeapOnSignalFeature.java

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,10 @@
2525
package com.oracle.svm.core;
2626

2727
import java.io.IOException;
28-
import java.text.DateFormat;
29-
import java.text.SimpleDateFormat;
30-
import java.util.Date;
31-
import java.util.TimeZone;
32-
33-
import org.graalvm.nativeimage.ProcessProperties;
34-
import org.graalvm.nativeimage.VMRuntime;
3528

3629
import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature;
3730
import com.oracle.svm.core.feature.InternalFeature;
31+
import com.oracle.svm.core.heap.dump.HeapDumping;
3832
import com.oracle.svm.core.jdk.RuntimeSupport;
3933
import com.oracle.svm.core.log.Log;
4034

@@ -50,7 +44,8 @@ public boolean isInConfiguration(IsInConfigurationAccess access) {
5044

5145
@Override
5246
public void beforeAnalysis(BeforeAnalysisAccess access) {
53-
RuntimeSupport.getRuntimeSupport().addStartupHook(new DumpHeapStartupHook());
47+
RuntimeSupport.getRuntimeSupport().addInitializationHook(new DumpHeapStartupHook());
48+
RuntimeSupport.getRuntimeSupport().addTearDownHook(new DumpHeapTeardownHook());
5449
}
5550
}
5651

@@ -60,24 +55,30 @@ public void execute(boolean isFirstIsolate) {
6055
if (isFirstIsolate && SubstrateOptions.EnableSignalHandling.getValue()) {
6156
DumpHeapReport.install();
6257
}
58+
59+
if (SubstrateOptions.HeapDumpOnOutOfMemoryError.getValue()) {
60+
HeapDumping.singleton().initializeDumpHeapOnOutOfMemoryError();
61+
}
6362
}
6463
}
6564

66-
class DumpHeapReport implements Signal.Handler {
67-
private static final TimeZone UTC_TIMEZONE = TimeZone.getTimeZone("UTC");
65+
final class DumpHeapTeardownHook implements RuntimeSupport.Hook {
66+
@Override
67+
public void execute(boolean isFirstIsolate) {
68+
/* Do this unconditionally, the runtime option could have changed in the meanwhile. */
69+
HeapDumping.singleton().teardownDumpHeapOnOutOfMemoryError();
70+
}
71+
}
6872

73+
class DumpHeapReport implements Signal.Handler {
6974
static void install() {
7075
Signal.handle(new Signal("USR1"), new DumpHeapReport());
7176
}
7277

7378
@Override
7479
public void handle(Signal arg0) {
75-
DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
76-
dateFormat.setTimeZone(UTC_TIMEZONE);
77-
String defaultHeapDumpFileName = "svm-heapdump-" + ProcessProperties.getProcessID() + "-" + dateFormat.format(new Date()) + ".hprof";
78-
String heapDumpPath = SubstrateOptions.getHeapDumpPath(defaultHeapDumpFileName);
7980
try {
80-
VMRuntime.dumpHeap(heapDumpPath, true);
81+
HeapDumping.singleton().dumpHeap(true);
8182
} catch (IOException e) {
8283
Log.log().string("IOException during dumpHeap: ").string(e.getMessage()).newline();
8384
}

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
import static org.graalvm.compiler.options.OptionType.Expert;
3232
import static org.graalvm.compiler.options.OptionType.User;
3333

34-
import java.nio.file.Files;
3534
import java.nio.file.InvalidPathException;
3635
import java.nio.file.Path;
3736
import java.nio.file.Paths;
@@ -825,21 +824,14 @@ public Boolean getValue(OptionValues values) {
825824
}
826825
};
827826

827+
@Option(help = "Dump heap to file (see HeapDumpPath) when the executable throws a java.lang.OutOfMemoryError because it ran out of Java heap.")//
828+
public static final RuntimeOptionKey<Boolean> HeapDumpOnOutOfMemoryError = new RuntimeOptionKey<>(false);
829+
828830
@Option(help = "The path (filename or directory) where heap dumps are created (defaults to the working directory).")//
829831
public static final RuntimeOptionKey<String> HeapDumpPath = new RuntimeOptionKey<>("", Immutable);
830832

831-
/* Utility method that follows the `-XX:HeapDumpPath` behavior of the JVM. */
832-
public static String getHeapDumpPath(String defaultFilename) {
833-
String heapDumpFilenameOrDirectory = HeapDumpPath.getValue();
834-
if (heapDumpFilenameOrDirectory.isEmpty()) {
835-
return defaultFilename;
836-
}
837-
var targetPath = Paths.get(heapDumpFilenameOrDirectory);
838-
if (Files.isDirectory(targetPath)) {
839-
targetPath = targetPath.resolve(defaultFilename);
840-
}
841-
return targetPath.toFile().getAbsolutePath();
842-
}
833+
@Option(help = "A prefix that is used for heap dump filenames if no heap dump filename was specified explicitly.")//
834+
public static final HostedOptionKey<String> HeapDumpDefaultFilenamePrefix = new HostedOptionKey<>("svm-heapdump-");
843835

844836
@Option(help = "Create a heap dump and exit.")//
845837
public static final RuntimeOptionKey<Boolean> DumpHeapAndExit = new RuntimeOptionKey<>(false, Immutable);

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/UnmanagedMemoryUtil.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
* </ul>
5252
* <p>
5353
* In some situations (e.g., during a serial GC or if it is guaranteed that all involved objects are
54-
* not yet visible to other threads), the methods in this class may also be used for objects the
54+
* not yet visible to other threads), the methods in this class may also be used for objects that
5555
* live in the Java heap. However, those usages should be kept to a minimum.
5656
*/
5757
public final class UnmanagedMemoryUtil {

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/VMInspectionOptions.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@
3737
import org.graalvm.nativeimage.Platform;
3838
import org.graalvm.nativeimage.Platform.WINDOWS;
3939
import org.graalvm.nativeimage.Platforms;
40-
import org.graalvm.nativeimage.VMRuntime;
4140

41+
import com.oracle.svm.core.heap.dump.HeapDumping;
4242
import com.oracle.svm.core.jdk.management.ManagementAgentModule;
4343
import com.oracle.svm.core.option.APIOption;
4444
import com.oracle.svm.core.option.HostedOptionKey;
@@ -109,9 +109,9 @@ public static boolean hasHeapDumpSupport() {
109109

110110
public static boolean dumpImageHeap() {
111111
if (hasHeapDumpSupport()) {
112-
String absoluteHeapDumpPath = SubstrateOptions.getHeapDumpPath(SubstrateOptions.Name.getValue() + ".hprof");
112+
String absoluteHeapDumpPath = HeapDumping.getHeapDumpPath(SubstrateOptions.Name.getValue() + ".hprof");
113113
try {
114-
VMRuntime.dumpHeap(absoluteHeapDumpPath, true);
114+
HeapDumping.singleton().dumpHeap(absoluteHeapDumpPath, true);
115115
} catch (IOException e) {
116116
System.err.println("Failed to create heap dump:");
117117
e.printStackTrace();

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/OutOfMemoryUtil.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,19 @@
2525
package com.oracle.svm.core.heap;
2626

2727
import com.oracle.svm.core.SubstrateGCOptions;
28+
import com.oracle.svm.core.SubstrateOptions;
2829
import com.oracle.svm.core.Uninterruptible;
30+
import com.oracle.svm.core.VMInspectionOptions;
2931
import com.oracle.svm.core.headers.LibC;
32+
import com.oracle.svm.core.heap.dump.HeapDumping;
3033
import com.oracle.svm.core.jdk.JDKUtils;
34+
import com.oracle.svm.core.jdk.UninterruptibleUtils.AtomicBoolean;
3135
import com.oracle.svm.core.log.Log;
3236
import com.oracle.svm.core.util.VMError;
3337

3438
public class OutOfMemoryUtil {
3539
private static final OutOfMemoryError OUT_OF_MEMORY_ERROR = new OutOfMemoryError("Garbage-collected heap size exceeded.");
40+
private static final AtomicBoolean HEAP_DUMPED = new AtomicBoolean(false);
3641

3742
@RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Can't allocate when out of memory.")
3843
public static OutOfMemoryError heapSizeExceeded() {
@@ -42,6 +47,10 @@ public static OutOfMemoryError heapSizeExceeded() {
4247
@Uninterruptible(reason = "Not uninterruptible but it doesn't matter for the callers.", calleeMustBe = false)
4348
@RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Can't allocate while out of memory.")
4449
public static OutOfMemoryError reportOutOfMemoryError(OutOfMemoryError error) {
50+
if (VMInspectionOptions.hasHeapDumpSupport() && SubstrateOptions.HeapDumpOnOutOfMemoryError.getValue() && HEAP_DUMPED.compareAndSet(false, true)) {
51+
HeapDumping.singleton().dumpHeapOnOutOfMemoryError();
52+
}
53+
4554
if (SubstrateGCOptions.ExitOnOutOfMemoryError.getValue()) {
4655
if (LibC.isSupported()) {
4756
Log.log().string("Terminating due to java.lang.OutOfMemoryError: ").string(JDKUtils.getRawMessage(error)).newline();

0 commit comments

Comments
 (0)