From 5489477230c70eefd5291f9052cc261ca38079c5 Mon Sep 17 00:00:00 2001 From: Andrija Kolic Date: Wed, 13 Aug 2025 15:21:25 +0200 Subject: [PATCH 1/2] Add HeapMetadataDumpFileName native image build option and NativeImageHeap.dumpMetadata method. --- .../com/oracle/svm/core/SubstrateOptions.java | 5 +++ .../oracle/svm/hosted/image/NativeImage.java | 1 + .../svm/hosted/image/NativeImageHeap.java | 35 +++++++++++++++++++ 3 files changed, 41 insertions(+) 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 aa3ad612f8c7..10d4ac7d0f38 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 @@ -1370,6 +1370,11 @@ public Boolean getValue(OptionValues values) { @Option(help = "Create a heap dump and exit.")// public static final RuntimeOptionKey DumpHeapAndExit = new RuntimeOptionKey<>(false, Immutable); + @Option(help = "Name of a csv file into which heap metadata should be dumped. " + + "This csv file will be located in the same directory as the image. " + + "If this option is an empty string, the metadata will not be dumped.") // + public static final HostedOptionKey HeapMetadataDumpFileName = new HostedOptionKey<>(""); + @Option(help = "Print some VM information and exit.")// public static final RuntimeOptionKey PrintVMInfoAndExit = new RuntimeOptionKey<>(false, Immutable); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImage.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImage.java index 803625c25ced..c009d99704b2 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImage.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImage.java @@ -561,6 +561,7 @@ public void build(String imageName, DebugContext debug) { // We print the heap statistics after the heap was successfully written because this // could modify objects that will be part of the image heap. printHeapStatistics(heap.getLayouter().getPartitions()); + heap.dumpMetadata(); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageHeap.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageHeap.java index 55c52916324d..a738013e8d1a 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageHeap.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageHeap.java @@ -26,8 +26,13 @@ import static com.oracle.svm.core.util.VMError.shouldNotReachHereUnexpectedInput; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; import java.lang.reflect.Array; import java.lang.reflect.Modifier; +import java.nio.file.Path; import java.util.ArrayDeque; import java.util.Arrays; import java.util.Collection; @@ -41,6 +46,7 @@ import java.util.Set; import java.util.function.Predicate; +import com.oracle.svm.core.option.HostedOptionValues; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.c.function.RelocatedPointer; import org.graalvm.word.UnsignedWord; @@ -792,6 +798,35 @@ public ObjectInfo addLateToImageHeap(Object object, Object reason) { return addToImageHeap(object, (HostedClass) type, getSize(object, type), System.identityHashCode(object), reason); } + /** + * Dumps metadata for every object in the heap. + */ + public void dumpMetadata() { + String metadataFileName = SubstrateOptions.HeapMetadataDumpFileName.getValue(); + if (metadataFileName == null || metadataFileName.isEmpty()) { + // Do not dump metadata if the file name isn't set + return; + } + + Path metadataFilePath = SubstrateOptions.getImagePath(HostedOptionValues.singleton()).resolve(metadataFileName); + File metadataFile = metadataFilePath.toFile(); + String metadataDir = metadataFile.getParent(); + if (!new File(metadataDir).exists()) { + throw VMError.shouldNotReachHere("Heap metadata directory does not exist: " + metadataDir); + } + + try (FileWriter metadataOut = new FileWriter(metadataFile); + BufferedWriter metadataBw = new BufferedWriter(metadataOut)) { + metadataBw.write("class-name,partition,offset-in-heap,size\n"); + for (ObjectInfo info : getObjects()) { + String csvLine = info.getClazz().getName() + "," + info.getPartition().getName() + "," + info.getOffset() + "," + info.getSize() + System.lineSeparator(); + metadataBw.write(csvLine); + } + } catch (IOException ex) { + throw new RuntimeException("Failed to dump heap metadata to " + metadataFile, ex); + } + } + private long getSize(Object object, HostedType type) { if (type.isInstanceClass()) { HostedInstanceClass clazz = (HostedInstanceClass) type; From b4f1b233036dc85b4a58e78341368a419a6831c8 Mon Sep 17 00:00:00 2001 From: Andrija Kolic Date: Tue, 30 Sep 2025 12:11:53 +0200 Subject: [PATCH 2/2] Rename the option to ImageHeapMetadataDumpFileName; make sure to specify that the feature refers to the image heap specifically. --- .../src/com/oracle/svm/core/SubstrateOptions.java | 4 ++-- .../src/com/oracle/svm/hosted/image/NativeImageHeap.java | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) 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 10d4ac7d0f38..8978a570808d 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 @@ -1370,10 +1370,10 @@ public Boolean getValue(OptionValues values) { @Option(help = "Create a heap dump and exit.")// public static final RuntimeOptionKey DumpHeapAndExit = new RuntimeOptionKey<>(false, Immutable); - @Option(help = "Name of a csv file into which heap metadata should be dumped. " + + @Option(help = "Name of a csv file into which image heap metadata should be dumped. " + "This csv file will be located in the same directory as the image. " + "If this option is an empty string, the metadata will not be dumped.") // - public static final HostedOptionKey HeapMetadataDumpFileName = new HostedOptionKey<>(""); + public static final HostedOptionKey ImageHeapMetadataDumpFileName = new HostedOptionKey<>(""); @Option(help = "Print some VM information and exit.")// public static final RuntimeOptionKey PrintVMInfoAndExit = new RuntimeOptionKey<>(false, Immutable); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageHeap.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageHeap.java index a738013e8d1a..8ffcae523613 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageHeap.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageHeap.java @@ -799,10 +799,10 @@ public ObjectInfo addLateToImageHeap(Object object, Object reason) { } /** - * Dumps metadata for every object in the heap. + * Dumps metadata for every object in the image heap. */ public void dumpMetadata() { - String metadataFileName = SubstrateOptions.HeapMetadataDumpFileName.getValue(); + String metadataFileName = SubstrateOptions.ImageHeapMetadataDumpFileName.getValue(); if (metadataFileName == null || metadataFileName.isEmpty()) { // Do not dump metadata if the file name isn't set return; @@ -812,7 +812,7 @@ public void dumpMetadata() { File metadataFile = metadataFilePath.toFile(); String metadataDir = metadataFile.getParent(); if (!new File(metadataDir).exists()) { - throw VMError.shouldNotReachHere("Heap metadata directory does not exist: " + metadataDir); + throw VMError.shouldNotReachHere("Image heap metadata directory does not exist: " + metadataDir); } try (FileWriter metadataOut = new FileWriter(metadataFile); @@ -823,7 +823,7 @@ public void dumpMetadata() { metadataBw.write(csvLine); } } catch (IOException ex) { - throw new RuntimeException("Failed to dump heap metadata to " + metadataFile, ex); + throw new RuntimeException("Failed to dump image heap metadata to " + metadataFile, ex); } }