diff --git a/docs/reference-manual/native-image/BuildOutput.md b/docs/reference-manual/native-image/BuildOutput.md
index 0cfabc2840c2..1f1a9c0de17f 100644
--- a/docs/reference-manual/native-image/BuildOutput.md
+++ b/docs/reference-manual/native-image/BuildOutput.md
@@ -24,7 +24,7 @@ Below is the example output when building a native executable of the `HelloWorld
GraalVM Native Image: Generating 'helloworld' (executable)...
================================================================================
[1/8] Initializing... (2.8s @ 0.15GB)
- Java version: 20+34, vendor version: GraalVM CE 20-dev+34.1
+ Java version: 25+13, vendor version: GraalVM CE 25-dev+13.1
Graal compiler: optimization level: 2, target machine: x86-64-v3
C compiler: gcc (linux, x86_64, 12.2.0)
Garbage collector: Serial GC (max heap size: 80% of RAM)
@@ -49,7 +49,7 @@ GraalVM Native Image: Generating 'helloworld' (executable)...
7.03MB (32.02%) for image heap: 93,301 objects and 5 resources
8.96MB (40.83%) for debug info generated in 1.0s
659.13kB ( 2.93%) for other data
- 21.96MB in total
+ 21.96MB in total image size, 21.04MB in total file size
--------------------------------------------------------------------------------
Top 10 origins of code area: Top 10 object types in image heap:
4.03MB java.base 1.14MB byte[] for code metadata
@@ -201,6 +201,10 @@ The progress indicator is printed periodically at an increasing interval.
### Creating Image
In this stage, the native binary is created and written to disk.
Debug info is also generated as part of this stage (if requested).
+This section breaks down the total image size as well as [code area](#glossary-code-area) and [image heap](#glossary-image-heap) (see below for more details).
+The total image size is calculated before linking by summing the sizes of the code area, image heap, debug information (if requested and embedded in the binary), and other data.
+The total file size is the actual size of the image on disk after linking.
+Typically, the file size is slightly smaller than the image size due to additional link time optimizations.
#### Code Area
The code area contains machine code produced by the Graal compiler for all reachable methods.
diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ByteFormattingUtil.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ByteFormattingUtil.java
index 67996723d9ea..a35f87d6a30a 100644
--- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ByteFormattingUtil.java
+++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ByteFormattingUtil.java
@@ -30,8 +30,9 @@ public class ByteFormattingUtil {
private static final double BYTES_TO_GB = 1000d * 1000d * 1000d;
public static String bytesToHuman(long bytes) {
+ assert bytes >= 0;
if (bytes < BYTES_TO_KB) {
- return toHuman(bytes, "B");
+ return plainBytes(bytes, "B");
} else if (bytes < BYTES_TO_MB) {
return toHuman(bytes / BYTES_TO_KB, "kB");
} else if (bytes < BYTES_TO_GB) {
@@ -48,4 +49,9 @@ public static String bytesToHumanGB(long bytes) {
private static String toHuman(double value, String unit) {
return "%.2f%s".formatted(value, unit);
}
+
+ private static String plainBytes(long value, String unit) {
+ assert 0 <= value && value < BYTES_TO_KB;
+ return "%d%s".formatted(value, unit);
+ }
}
diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java
index 96754c5f959e..8fe78fb8b2c6 100644
--- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java
+++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java
@@ -734,6 +734,7 @@ protected void doRun(Map entryPoints, JavaMainSupport j
compileQueue.purge();
int numCompilations = codeCache.getOrderedCompilations().size();
+ int imageDiskFileSize = -1;
try (StopTimer t = TimerCollection.createTimerAndStart(TimerCollection.Registry.WRITE)) {
loader.watchdog.recordActivity();
@@ -755,6 +756,12 @@ protected void doRun(Map entryPoints, JavaMainSupport j
AfterImageWriteAccessImpl afterConfig = new AfterImageWriteAccessImpl(featureHandler, loader, hUniverse, inv, tmpDir, image.getImageKind(), debug);
featureHandler.forEachFeature(feature -> feature.afterImageWrite(afterConfig));
+ try {
+ // size changes during linking and afterConfig phase
+ imageDiskFileSize = (int) inv.getOutputFile().toFile().length();
+ } catch (Exception e) {
+ imageDiskFileSize = -1; // we can't read a disk file size
+ }
}
try (StopTimer t = TimerCollection.createTimerAndStart(TimerCollection.Registry.ARCHIVE_LAYER)) {
if (ImageLayerBuildingSupport.buildingSharedLayer()) {
@@ -762,7 +769,8 @@ protected void doRun(Map entryPoints, JavaMainSupport j
HostedImageLayerBuildingSupport.singleton().archiveLayer(imageName);
}
}
- reporter.printCreationEnd(image.getImageFileSize(), heap.getLayerObjectCount(), image.getImageHeapSize(), codeCache.getCodeAreaSize(), numCompilations, image.getDebugInfoSize());
+ reporter.printCreationEnd(image.getImageFileSize(), heap.getLayerObjectCount(), image.getImageHeapSize(), codeCache.getCodeAreaSize(), numCompilations, image.getDebugInfoSize(),
+ imageDiskFileSize);
}
}
diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java
index f618815ce85e..d17651603c89 100644
--- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java
+++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java
@@ -584,7 +584,7 @@ public void setDebugInfoTimer(Timer timer) {
this.debugInfoTimer = timer;
}
- public void printCreationEnd(int imageFileSize, int heapObjectCount, long imageHeapSize, int codeAreaSize, int numCompilations, int debugInfoSize) {
+ public void printCreationEnd(int imageFileSize, int heapObjectCount, long imageHeapSize, int codeAreaSize, int numCompilations, int debugInfoSize, int imageDiskFileSize) {
recordJsonMetric(ImageDetailKey.IMAGE_HEAP_OBJECT_COUNT, heapObjectCount);
Timer imageTimer = getTimer(TimerCollection.Registry.IMAGE);
Timer writeTimer = getTimer(TimerCollection.Registry.WRITE);
@@ -620,7 +620,11 @@ public void printCreationEnd(int imageFileSize, int heapObjectCount, long imageH
recordJsonMetric(ImageDetailKey.NUM_COMP_UNITS, numCompilations);
l().a(format, ByteFormattingUtil.bytesToHuman(otherBytes), Utils.toPercentage(otherBytes, imageFileSize))
.doclink("other data", "#glossary-other-data").println();
- l().a("%9s in total", ByteFormattingUtil.bytesToHuman(imageFileSize)).println();
+ l().a("%9s in total image size", ByteFormattingUtil.bytesToHuman(imageFileSize));
+ if (imageDiskFileSize >= 0) {
+ l().a(", %s in total file size", ByteFormattingUtil.bytesToHuman(imageDiskFileSize));
+ }
+ l().println();
printBreakdowns();
ImageSingletons.lookup(ProgressReporterFeature.class).afterBreakdowns();
printRecommendations();