diff --git a/docs/reference-manual/native-image/guides/create-heap-dump-from-native-executable.md b/docs/reference-manual/native-image/guides/create-heap-dump-from-native-executable.md index 97be87ecafbc..dfd04e557551 100644 --- a/docs/reference-manual/native-image/guides/create-heap-dump-from-native-executable.md +++ b/docs/reference-manual/native-image/guides/create-heap-dump-from-native-executable.md @@ -7,14 +7,16 @@ permalink: /reference-manual/native-image/guides/create-heap-dump/ # Create a Heap Dump from a Native Executable -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. +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. -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: +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: 1. Create heap dumps with VisualVM. -2. Dump the initial heap of a native executable using the `-XX:+DumpHeapAndExit` command-line option. -3. Create heap dumps sending a `SIGUSR1` signal at run time. -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. +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. +3. Dump the initial heap of a native executable using the `-XX:+DumpHeapAndExit` command-line option. +4. Create heap dumps sending a `SIGUSR1` signal at run time. +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. All approaches are described below. @@ -30,6 +32,19 @@ For this, you need to add `jvmstat` to the `--enable-monitoring` option (for exa This will allow VisualVM to pick up and list running Native Image processes. 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"). +## Create a Heap Dump on `OutOfMemoryError` + +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. +The heap dump is created in a file named `svm-heapdump--OOME.hprof`. +For example: + +```shell +./mem-leak-example -XX:+HeapDumpOnOutOfMemoryError +Dumping heap to svm-heapdump-67799-OOME.hprof ... +Heap dump file created [10046752 bytes in 0.49 secs] +Exception in thread "main" java.lang.OutOfMemoryError: Garbage-collected heap size exceeded. +``` + ## Dump the Initial Heap of a Native Executable 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 for (int i = 0; i < 1000; i++) { CROWD.add(new Person()); } + long pid = ProcessProperties.getProcessID(); StringBuffer sb1 = new StringBuffer(100); sb1.append(DATE_FORMATTER.format(new Date())); @@ -105,6 +121,7 @@ For other installation options, visit the [Downloads section](https://www.graalv sb1.append("to dump the heap into the working directory.\n"); sb1.append("Starting thread!"); System.out.println(sb1); + SVMHeapDump t = new SVMHeapDump(); t.start(); while (t.isAlive()) { @@ -120,17 +137,17 @@ For other installation options, visit the [Downloads section](https://www.graalv } class Person { - private static Random R = new Random(); - private String name; - private int age; + private static Random R = new Random(); + private String name; + private int age; - public Person() { - byte[] array = new byte[7]; - R.nextBytes(array); - name = new String(array, Charset.forName("UTF-8")); - age = R.nextInt(100); - } + public Person() { + byte[] array = new byte[7]; + R.nextBytes(array); + name = new String(array, Charset.forName("UTF-8")); + age = R.nextInt(100); } + } ``` 3. Build a native executable: diff --git a/substratevm/CHANGELOG.md b/substratevm/CHANGELOG.md index 2cbfc4712ff6..bf4f33f707b5 100644 --- a/substratevm/CHANGELOG.md +++ b/substratevm/CHANGELOG.md @@ -10,6 +10,7 @@ This changelog summarizes major changes to GraalVM Native Image. * (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. * (GR-46392) Add `--parallelism` option to control how many threads are used by the build process. * (GR-46392) Add build resources section to the build output that shows the memory and thread limits of the build process. +* (GR-38994) Together with Red Hat, we added support for `-XX:+HeapDumpOnOutOfMemoryError`. * (GR-47365) Throw `MissingReflectionRegistrationError` when attempting to create a proxy class without having it registered at build-time, instead of a `VMError`. ## Version 23.0.0 diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixRawFileOperationSupport.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixRawFileOperationSupport.java index a17c5b5b09ec..b318b1b93cd9 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixRawFileOperationSupport.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixRawFileOperationSupport.java @@ -30,7 +30,9 @@ import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.c.type.CCharPointer; import org.graalvm.nativeimage.c.type.CTypeConversion; +import org.graalvm.nativeimage.impl.UnmanagedMemorySupport; import org.graalvm.word.Pointer; import org.graalvm.word.SignedWord; import org.graalvm.word.UnsignedWord; @@ -53,6 +55,21 @@ public PosixRawFileOperationSupport(boolean useNativeByteOrder) { super(useNativeByteOrder); } + @Override + public CCharPointer allocateCPath(String path) { + byte[] data = path.getBytes(); + CCharPointer filename = ImageSingletons.lookup(UnmanagedMemorySupport.class).malloc(WordFactory.unsigned(data.length + 1)); + if (filename.isNull()) { + return WordFactory.nullPointer(); + } + + for (int i = 0; i < data.length; i++) { + filename.write(i, data[i]); + } + filename.write(data.length, (byte) 0); + return filename; + } + @Override public RawFileDescriptor create(File file, FileCreationMode creationMode, FileAccessMode accessMode) { String path = file.getPath(); @@ -60,6 +77,13 @@ public RawFileDescriptor create(File file, FileCreationMode creationMode, FileAc return open0(path, flags); } + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public RawFileDescriptor create(CCharPointer cPath, FileCreationMode creationMode, FileAccessMode accessMode) { + int flags = parseMode(creationMode) | parseMode(accessMode); + return open0(cPath, flags); + } + @Override public RawFileDescriptor open(File file, FileAccessMode mode) { String path = file.getPath(); @@ -67,13 +91,25 @@ public RawFileDescriptor open(File file, FileAccessMode mode) { return open0(path, flags); } + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public RawFileDescriptor open(CCharPointer cPath, FileAccessMode mode) { + int flags = parseMode(mode); + return open0(cPath, flags); + } + private static RawFileDescriptor open0(String path, int flags) { - int permissions = PosixStat.S_IRUSR() | PosixStat.S_IWUSR(); try (CTypeConversion.CCharPointerHolder cPath = CTypeConversion.toCString(path)) { - return WordFactory.signed(Fcntl.NoTransitions.open(cPath.get(), flags, permissions)); + return open0(cPath.get(), flags); } } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private static RawFileDescriptor open0(CCharPointer cPath, int flags) { + int permissions = PosixStat.S_IRUSR() | PosixStat.S_IWUSR(); + return WordFactory.signed(Fcntl.NoTransitions.open(cPath, flags, permissions)); + } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @Override public boolean isValid(RawFileDescriptor fd) { @@ -154,6 +190,7 @@ private static int getPosixFileDescriptor(RawFileDescriptor fd) { return result; } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static int parseMode(FileCreationMode mode) { switch (mode) { case CREATE: @@ -165,6 +202,7 @@ private static int parseMode(FileCreationMode mode) { } } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private static int parseMode(FileAccessMode mode) { switch (mode) { case READ: diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/DumpHeapOnSignalFeature.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/DumpHeapOnSignalFeature.java index 212ae60d9c14..86ec2f20aaa5 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/DumpHeapOnSignalFeature.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/DumpHeapOnSignalFeature.java @@ -25,16 +25,10 @@ package com.oracle.svm.core; import java.io.IOException; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.TimeZone; - -import org.graalvm.nativeimage.ProcessProperties; -import org.graalvm.nativeimage.VMRuntime; import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.core.heap.dump.HeapDumping; import com.oracle.svm.core.jdk.RuntimeSupport; import com.oracle.svm.core.log.Log; @@ -50,7 +44,8 @@ public boolean isInConfiguration(IsInConfigurationAccess access) { @Override public void beforeAnalysis(BeforeAnalysisAccess access) { - RuntimeSupport.getRuntimeSupport().addStartupHook(new DumpHeapStartupHook()); + RuntimeSupport.getRuntimeSupport().addInitializationHook(new DumpHeapStartupHook()); + RuntimeSupport.getRuntimeSupport().addTearDownHook(new DumpHeapTeardownHook()); } } @@ -60,24 +55,30 @@ public void execute(boolean isFirstIsolate) { if (isFirstIsolate && SubstrateOptions.EnableSignalHandling.getValue()) { DumpHeapReport.install(); } + + if (SubstrateOptions.HeapDumpOnOutOfMemoryError.getValue()) { + HeapDumping.singleton().initializeDumpHeapOnOutOfMemoryError(); + } } } -class DumpHeapReport implements Signal.Handler { - private static final TimeZone UTC_TIMEZONE = TimeZone.getTimeZone("UTC"); +final class DumpHeapTeardownHook implements RuntimeSupport.Hook { + @Override + public void execute(boolean isFirstIsolate) { + /* Do this unconditionally, the runtime option could have changed in the meanwhile. */ + HeapDumping.singleton().teardownDumpHeapOnOutOfMemoryError(); + } +} +class DumpHeapReport implements Signal.Handler { static void install() { Signal.handle(new Signal("USR1"), new DumpHeapReport()); } @Override public void handle(Signal arg0) { - DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'"); - dateFormat.setTimeZone(UTC_TIMEZONE); - String defaultHeapDumpFileName = "svm-heapdump-" + ProcessProperties.getProcessID() + "-" + dateFormat.format(new Date()) + ".hprof"; - String heapDumpPath = SubstrateOptions.getHeapDumpPath(defaultHeapDumpFileName); try { - VMRuntime.dumpHeap(heapDumpPath, true); + HeapDumping.singleton().dumpHeap(true); } catch (IOException e) { Log.log().string("IOException during dumpHeap: ").string(e.getMessage()).newline(); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java index 23e0de2ea169..722d7b50c498 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java @@ -31,7 +31,6 @@ import static org.graalvm.compiler.options.OptionType.Expert; import static org.graalvm.compiler.options.OptionType.User; -import java.nio.file.Files; import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.nio.file.Paths; @@ -825,21 +824,14 @@ public Boolean getValue(OptionValues values) { } }; + @Option(help = "Dump heap to file (see HeapDumpPath) when the executable throws a java.lang.OutOfMemoryError because it ran out of Java heap.")// + public static final RuntimeOptionKey HeapDumpOnOutOfMemoryError = new RuntimeOptionKey<>(false); + @Option(help = "The path (filename or directory) where heap dumps are created (defaults to the working directory).")// public static final RuntimeOptionKey HeapDumpPath = new RuntimeOptionKey<>("", Immutable); - /* Utility method that follows the `-XX:HeapDumpPath` behavior of the JVM. */ - public static String getHeapDumpPath(String defaultFilename) { - String heapDumpFilenameOrDirectory = HeapDumpPath.getValue(); - if (heapDumpFilenameOrDirectory.isEmpty()) { - return defaultFilename; - } - var targetPath = Paths.get(heapDumpFilenameOrDirectory); - if (Files.isDirectory(targetPath)) { - targetPath = targetPath.resolve(defaultFilename); - } - return targetPath.toFile().getAbsolutePath(); - } + @Option(help = "A prefix that is used for heap dump filenames if no heap dump filename was specified explicitly.")// + public static final HostedOptionKey HeapDumpDefaultFilenamePrefix = new HostedOptionKey<>("svm-heapdump-"); @Option(help = "Create a heap dump and exit.")// public static final RuntimeOptionKey DumpHeapAndExit = new RuntimeOptionKey<>(false, Immutable); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/UnmanagedMemoryUtil.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/UnmanagedMemoryUtil.java index f6a6ac4b29de..ac7ed1797c47 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/UnmanagedMemoryUtil.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/UnmanagedMemoryUtil.java @@ -51,7 +51,7 @@ * *

* In some situations (e.g., during a serial GC or if it is guaranteed that all involved objects are - * not yet visible to other threads), the methods in this class may also be used for objects the + * not yet visible to other threads), the methods in this class may also be used for objects that * live in the Java heap. However, those usages should be kept to a minimum. */ public final class UnmanagedMemoryUtil { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/VMInspectionOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/VMInspectionOptions.java index fb344c4fa5a8..3fb496f4b0ca 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/VMInspectionOptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/VMInspectionOptions.java @@ -37,8 +37,8 @@ import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platform.WINDOWS; import org.graalvm.nativeimage.Platforms; -import org.graalvm.nativeimage.VMRuntime; +import com.oracle.svm.core.heap.dump.HeapDumping; import com.oracle.svm.core.jdk.management.ManagementAgentModule; import com.oracle.svm.core.option.APIOption; import com.oracle.svm.core.option.HostedOptionKey; @@ -109,9 +109,9 @@ public static boolean hasHeapDumpSupport() { public static boolean dumpImageHeap() { if (hasHeapDumpSupport()) { - String absoluteHeapDumpPath = SubstrateOptions.getHeapDumpPath(SubstrateOptions.Name.getValue() + ".hprof"); + String absoluteHeapDumpPath = HeapDumping.getHeapDumpPath(SubstrateOptions.Name.getValue() + ".hprof"); try { - VMRuntime.dumpHeap(absoluteHeapDumpPath, true); + HeapDumping.singleton().dumpHeap(absoluteHeapDumpPath, true); } catch (IOException e) { System.err.println("Failed to create heap dump:"); e.printStackTrace(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/OutOfMemoryUtil.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/OutOfMemoryUtil.java index 2daabb05d6ec..3874bd47d27d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/OutOfMemoryUtil.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/OutOfMemoryUtil.java @@ -25,14 +25,19 @@ package com.oracle.svm.core.heap; import com.oracle.svm.core.SubstrateGCOptions; +import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.VMInspectionOptions; import com.oracle.svm.core.headers.LibC; +import com.oracle.svm.core.heap.dump.HeapDumping; import com.oracle.svm.core.jdk.JDKUtils; +import com.oracle.svm.core.jdk.UninterruptibleUtils.AtomicBoolean; import com.oracle.svm.core.log.Log; import com.oracle.svm.core.util.VMError; public class OutOfMemoryUtil { private static final OutOfMemoryError OUT_OF_MEMORY_ERROR = new OutOfMemoryError("Garbage-collected heap size exceeded."); + private static final AtomicBoolean HEAP_DUMPED = new AtomicBoolean(false); @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Can't allocate when out of memory.") public static OutOfMemoryError heapSizeExceeded() { @@ -42,6 +47,10 @@ public static OutOfMemoryError heapSizeExceeded() { @Uninterruptible(reason = "Not uninterruptible but it doesn't matter for the callers.", calleeMustBe = false) @RestrictHeapAccess(access = RestrictHeapAccess.Access.NO_ALLOCATION, reason = "Can't allocate while out of memory.") public static OutOfMemoryError reportOutOfMemoryError(OutOfMemoryError error) { + if (VMInspectionOptions.hasHeapDumpSupport() && SubstrateOptions.HeapDumpOnOutOfMemoryError.getValue() && HEAP_DUMPED.compareAndSet(false, true)) { + HeapDumping.singleton().dumpHeapOnOutOfMemoryError(); + } + if (SubstrateGCOptions.ExitOnOutOfMemoryError.getValue()) { if (LibC.isSupported()) { Log.log().string("Terminating due to java.lang.OutOfMemoryError: ").string(JDKUtils.getRawMessage(error)).newline(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpSupportImpl.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpSupportImpl.java index 7641b3e75f68..e8dc0c4947a6 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpSupportImpl.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumpSupportImpl.java @@ -25,18 +25,19 @@ package com.oracle.svm.core.heap.dump; import static com.oracle.svm.core.heap.RestrictHeapAccess.Access.NO_ALLOCATION; -import static com.oracle.svm.core.heap.RestrictHeapAccess.Access.UNRESTRICTED; import java.io.IOException; import org.graalvm.compiler.api.replacements.Fold; +import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.StackValue; 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.impl.HeapDumpSupport; +import org.graalvm.nativeimage.c.type.CCharPointer; +import org.graalvm.nativeimage.impl.UnmanagedMemorySupport; import org.graalvm.word.Pointer; import org.graalvm.word.WordFactory; @@ -52,17 +53,62 @@ import com.oracle.svm.core.thread.NativeVMOperation; import com.oracle.svm.core.thread.NativeVMOperationData; import com.oracle.svm.core.thread.VMOperation; +import com.oracle.svm.core.util.TimeUtils; -public class HeapDumpSupportImpl implements HeapDumpSupport { +public class HeapDumpSupportImpl extends HeapDumping { private final HeapDumpWriter writer; private final HeapDumpOperation heapDumpOperation; + private CCharPointer outOfMemoryHeapDumpPath; + @Platforms(Platform.HOSTED_ONLY.class) public HeapDumpSupportImpl(HeapDumpMetadata metadata) { this.writer = new HeapDumpWriter(metadata); this.heapDumpOperation = new HeapDumpOperation(); } + @Override + public void initializeDumpHeapOnOutOfMemoryError() { + String defaultFilename = getDefaultHeapDumpFilename("OOME"); + String heapDumpPath = getHeapDumpPath(defaultFilename); + outOfMemoryHeapDumpPath = getFileSupport().allocateCPath(heapDumpPath); + } + + @Override + public void teardownDumpHeapOnOutOfMemoryError() { + ImageSingletons.lookup(UnmanagedMemorySupport.class).free(outOfMemoryHeapDumpPath); + outOfMemoryHeapDumpPath = WordFactory.nullPointer(); + } + + @Override + @RestrictHeapAccess(access = NO_ALLOCATION, reason = "OutOfMemoryError heap dumping must not allocate.") + public void dumpHeapOnOutOfMemoryError() { + if (outOfMemoryHeapDumpPath.isNull()) { + Log.log().string("OutOfMemoryError heap dumping failed because the heap dump file path could not be allocated.").newline(); + return; + } + + RawFileDescriptor fd = getFileSupport().create(outOfMemoryHeapDumpPath, FileCreationMode.CREATE_OR_REPLACE, RawFileOperationSupport.FileAccessMode.READ_WRITE); + if (!getFileSupport().isValid(fd)) { + Log.log().string("OutOfMemoryError heap dumping failed because the heap dump file could not be created: ").string(outOfMemoryHeapDumpPath).newline(); + return; + } + + try { + Log.log().string("Dumping heap to ").string(outOfMemoryHeapDumpPath).string(" ...").newline(); + long start = System.currentTimeMillis(); + if (dumpHeap(fd, false)) { + long fileSize = getFileSupport().size(fd); + long elapsedMs = System.currentTimeMillis() - start; + long seconds = elapsedMs / TimeUtils.millisPerSecond; + long ms = elapsedMs % TimeUtils.millisPerSecond; + Log.log().string("Heap dump file created [").signed(fileSize).string(" bytes in ").signed(seconds).character('.').signed(ms).string(" secs]").newline(); + } + } finally { + getFileSupport().close(fd); + } + } + @Override public void dumpHeap(String filename, boolean gcBefore) throws IOException { RawFileDescriptor fd = getFileSupport().create(filename, FileCreationMode.CREATE_OR_REPLACE, RawFileOperationSupport.FileAccessMode.READ_WRITE); @@ -77,16 +123,19 @@ public void dumpHeap(String filename, boolean gcBefore) throws IOException { } } - public void writeHeapTo(RawFileDescriptor fd, boolean gcBefore) throws IOException { + private boolean dumpHeap(RawFileDescriptor fd, boolean gcBefore) { int size = SizeOf.get(HeapDumpVMOperationData.class); HeapDumpVMOperationData data = StackValue.get(size); UnmanagedMemoryUtil.fill((Pointer) data, WordFactory.unsigned(size), (byte) 0); - data.setGCBefore(gcBefore); data.setRawFileDescriptor(fd); heapDumpOperation.enqueue(data); + return data.getSuccess(); + } - if (!data.getSuccess()) { + public void writeHeapTo(RawFileDescriptor fd, boolean gcBefore) throws IOException { + boolean success = dumpHeap(fd, gcBefore); + if (!success) { throw new IOException("An error occurred while writing the heap dump."); } } @@ -135,14 +184,9 @@ protected void operate(NativeVMOperationData d) { boolean success = writer.dumpHeap(data.getRawFileDescriptor()); data.setSuccess(success); } catch (Throwable e) { - reportError(e); + Log.log().string("An exception occurred during heap dumping. The data in the heap dump file may be corrupt: ").string(e.getClass().getName()).newline(); + data.setSuccess(false); } } - - @RestrictHeapAccess(access = UNRESTRICTED, reason = "Error reporting may allocate.") - private static void reportError(Throwable e) { - Log.log().string("An exception occurred during heap dumping. The data in the heap dump file may be corrupt.").newline().string(e.getClass().getName()).string(": ") - .string(e.getMessage()); - } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumping.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumping.java new file mode 100644 index 000000000000..a900648dba31 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumping.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.heap.dump; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +import org.graalvm.compiler.api.replacements.Fold; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.ProcessProperties; +import org.graalvm.nativeimage.impl.HeapDumpSupport; + +import com.oracle.svm.core.SubstrateOptions; + +public abstract class HeapDumping implements HeapDumpSupport { + @Fold + public static HeapDumping singleton() { + return ImageSingletons.lookup(HeapDumping.class); + } + + public abstract void initializeDumpHeapOnOutOfMemoryError(); + + public abstract void teardownDumpHeapOnOutOfMemoryError(); + + public static String getHeapDumpPath(String defaultFilename) { + String heapDumpFilenameOrDirectory = SubstrateOptions.HeapDumpPath.getValue(); + if (heapDumpFilenameOrDirectory.isEmpty()) { + return defaultFilename; + } + var targetPath = Paths.get(heapDumpFilenameOrDirectory); + if (Files.isDirectory(targetPath)) { + targetPath = targetPath.resolve(defaultFilename); + } + return targetPath.toFile().getAbsolutePath(); + } + + public void dumpHeap(boolean gcBefore) throws IOException { + String suffix = Long.toString(System.currentTimeMillis()); + String defaultFilename = getDefaultHeapDumpFilename(suffix); + dumpHeap(getHeapDumpPath(defaultFilename), gcBefore); + } + + protected static String getDefaultHeapDumpFilename(String suffix) { + return SubstrateOptions.HeapDumpDefaultFilenamePrefix.getValue() + ProcessProperties.getProcessID() + "-" + suffix + ".hprof"; + } + + public abstract void dumpHeapOnOutOfMemoryError(); +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpSupportImpl.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpSupportImpl.java index 1ac7046f8550..329fbbc46691 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpSupportImpl.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heapdump/HeapDumpSupportImpl.java @@ -26,10 +26,25 @@ import java.io.FileOutputStream; -import org.graalvm.nativeimage.impl.HeapDumpSupport; +import com.oracle.svm.core.heap.dump.HeapDumping; /* Legacy implementation, only used by other legacy code (see GR-44538). */ -public class HeapDumpSupportImpl implements HeapDumpSupport { +public class HeapDumpSupportImpl extends HeapDumping { + @Override + public void initializeDumpHeapOnOutOfMemoryError() { + /* Nothing to do. */ + } + + @Override + public void teardownDumpHeapOnOutOfMemoryError() { + /* Nothing to do. */ + } + + @Override + public void dumpHeapOnOutOfMemoryError() { + throw new UnsupportedOperationException(); + } + @Override public void dumpHeap(String outputFile, boolean live) throws java.io.IOException { try (FileOutputStream fileOutputStream = new FileOutputStream(outputFile)) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/RawFileOperationSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/RawFileOperationSupport.java index b0d4aa0b720d..25c713fcaa4d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/RawFileOperationSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/os/RawFileOperationSupport.java @@ -28,6 +28,8 @@ import org.graalvm.compiler.api.replacements.Fold; import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.c.type.CCharPointer; +import org.graalvm.nativeimage.impl.UnmanagedMemorySupport; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordBase; @@ -70,6 +72,15 @@ static RawFileOperationSupport nativeByteOrder() { return RawFileOperationSupportHolder.getNativeByteOrder(); } + /** + * Tries to allocate a platform-dependent C string for the given path. Note that the returned + * value needs to be freed manually once it is no longer needed (see + * {@link UnmanagedMemorySupport#free}). + * + * @return If the allocation is successful, a non-null value is returned. + */ + CCharPointer allocateCPath(String path); + /** * Creates a file with the specified {@link FileCreationMode creation} and {@link FileAccessMode * access modes}. @@ -88,6 +99,16 @@ static RawFileOperationSupport nativeByteOrder() { */ RawFileDescriptor create(File file, FileCreationMode creationMode, FileAccessMode accessMode); + /** + * Creates a file with the specified {@link FileCreationMode creation} and {@link FileAccessMode + * access modes}. + * + * @return If the operation is successful, it returns the file descriptor. Otherwise, it returns + * a value where {@link #isValid} will return false. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + RawFileDescriptor create(CCharPointer path, FileCreationMode creationMode, FileAccessMode accessMode); + /** * Opens a file with the specified {@link FileAccessMode access mode}. * @@ -104,6 +125,15 @@ static RawFileOperationSupport nativeByteOrder() { */ RawFileDescriptor open(File file, FileAccessMode accessMode); + /** + * Opens a file with the specified {@link FileAccessMode access mode}. + * + * @return If the operation is successful, it returns the file descriptor. Otherwise, it returns + * a value where {@link #isValid} will return false. + */ + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + RawFileDescriptor open(CCharPointer path, FileAccessMode accessMode); + /** * Checks if a file descriptor is valid or if it represents an error value. * diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpFeature.java index 7b19650c7f32..9a72e00b871c 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/HeapDumpFeature.java @@ -42,8 +42,9 @@ import com.oracle.svm.core.feature.InternalFeature; import com.oracle.svm.core.heap.dump.HProfType; import com.oracle.svm.core.heap.dump.HeapDumpMetadata; -import com.oracle.svm.core.heap.dump.HeapDumpSupportImpl; import com.oracle.svm.core.heap.dump.HeapDumpWriter; +import com.oracle.svm.core.heap.dump.HeapDumping; +import com.oracle.svm.core.heapdump.HeapDumpSupportImpl; import com.oracle.svm.core.heapdump.HeapDumpUtils; import com.oracle.svm.core.heapdump.HeapDumpWriterImpl; import com.oracle.svm.core.meta.SharedField; @@ -75,15 +76,21 @@ public boolean isInConfiguration(IsInConfigurationAccess access) { } @Override - public void beforeAnalysis(BeforeAnalysisAccess access) { + public void duringSetup(DuringSetupAccess access) { if (useLegacyImplementation()) { - ImageSingletons.add(HeapDumpSupport.class, new com.oracle.svm.core.heapdump.HeapDumpSupportImpl()); + HeapDumping heapDumpSupport = new HeapDumpSupportImpl(); + + ImageSingletons.add(HeapDumpSupport.class, heapDumpSupport); + ImageSingletons.add(HeapDumping.class, heapDumpSupport); ImageSingletons.add(HeapDumpUtils.class, new HeapDumpUtils()); ImageSingletons.add(com.oracle.svm.core.heapdump.HeapDumpWriter.class, new HeapDumpWriterImpl()); } else { HeapDumpMetadata metadata = new HeapDumpMetadata(); + HeapDumping heapDumpSupport = new com.oracle.svm.core.heap.dump.HeapDumpSupportImpl(metadata); + + ImageSingletons.add(HeapDumpSupport.class, heapDumpSupport); + ImageSingletons.add(HeapDumping.class, heapDumpSupport); ImageSingletons.add(HeapDumpMetadata.class, metadata); - ImageSingletons.add(HeapDumpSupport.class, new HeapDumpSupportImpl(metadata)); } }