From 624ff2fb6e899ff9198cfe27b3994a1a2f50e90c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Galder=20Zamarren=CC=83o?= Date: Tue, 18 Jul 2023 14:52:37 +0200 Subject: [PATCH 1/5] Add support for -XX:+HeapDumpOnOutOfMemoryError --- .../org/graalvm/nativeimage/VMRuntime.java | 14 ++++++ .../nativeimage/impl/HeapDumpSupport.java | 3 ++ .../oracle/svm/core/genscavenge/GCImpl.java | 12 ++++- .../posix/PosixRawFileOperationSupport.java | 18 +++++++ .../svm/core/DumpHeapOnSignalFeature.java | 5 ++ .../com/oracle/svm/core/SubstrateOptions.java | 7 +++ .../core/heap/dump/HeapDumpSupportImpl.java | 50 +++++++++++++++++-- .../core/heapdump/HeapDumpSupportImpl.java | 10 ++++ .../svm/core/os/RawFileOperationSupport.java | 18 +++++++ 9 files changed, 131 insertions(+), 6 deletions(-) diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/VMRuntime.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/VMRuntime.java index 983207c0dbcc..a367548bc523 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/VMRuntime.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/VMRuntime.java @@ -92,6 +92,20 @@ public static void dumpHeap(String outputFile, boolean live) throws IOException ImageSingletons.lookup(HeapDumpSupport.class).dumpHeap(outputFile, live); } + /** + * Dumps the heap to a predetermined file in case of an @{@link OutOfMemoryError}, in the same format as the hprof heap dump. + * + * @throws UnsupportedOperationException if this operation is not supported. + * + * @since 23.1 + */ + public static void dumpHeapOnOutOfMemoryError() { + if (!ImageSingletons.contains(HeapDumpSupport.class)) { + throw new UnsupportedOperationException(); + } + ImageSingletons.lookup(HeapDumpSupport.class).dumpHeapOnOutOfMemoryError(); + } + private VMRuntime() { } } diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/HeapDumpSupport.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/HeapDumpSupport.java index 444974571abc..da8180d0be71 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/HeapDumpSupport.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/HeapDumpSupport.java @@ -44,4 +44,7 @@ public interface HeapDumpSupport { void dumpHeap(String outputFile, boolean live) throws java.io.IOException; + void dumpHeapOnOutOfMemoryError(); + + void initHeapDumpOnOutOfMemoryErrorPath(); } diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java index 65415bf20647..2b0d98e96e0c 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java @@ -35,6 +35,7 @@ import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.StackValue; +import org.graalvm.nativeimage.VMRuntime; import org.graalvm.nativeimage.c.function.CodePointer; import org.graalvm.nativeimage.c.struct.RawField; import org.graalvm.nativeimage.c.struct.RawStructure; @@ -150,10 +151,17 @@ public void maybeCollectOnAllocation() { outOfMemory = collectWithoutAllocating(GenScavengeGCCause.OnAllocation, false); } if (outOfMemory) { - throw OutOfMemoryUtil.heapSizeExceeded(); + heapSizeExceeded(); } } + private static void heapSizeExceeded() { + if (SubstrateOptions.isHeapDumpOnOutOfMemoryError()) { + VMRuntime.dumpHeapOnOutOfMemoryError(); + } + throw OutOfMemoryUtil.heapSizeExceeded(); + } + @Override public void maybeCauseUserRequestedCollection(GCCause cause, boolean fullGC) { if (policy.shouldCollectOnRequest(cause, fullGC)) { @@ -165,7 +173,7 @@ private void collect(GCCause cause, boolean forceFullGC) { if (!hasNeverCollectPolicy()) { boolean outOfMemory = collectWithoutAllocating(cause, forceFullGC); if (outOfMemory) { - throw OutOfMemoryUtil.heapSizeExceeded(); + heapSizeExceeded(); } } } 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..c44c3942f995 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,6 +30,7 @@ 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.word.Pointer; import org.graalvm.word.SignedWord; @@ -60,6 +61,17 @@ public RawFileDescriptor create(File file, FileCreationMode creationMode, FileAc return open0(path, flags); } + @Override + public RawFileDescriptor create(CCharPointer cPath, FileCreationMode creationMode, FileAccessMode accessMode) { + int flags = parseMode(creationMode) | parseMode(accessMode); + return open0(cPath, flags); + } + + 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)); + } + @Override public RawFileDescriptor open(File file, FileAccessMode mode) { String path = file.getPath(); @@ -67,6 +79,12 @@ public RawFileDescriptor open(File file, FileAccessMode mode) { return open0(path, flags); } + @Override + 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)) { 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..c5c1f6dcc512 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 @@ -30,6 +30,7 @@ import java.util.Date; import java.util.TimeZone; +import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.ProcessProperties; import org.graalvm.nativeimage.VMRuntime; @@ -39,6 +40,7 @@ import com.oracle.svm.core.log.Log; import jdk.internal.misc.Signal; +import org.graalvm.nativeimage.impl.HeapDumpSupport; @AutomaticallyRegisteredFeature public class DumpHeapOnSignalFeature implements InternalFeature { @@ -59,6 +61,9 @@ final class DumpHeapStartupHook implements RuntimeSupport.Hook { public void execute(boolean isFirstIsolate) { if (isFirstIsolate && SubstrateOptions.EnableSignalHandling.getValue()) { DumpHeapReport.install(); + if (SubstrateOptions.isHeapDumpOnOutOfMemoryError()) { + ImageSingletons.lookup(HeapDumpSupport.class).initHeapDumpOnOutOfMemoryErrorPath(); + } } } } 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..0d11295f5712 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 @@ -825,6 +825,13 @@ public Boolean getValue(OptionValues values) { } }; + @Option(help = "Dump heap to file when java.lang.OutOfMemoryError is thrown.")// + public static final RuntimeOptionKey HeapDumpOnOutOfMemoryError = new RuntimeOptionKey<>(false, Immutable); + + public static boolean isHeapDumpOnOutOfMemoryError() { + return VMInspectionOptions.hasHeapDumpSupport() && SubstrateOptions.HeapDumpOnOutOfMemoryError.getValue(); + } + @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); 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..a434b2a10219 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,19 +25,26 @@ 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 com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.SubstrateUtil; +import com.oracle.svm.core.headers.LibC; import org.graalvm.compiler.api.replacements.Fold; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.ProcessProperties; import org.graalvm.nativeimage.StackValue; +import org.graalvm.nativeimage.UnmanagedMemory; 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.c.type.CCharPointer; +import org.graalvm.nativeimage.c.type.CTypeConversion; import org.graalvm.nativeimage.impl.HeapDumpSupport; import org.graalvm.word.Pointer; +import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordFactory; import com.oracle.svm.core.UnmanagedMemoryUtil; @@ -56,6 +63,7 @@ public class HeapDumpSupportImpl implements HeapDumpSupport { private final HeapDumpWriter writer; private final HeapDumpOperation heapDumpOperation; + private CCharPointer heapOnErrorDumpPath; @Platforms(Platform.HOSTED_ONLY.class) public HeapDumpSupportImpl(HeapDumpMetadata metadata) { @@ -77,6 +85,40 @@ public void dumpHeap(String filename, boolean gcBefore) throws IOException { } } + @Override + @RestrictHeapAccess(access = NO_ALLOCATION, reason = "Heap dumping on OutOfMemoryError must not allocate.") + public void dumpHeapOnOutOfMemoryError() { + final RawFileDescriptor fd = getFileSupport().create(heapOnErrorDumpPath, FileCreationMode.CREATE_OR_REPLACE, RawFileOperationSupport.FileAccessMode.READ_WRITE); + if (!getFileSupport().isValid(fd)) { + Log.log().string("Invalid file descriptor opening heap dump on OutOfMemoryError.").newline(); + return; + } + + int size = SizeOf.get(HeapDumpVMOperationData.class); + HeapDumpVMOperationData data = StackValue.get(size); + UnmanagedMemoryUtil.fill((Pointer) data, WordFactory.unsigned(size), (byte) 0); + + data.setGCBefore(false); + data.setRawFileDescriptor(fd); + heapDumpOperation.enqueue(data); + } + + @Override + public void initHeapDumpOnOutOfMemoryErrorPath() { + String dumpFileName = "svm-heapdump-" + ProcessProperties.getProcessID() + ".hprof"; + String dumpPath = SubstrateOptions.getHeapDumpPath(dumpFileName); + try (CTypeConversion.CCharPointerHolder cPath = CTypeConversion.toCString(dumpPath)) { + heapOnErrorDumpPath = copyDumpPath(cPath.get()); + } + } + + private static CCharPointer copyDumpPath(CCharPointer cPath) { + final UnsignedWord length = SubstrateUtil.strlen(cPath); + final CCharPointer copy = UnmanagedMemory.malloc(length); + LibC.memcpy(copy, cPath, length); + return copy; + } + public void writeHeapTo(RawFileDescriptor fd, boolean gcBefore) throws IOException { int size = SizeOf.get(HeapDumpVMOperationData.class); HeapDumpVMOperationData data = StackValue.get(size); @@ -136,13 +178,13 @@ protected void operate(NativeVMOperationData d) { data.setSuccess(success); } catch (Throwable e) { reportError(e); + data.setSuccess(false); } } - @RestrictHeapAccess(access = UNRESTRICTED, reason = "Error reporting may allocate.") + @RestrictHeapAccess(access = NO_ALLOCATION, reason = "Error reporting must not 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()); + Log.log().string("An exception occurred during heap dumping. The data in the heap dump file may be corrupt: ").string(e.getClass().getName()); } } } 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..ad5574b92b38 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 @@ -36,4 +36,14 @@ public void dumpHeap(String outputFile, boolean live) throws java.io.IOException com.oracle.svm.core.heapdump.HeapDumpWriter.singleton().writeHeapTo(fileOutputStream, live); } } + + @Override + public void dumpHeapOnOutOfMemoryError() { + // No-op + } + + @Override + public void initHeapDumpOnOutOfMemoryErrorPath() { + // No-op + } } 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..4404be3ac985 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,7 @@ import org.graalvm.compiler.api.replacements.Fold; import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.c.type.CCharPointer; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordBase; @@ -88,6 +89,15 @@ 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. + */ + RawFileDescriptor create(CCharPointer path, FileCreationMode creationMode, FileAccessMode accessMode); + /** * Opens a file with the specified {@link FileAccessMode access mode}. * @@ -104,6 +114,14 @@ 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. + */ + RawFileDescriptor open(CCharPointer path, FileAccessMode accessMode); + /** * Checks if a file descriptor is valid or if it represents an error value. * From f75d2e2e13a9a83b823a264e212d20e7db69d059 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Galder=20Zamarren=CC=83o?= Date: Tue, 18 Jul 2023 15:45:27 +0200 Subject: [PATCH 2/5] Log when heap dump is generated on OutOfMemoryError --- .../com/oracle/svm/core/heap/dump/HeapDumpSupportImpl.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 a434b2a10219..44e004ef54b6 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 @@ -88,9 +88,11 @@ public void dumpHeap(String filename, boolean gcBefore) throws IOException { @Override @RestrictHeapAccess(access = NO_ALLOCATION, reason = "Heap dumping on OutOfMemoryError must not allocate.") public void dumpHeapOnOutOfMemoryError() { + final Log log = Log.log(); + final RawFileDescriptor fd = getFileSupport().create(heapOnErrorDumpPath, FileCreationMode.CREATE_OR_REPLACE, RawFileOperationSupport.FileAccessMode.READ_WRITE); if (!getFileSupport().isValid(fd)) { - Log.log().string("Invalid file descriptor opening heap dump on OutOfMemoryError.").newline(); + log.string("Invalid file descriptor opening heap dump on OutOfMemoryError.").newline(); return; } @@ -98,6 +100,8 @@ public void dumpHeapOnOutOfMemoryError() { HeapDumpVMOperationData data = StackValue.get(size); UnmanagedMemoryUtil.fill((Pointer) data, WordFactory.unsigned(size), (byte) 0); + log.string("Dumping heap to ").string(heapOnErrorDumpPath).string(" ...").newline(); + data.setGCBefore(false); data.setRawFileDescriptor(fd); heapDumpOperation.enqueue(data); From 37319360e42d4c9c4bd363b34bd326b82e2119e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Galder=20Zamarren=CC=83o?= Date: Tue, 18 Jul 2023 15:45:53 +0200 Subject: [PATCH 3/5] Add documentation for -XX:+HeapDumpOnOutOfMemoryError --- .../create-heap-dump-from-native-executable.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) 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..d53b5a21b3d0 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 @@ -9,12 +9,13 @@ permalink: /reference-manual/native-image/guides/create-heap-dump/ 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. +5. If `-XX:+HeapDumpOnOutOfMemoryError` is passed in at runtime and an `OutOfMemoryError` is encountered, a heap dump will be produced. All approaches are described below. @@ -275,6 +276,18 @@ The condition to create a heap dump is provided as an option on the command line ![Native Image Heap Dump View in VisualVM](img/heap-dump-api.png) +## Create a Heap Dump on `OutOfMemoryError` + +Start the application with `-XX:+HeapDumpOnOutOfMemoryError` option to get a heap dump when an `OutOfMemoryError` occurs. +The generated heap dump file name will have the `svm-heapdump-.hprof` naming format. +For example: + +```shell +./example -XX:+HeapDumpOnOutOfMemoryError +Dumping heap to svm-heapdump-67799.hprof ... +Exception in thread "main" java.lang.OutOfMemoryError: Garbage-collected heap size exceeded. +``` + ### Related Documentation * [Debugging and Diagnostics](../DebuggingAndDiagnostics.md) From 9a8c441ba06ccc346b49c6d5402be3e752f4efac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Galder=20Zamarren=CC=83o?= Date: Wed, 26 Jul 2023 16:12:59 +0100 Subject: [PATCH 4/5] Generate only one heap dump on OOME --- .../com/oracle/svm/core/genscavenge/GCImpl.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java index 2b0d98e96e0c..f3051993631e 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java @@ -29,6 +29,7 @@ import java.lang.ref.Reference; +import com.oracle.svm.core.jdk.UninterruptibleUtils.AtomicBoolean; import org.graalvm.compiler.api.replacements.Fold; import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.IsolateThread; @@ -117,6 +118,8 @@ public final class GCImpl implements GC { private UnsignedWord collectionEpoch = WordFactory.zero(); private long lastWholeHeapExaminedTimeMillis = -1; + private final AtomicBoolean outOfMemoryReported = new AtomicBoolean(false); + @Platforms(Platform.HOSTED_ONLY.class) GCImpl() { this.policy = CollectionPolicy.getInitialPolicy(); @@ -151,15 +154,15 @@ public void maybeCollectOnAllocation() { outOfMemory = collectWithoutAllocating(GenScavengeGCCause.OnAllocation, false); } if (outOfMemory) { - heapSizeExceeded(); + reportJavaOutOfMemory(); + throw OutOfMemoryUtil.heapSizeExceeded(); } } - private static void heapSizeExceeded() { - if (SubstrateOptions.isHeapDumpOnOutOfMemoryError()) { + private void reportJavaOutOfMemory() { + if (SubstrateOptions.isHeapDumpOnOutOfMemoryError() && outOfMemoryReported.compareAndSet(false, true)) { VMRuntime.dumpHeapOnOutOfMemoryError(); } - throw OutOfMemoryUtil.heapSizeExceeded(); } @Override @@ -173,7 +176,8 @@ private void collect(GCCause cause, boolean forceFullGC) { if (!hasNeverCollectPolicy()) { boolean outOfMemory = collectWithoutAllocating(cause, forceFullGC); if (outOfMemory) { - heapSizeExceeded(); + reportJavaOutOfMemory(); + throw OutOfMemoryUtil.heapSizeExceeded(); } } } From 1cba52313ae7f1812e549f6c84b48170d7e3dc8a Mon Sep 17 00:00:00 2001 From: Christian Haeubl Date: Wed, 2 Aug 2023 10:02:48 +0200 Subject: [PATCH 5/5] Fixes and improvements for -XX:+HeapDumpOnOutOfMemoryError. --- ...create-heap-dump-from-native-executable.md | 56 +++++----- .../org/graalvm/nativeimage/VMRuntime.java | 14 --- .../nativeimage/impl/HeapDumpSupport.java | 3 - substratevm/CHANGELOG.md | 1 + .../oracle/svm/core/genscavenge/GCImpl.java | 12 -- .../posix/PosixRawFileOperationSupport.java | 34 ++++-- .../svm/core/DumpHeapOnSignalFeature.java | 36 +++--- .../com/oracle/svm/core/SubstrateOptions.java | 23 +--- .../oracle/svm/core/UnmanagedMemoryUtil.java | 2 +- .../oracle/svm/core/VMInspectionOptions.java | 6 +- .../oracle/svm/core/heap/OutOfMemoryUtil.java | 9 ++ .../core/heap/dump/HeapDumpSupportImpl.java | 104 +++++++++--------- .../svm/core/heap/dump/HeapDumping.java | 71 ++++++++++++ .../core/heapdump/HeapDumpSupportImpl.java | 23 ++-- .../svm/core/os/RawFileOperationSupport.java | 12 ++ .../svm/hosted/heap/HeapDumpFeature.java | 15 ++- 16 files changed, 250 insertions(+), 171 deletions(-) create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/dump/HeapDumping.java 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 d53b5a21b3d0..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,15 +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 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. -5. If `-XX:+HeapDumpOnOutOfMemoryError` is passed in at runtime and an `OutOfMemoryError` is encountered, a heap dump will be produced. +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. @@ -31,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. @@ -96,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())); @@ -106,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()) { @@ -121,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: @@ -276,18 +292,6 @@ The condition to create a heap dump is provided as an option on the command line ![Native Image Heap Dump View in VisualVM](img/heap-dump-api.png) -## Create a Heap Dump on `OutOfMemoryError` - -Start the application with `-XX:+HeapDumpOnOutOfMemoryError` option to get a heap dump when an `OutOfMemoryError` occurs. -The generated heap dump file name will have the `svm-heapdump-.hprof` naming format. -For example: - -```shell -./example -XX:+HeapDumpOnOutOfMemoryError -Dumping heap to svm-heapdump-67799.hprof ... -Exception in thread "main" java.lang.OutOfMemoryError: Garbage-collected heap size exceeded. -``` - ### Related Documentation * [Debugging and Diagnostics](../DebuggingAndDiagnostics.md) diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/VMRuntime.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/VMRuntime.java index a367548bc523..983207c0dbcc 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/VMRuntime.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/VMRuntime.java @@ -92,20 +92,6 @@ public static void dumpHeap(String outputFile, boolean live) throws IOException ImageSingletons.lookup(HeapDumpSupport.class).dumpHeap(outputFile, live); } - /** - * Dumps the heap to a predetermined file in case of an @{@link OutOfMemoryError}, in the same format as the hprof heap dump. - * - * @throws UnsupportedOperationException if this operation is not supported. - * - * @since 23.1 - */ - public static void dumpHeapOnOutOfMemoryError() { - if (!ImageSingletons.contains(HeapDumpSupport.class)) { - throw new UnsupportedOperationException(); - } - ImageSingletons.lookup(HeapDumpSupport.class).dumpHeapOnOutOfMemoryError(); - } - private VMRuntime() { } } diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/HeapDumpSupport.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/HeapDumpSupport.java index da8180d0be71..444974571abc 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/HeapDumpSupport.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/HeapDumpSupport.java @@ -44,7 +44,4 @@ public interface HeapDumpSupport { void dumpHeap(String outputFile, boolean live) throws java.io.IOException; - void dumpHeapOnOutOfMemoryError(); - - void initHeapDumpOnOutOfMemoryErrorPath(); } 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.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java index f3051993631e..65415bf20647 100644 --- a/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java +++ b/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/GCImpl.java @@ -29,14 +29,12 @@ import java.lang.ref.Reference; -import com.oracle.svm.core.jdk.UninterruptibleUtils.AtomicBoolean; import org.graalvm.compiler.api.replacements.Fold; import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.StackValue; -import org.graalvm.nativeimage.VMRuntime; import org.graalvm.nativeimage.c.function.CodePointer; import org.graalvm.nativeimage.c.struct.RawField; import org.graalvm.nativeimage.c.struct.RawStructure; @@ -118,8 +116,6 @@ public final class GCImpl implements GC { private UnsignedWord collectionEpoch = WordFactory.zero(); private long lastWholeHeapExaminedTimeMillis = -1; - private final AtomicBoolean outOfMemoryReported = new AtomicBoolean(false); - @Platforms(Platform.HOSTED_ONLY.class) GCImpl() { this.policy = CollectionPolicy.getInitialPolicy(); @@ -154,17 +150,10 @@ public void maybeCollectOnAllocation() { outOfMemory = collectWithoutAllocating(GenScavengeGCCause.OnAllocation, false); } if (outOfMemory) { - reportJavaOutOfMemory(); throw OutOfMemoryUtil.heapSizeExceeded(); } } - private void reportJavaOutOfMemory() { - if (SubstrateOptions.isHeapDumpOnOutOfMemoryError() && outOfMemoryReported.compareAndSet(false, true)) { - VMRuntime.dumpHeapOnOutOfMemoryError(); - } - } - @Override public void maybeCauseUserRequestedCollection(GCCause cause, boolean fullGC) { if (policy.shouldCollectOnRequest(cause, fullGC)) { @@ -176,7 +165,6 @@ private void collect(GCCause cause, boolean forceFullGC) { if (!hasNeverCollectPolicy()) { boolean outOfMemory = collectWithoutAllocating(cause, forceFullGC); if (outOfMemory) { - reportJavaOutOfMemory(); throw OutOfMemoryUtil.heapSizeExceeded(); } } 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 c44c3942f995..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 @@ -32,6 +32,7 @@ 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; @@ -54,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(); @@ -62,16 +78,12 @@ public RawFileDescriptor create(File file, FileCreationMode creationMode, FileAc } @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); } - 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)); - } - @Override public RawFileDescriptor open(File file, FileAccessMode mode) { String path = file.getPath(); @@ -80,18 +92,24 @@ public RawFileDescriptor open(File file, FileAccessMode mode) { } @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) { @@ -172,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: @@ -183,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 c5c1f6dcc512..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,22 +25,14 @@ 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.ImageSingletons; -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; import jdk.internal.misc.Signal; -import org.graalvm.nativeimage.impl.HeapDumpSupport; @AutomaticallyRegisteredFeature public class DumpHeapOnSignalFeature implements InternalFeature { @@ -52,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()); } } @@ -61,28 +54,31 @@ final class DumpHeapStartupHook implements RuntimeSupport.Hook { public void execute(boolean isFirstIsolate) { if (isFirstIsolate && SubstrateOptions.EnableSignalHandling.getValue()) { DumpHeapReport.install(); - if (SubstrateOptions.isHeapDumpOnOutOfMemoryError()) { - ImageSingletons.lookup(HeapDumpSupport.class).initHeapDumpOnOutOfMemoryErrorPath(); - } + } + + 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 0d11295f5712..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,28 +824,14 @@ public Boolean getValue(OptionValues values) { } }; - @Option(help = "Dump heap to file when java.lang.OutOfMemoryError is thrown.")// - public static final RuntimeOptionKey HeapDumpOnOutOfMemoryError = new RuntimeOptionKey<>(false, Immutable); - - public static boolean isHeapDumpOnOutOfMemoryError() { - return VMInspectionOptions.hasHeapDumpSupport() && SubstrateOptions.HeapDumpOnOutOfMemoryError.getValue(); - } + @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 44e004ef54b6..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 @@ -28,23 +28,17 @@ import java.io.IOException; -import com.oracle.svm.core.SubstrateOptions; -import com.oracle.svm.core.SubstrateUtil; -import com.oracle.svm.core.headers.LibC; 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.ProcessProperties; import org.graalvm.nativeimage.StackValue; -import org.graalvm.nativeimage.UnmanagedMemory; 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.c.type.CCharPointer; -import org.graalvm.nativeimage.c.type.CTypeConversion; -import org.graalvm.nativeimage.impl.HeapDumpSupport; +import org.graalvm.nativeimage.impl.UnmanagedMemorySupport; import org.graalvm.word.Pointer; -import org.graalvm.word.UnsignedWord; import org.graalvm.word.WordFactory; import com.oracle.svm.core.UnmanagedMemoryUtil; @@ -59,11 +53,13 @@ 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 heapOnErrorDumpPath; + + private CCharPointer outOfMemoryHeapDumpPath; @Platforms(Platform.HOSTED_ONLY.class) public HeapDumpSupportImpl(HeapDumpMetadata metadata) { @@ -72,67 +68,74 @@ public HeapDumpSupportImpl(HeapDumpMetadata metadata) { } @Override - public void dumpHeap(String filename, boolean gcBefore) throws IOException { - RawFileDescriptor fd = getFileSupport().create(filename, FileCreationMode.CREATE_OR_REPLACE, RawFileOperationSupport.FileAccessMode.READ_WRITE); - if (!getFileSupport().isValid(fd)) { - throw new IOException("Could not create the heap dump file: " + filename); - } + public void initializeDumpHeapOnOutOfMemoryError() { + String defaultFilename = getDefaultHeapDumpFilename("OOME"); + String heapDumpPath = getHeapDumpPath(defaultFilename); + outOfMemoryHeapDumpPath = getFileSupport().allocateCPath(heapDumpPath); + } - try { - writeHeapTo(fd, gcBefore); - } finally { - getFileSupport().close(fd); - } + @Override + public void teardownDumpHeapOnOutOfMemoryError() { + ImageSingletons.lookup(UnmanagedMemorySupport.class).free(outOfMemoryHeapDumpPath); + outOfMemoryHeapDumpPath = WordFactory.nullPointer(); } @Override - @RestrictHeapAccess(access = NO_ALLOCATION, reason = "Heap dumping on OutOfMemoryError must not allocate.") + @RestrictHeapAccess(access = NO_ALLOCATION, reason = "OutOfMemoryError heap dumping must not allocate.") public void dumpHeapOnOutOfMemoryError() { - final Log log = Log.log(); + if (outOfMemoryHeapDumpPath.isNull()) { + Log.log().string("OutOfMemoryError heap dumping failed because the heap dump file path could not be allocated.").newline(); + return; + } - final RawFileDescriptor fd = getFileSupport().create(heapOnErrorDumpPath, FileCreationMode.CREATE_OR_REPLACE, RawFileOperationSupport.FileAccessMode.READ_WRITE); + RawFileDescriptor fd = getFileSupport().create(outOfMemoryHeapDumpPath, FileCreationMode.CREATE_OR_REPLACE, RawFileOperationSupport.FileAccessMode.READ_WRITE); if (!getFileSupport().isValid(fd)) { - log.string("Invalid file descriptor opening heap dump on OutOfMemoryError.").newline(); + Log.log().string("OutOfMemoryError heap dumping failed because the heap dump file could not be created: ").string(outOfMemoryHeapDumpPath).newline(); return; } - int size = SizeOf.get(HeapDumpVMOperationData.class); - HeapDumpVMOperationData data = StackValue.get(size); - UnmanagedMemoryUtil.fill((Pointer) data, WordFactory.unsigned(size), (byte) 0); - - log.string("Dumping heap to ").string(heapOnErrorDumpPath).string(" ...").newline(); - - data.setGCBefore(false); - data.setRawFileDescriptor(fd); - heapDumpOperation.enqueue(data); + 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 initHeapDumpOnOutOfMemoryErrorPath() { - String dumpFileName = "svm-heapdump-" + ProcessProperties.getProcessID() + ".hprof"; - String dumpPath = SubstrateOptions.getHeapDumpPath(dumpFileName); - try (CTypeConversion.CCharPointerHolder cPath = CTypeConversion.toCString(dumpPath)) { - heapOnErrorDumpPath = copyDumpPath(cPath.get()); + public void dumpHeap(String filename, boolean gcBefore) throws IOException { + RawFileDescriptor fd = getFileSupport().create(filename, FileCreationMode.CREATE_OR_REPLACE, RawFileOperationSupport.FileAccessMode.READ_WRITE); + if (!getFileSupport().isValid(fd)) { + throw new IOException("Could not create the heap dump file: " + filename); } - } - private static CCharPointer copyDumpPath(CCharPointer cPath) { - final UnsignedWord length = SubstrateUtil.strlen(cPath); - final CCharPointer copy = UnmanagedMemory.malloc(length); - LibC.memcpy(copy, cPath, length); - return copy; + try { + writeHeapTo(fd, gcBefore); + } finally { + getFileSupport().close(fd); + } } - 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."); } } @@ -181,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 = NO_ALLOCATION, reason = "Error reporting must not 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: ").string(e.getClass().getName()); - } } } 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 ad5574b92b38..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,24 +26,29 @@ 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 dumpHeap(String outputFile, boolean live) throws java.io.IOException { - try (FileOutputStream fileOutputStream = new FileOutputStream(outputFile)) { - com.oracle.svm.core.heapdump.HeapDumpWriter.singleton().writeHeapTo(fileOutputStream, live); - } + public void initializeDumpHeapOnOutOfMemoryError() { + /* Nothing to do. */ + } + + @Override + public void teardownDumpHeapOnOutOfMemoryError() { + /* Nothing to do. */ } @Override public void dumpHeapOnOutOfMemoryError() { - // No-op + throw new UnsupportedOperationException(); } @Override - public void initHeapDumpOnOutOfMemoryErrorPath() { - // No-op + public void dumpHeap(String outputFile, boolean live) throws java.io.IOException { + try (FileOutputStream fileOutputStream = new FileOutputStream(outputFile)) { + com.oracle.svm.core.heapdump.HeapDumpWriter.singleton().writeHeapTo(fileOutputStream, live); + } } } 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 4404be3ac985..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 @@ -29,6 +29,7 @@ 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; @@ -71,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}. @@ -96,6 +106,7 @@ static RawFileOperationSupport nativeByteOrder() { * @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); /** @@ -120,6 +131,7 @@ static RawFileOperationSupport nativeByteOrder() { * @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); /** 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)); } }