Skip to content

Commit 8978fe3

Browse files
Fixes and improvements for -XX:+HeapDumpOnOutOfMemoryError.
1 parent c264f67 commit 8978fe3

File tree

16 files changed

+264
-187
lines changed

16 files changed

+264
-187
lines changed

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

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +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

1213
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.
18-
5. If `-XX:+HeapDumpOnOutOfMemoryError` is passed in at runtime and an `OutOfMemoryError` is encountered, a heap dump will be produced.
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.
1920

2021
All approaches are described below.
2122

@@ -31,6 +32,18 @@ For this, you need to add `jvmstat` to the `--enable-monitoring` option (for exa
3132
This will allow VisualVM to pick up and list running Native Image processes.
3233
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").
3334

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+
Exception in thread "main" java.lang.OutOfMemoryError: Garbage-collected heap size exceeded.
45+
```
46+
3447
## Dump the Initial Heap of a Native Executable
3548

3649
Use the `-XX:+DumpHeapAndExit` command-line option to dump the initial heap of a native executable.
@@ -96,6 +109,7 @@ For other installation options, visit the [Downloads section](https://www.graalv
96109
for (int i = 0; i < 1000; i++) {
97110
CROWD.add(new Person());
98111
}
112+
99113
long pid = ProcessProperties.getProcessID();
100114
StringBuffer sb1 = new StringBuffer(100);
101115
sb1.append(DATE_FORMATTER.format(new Date()));
@@ -106,6 +120,7 @@ For other installation options, visit the [Downloads section](https://www.graalv
106120
sb1.append("to dump the heap into the working directory.\n");
107121
sb1.append("Starting thread!");
108122
System.out.println(sb1);
123+
109124
SVMHeapDump t = new SVMHeapDump();
110125
t.start();
111126
while (t.isAlive()) {
@@ -121,17 +136,17 @@ For other installation options, visit the [Downloads section](https://www.graalv
121136
}
122137

123138
class Person {
124-
private static Random R = new Random();
125-
private String name;
126-
private int age;
139+
private static Random R = new Random();
140+
private String name;
141+
private int age;
127142

128-
public Person() {
129-
byte[] array = new byte[7];
130-
R.nextBytes(array);
131-
name = new String(array, Charset.forName("UTF-8"));
132-
age = R.nextInt(100);
133-
}
143+
public Person() {
144+
byte[] array = new byte[7];
145+
R.nextBytes(array);
146+
name = new String(array, Charset.forName("UTF-8"));
147+
age = R.nextInt(100);
134148
}
149+
}
135150
```
136151

137152
3. Build a native executable:
@@ -276,18 +291,6 @@ The condition to create a heap dump is provided as an option on the command line
276291

277292
![Native Image Heap Dump View in VisualVM](img/heap-dump-api.png)
278293

279-
## Create a Heap Dump on `OutOfMemoryError`
280-
281-
Start the application with `-XX:+HeapDumpOnOutOfMemoryError` option to get a heap dump when an `OutOfMemoryError` occurs.
282-
The generated heap dump file name will have the `svm-heapdump-<PID>.hprof` naming format.
283-
For example:
284-
285-
```shell
286-
./example -XX:+HeapDumpOnOutOfMemoryError
287-
Dumping heap to svm-heapdump-67799.hprof ...
288-
Exception in thread "main" java.lang.OutOfMemoryError: Garbage-collected heap size exceeded.
289-
```
290-
291294
### Related Documentation
292295

293296
* [Debugging and Diagnostics](../DebuggingAndDiagnostics.md)

sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/VMRuntime.java

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -92,20 +92,6 @@ public static void dumpHeap(String outputFile, boolean live) throws IOException
9292
ImageSingletons.lookup(HeapDumpSupport.class).dumpHeap(outputFile, live);
9393
}
9494

95-
/**
96-
* Dumps the heap to a predetermined file in case of an @{@link OutOfMemoryError}, in the same format as the hprof heap dump.
97-
*
98-
* @throws UnsupportedOperationException if this operation is not supported.
99-
*
100-
* @since 23.1
101-
*/
102-
public static void dumpHeapOnOutOfMemoryError() {
103-
if (!ImageSingletons.contains(HeapDumpSupport.class)) {
104-
throw new UnsupportedOperationException();
105-
}
106-
ImageSingletons.lookup(HeapDumpSupport.class).dumpHeapOnOutOfMemoryError();
107-
}
108-
10995
private VMRuntime() {
11096
}
11197
}

sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/HeapDumpSupport.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,4 @@ public interface HeapDumpSupport {
4444

4545
void dumpHeap(String outputFile, boolean live) throws java.io.IOException;
4646

47-
void dumpHeapOnOutOfMemoryError();
48-
49-
void initHeapDumpOnOutOfMemoryErrorPath();
5047
}

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

1415
## Version 23.0.0
1516
* (GR-40187) Report invalid use of SVM specific classes on image class- or module-path as error. As a temporary workaround, `-H:+AllowDeprecatedBuilderClassesOnImageClasspath` allows turning the error into a warning.

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

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,12 @@
2929

3030
import java.lang.ref.Reference;
3131

32-
import com.oracle.svm.core.jdk.UninterruptibleUtils.AtomicBoolean;
3332
import org.graalvm.compiler.api.replacements.Fold;
3433
import org.graalvm.nativeimage.CurrentIsolate;
3534
import org.graalvm.nativeimage.IsolateThread;
3635
import org.graalvm.nativeimage.Platform;
3736
import org.graalvm.nativeimage.Platforms;
3837
import org.graalvm.nativeimage.StackValue;
39-
import org.graalvm.nativeimage.VMRuntime;
4038
import org.graalvm.nativeimage.c.function.CodePointer;
4139
import org.graalvm.nativeimage.c.struct.RawField;
4240
import org.graalvm.nativeimage.c.struct.RawStructure;
@@ -118,8 +116,6 @@ public final class GCImpl implements GC {
118116
private UnsignedWord collectionEpoch = WordFactory.zero();
119117
private long lastWholeHeapExaminedTimeMillis = -1;
120118

121-
private final AtomicBoolean outOfMemoryReported = new AtomicBoolean(false);
122-
123119
@Platforms(Platform.HOSTED_ONLY.class)
124120
GCImpl() {
125121
this.policy = CollectionPolicy.getInitialPolicy();
@@ -154,17 +150,10 @@ public void maybeCollectOnAllocation() {
154150
outOfMemory = collectWithoutAllocating(GenScavengeGCCause.OnAllocation, false);
155151
}
156152
if (outOfMemory) {
157-
reportJavaOutOfMemory();
158153
throw OutOfMemoryUtil.heapSizeExceeded();
159154
}
160155
}
161156

162-
private void reportJavaOutOfMemory() {
163-
if (SubstrateOptions.isHeapDumpOnOutOfMemoryError() && outOfMemoryReported.compareAndSet(false, true)) {
164-
VMRuntime.dumpHeapOnOutOfMemoryError();
165-
}
166-
}
167-
168157
@Override
169158
public void maybeCauseUserRequestedCollection(GCCause cause, boolean fullGC) {
170159
if (policy.shouldCollectOnRequest(cause, fullGC)) {
@@ -176,7 +165,6 @@ private void collect(GCCause cause, boolean forceFullGC) {
176165
if (!hasNeverCollectPolicy()) {
177166
boolean outOfMemory = collectWithoutAllocating(cause, forceFullGC);
178167
if (outOfMemory) {
179-
reportJavaOutOfMemory();
180168
throw OutOfMemoryUtil.heapSizeExceeded();
181169
}
182170
}

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

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.graalvm.nativeimage.Platforms;
3333
import org.graalvm.nativeimage.c.type.CCharPointer;
3434
import org.graalvm.nativeimage.c.type.CTypeConversion;
35+
import org.graalvm.nativeimage.impl.UnmanagedMemorySupport;
3536
import org.graalvm.word.Pointer;
3637
import org.graalvm.word.SignedWord;
3738
import org.graalvm.word.UnsignedWord;
@@ -54,6 +55,21 @@ public PosixRawFileOperationSupport(boolean useNativeByteOrder) {
5455
super(useNativeByteOrder);
5556
}
5657

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+
5773
@Override
5874
public RawFileDescriptor create(File file, FileCreationMode creationMode, FileAccessMode accessMode) {
5975
String path = file.getPath();
@@ -62,16 +78,12 @@ public RawFileDescriptor create(File file, FileCreationMode creationMode, FileAc
6278
}
6379

6480
@Override
81+
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
6582
public RawFileDescriptor create(CCharPointer cPath, FileCreationMode creationMode, FileAccessMode accessMode) {
6683
int flags = parseMode(creationMode) | parseMode(accessMode);
6784
return open0(cPath, flags);
6885
}
6986

70-
private static RawFileDescriptor open0(CCharPointer cPath, int flags) {
71-
int permissions = PosixStat.S_IRUSR() | PosixStat.S_IWUSR();
72-
return WordFactory.signed(Fcntl.NoTransitions.open(cPath, flags, permissions));
73-
}
74-
7587
@Override
7688
public RawFileDescriptor open(File file, FileAccessMode mode) {
7789
String path = file.getPath();
@@ -80,18 +92,24 @@ public RawFileDescriptor open(File file, FileAccessMode mode) {
8092
}
8193

8294
@Override
95+
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
8396
public RawFileDescriptor open(CCharPointer cPath, FileAccessMode mode) {
8497
int flags = parseMode(mode);
8598
return open0(cPath, flags);
8699
}
87100

88101
private static RawFileDescriptor open0(String path, int flags) {
89-
int permissions = PosixStat.S_IRUSR() | PosixStat.S_IWUSR();
90102
try (CTypeConversion.CCharPointerHolder cPath = CTypeConversion.toCString(path)) {
91-
return WordFactory.signed(Fcntl.NoTransitions.open(cPath.get(), flags, permissions));
103+
return open0(cPath.get(), flags);
92104
}
93105
}
94106

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+
95113
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
96114
@Override
97115
public boolean isValid(RawFileDescriptor fd) {
@@ -172,6 +190,7 @@ private static int getPosixFileDescriptor(RawFileDescriptor fd) {
172190
return result;
173191
}
174192

193+
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
175194
private static int parseMode(FileCreationMode mode) {
176195
switch (mode) {
177196
case CREATE:
@@ -183,6 +202,7 @@ private static int parseMode(FileCreationMode mode) {
183202
}
184203
}
185204

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

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

Lines changed: 20 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -25,64 +25,57 @@
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.ImageSingletons;
34-
import org.graalvm.nativeimage.ProcessProperties;
35-
import org.graalvm.nativeimage.VMRuntime;
3628

3729
import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature;
3830
import com.oracle.svm.core.feature.InternalFeature;
31+
import com.oracle.svm.core.heap.dump.HeapDumping;
3932
import com.oracle.svm.core.jdk.RuntimeSupport;
4033
import com.oracle.svm.core.log.Log;
4134

4235
import jdk.internal.misc.Signal;
43-
import org.graalvm.nativeimage.impl.HeapDumpSupport;
4436

4537
@AutomaticallyRegisteredFeature
4638
public class DumpHeapOnSignalFeature implements InternalFeature {
47-
48-
@Override
49-
public boolean isInConfiguration(IsInConfigurationAccess access) {
50-
return VMInspectionOptions.hasHeapDumpSupport();
51-
}
52-
5339
@Override
5440
public void beforeAnalysis(BeforeAnalysisAccess access) {
55-
RuntimeSupport.getRuntimeSupport().addStartupHook(new DumpHeapStartupHook());
41+
if (HeapDumping.isSupported()) {
42+
RuntimeSupport.getRuntimeSupport().addInitializationHook(new DumpHeapStartupHook());
43+
RuntimeSupport.getRuntimeSupport().addTearDownHook(new DumpHeapTeardownHook());
44+
}
5645
}
5746
}
5847

5948
final class DumpHeapStartupHook implements RuntimeSupport.Hook {
6049
@Override
6150
public void execute(boolean isFirstIsolate) {
62-
if (isFirstIsolate && SubstrateOptions.EnableSignalHandling.getValue()) {
63-
DumpHeapReport.install();
64-
if (SubstrateOptions.isHeapDumpOnOutOfMemoryError()) {
65-
ImageSingletons.lookup(HeapDumpSupport.class).initHeapDumpOnOutOfMemoryErrorPath();
51+
if (HeapDumping.isSupported()) {
52+
if (VMInspectionOptions.useHeapDumpSignalHandler() && isFirstIsolate && SubstrateOptions.EnableSignalHandling.getValue()) {
53+
DumpHeapReport.install();
6654
}
55+
56+
HeapDumping.singleton().initialize();
6757
}
6858
}
6959
}
7060

71-
class DumpHeapReport implements Signal.Handler {
72-
private static final TimeZone UTC_TIMEZONE = TimeZone.getTimeZone("UTC");
61+
final class DumpHeapTeardownHook implements RuntimeSupport.Hook {
62+
@Override
63+
public void execute(boolean isFirstIsolate) {
64+
if (HeapDumping.isSupported()) {
65+
HeapDumping.singleton().teardown();
66+
}
67+
}
68+
}
7369

70+
class DumpHeapReport implements Signal.Handler {
7471
static void install() {
7572
Signal.handle(new Signal("USR1"), new DumpHeapReport());
7673
}
7774

7875
@Override
7976
public void handle(Signal arg0) {
80-
DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
81-
dateFormat.setTimeZone(UTC_TIMEZONE);
82-
String defaultHeapDumpFileName = "svm-heapdump-" + ProcessProperties.getProcessID() + "-" + dateFormat.format(new Date()) + ".hprof";
83-
String heapDumpPath = SubstrateOptions.getHeapDumpPath(defaultHeapDumpFileName);
8477
try {
85-
VMRuntime.dumpHeap(heapDumpPath, true);
78+
HeapDumping.singleton().dumpHeap(true);
8679
} catch (IOException e) {
8780
Log.log().string("IOException during dumpHeap: ").string(e.getMessage()).newline();
8881
}

0 commit comments

Comments
 (0)