Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 57 additions & 37 deletions docs/reference-manual/native-image/BuildOutput.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,27 @@ redirect_from: /reference-manual/native-image/BuildOutput/
Here you will find information about the build output of GraalVM Native Image.
Below is the example output when building a native executable of the `HelloWorld` class:


<!--
To update the output below:
$ cd substratevm-enterprise
Ensure to set JAVA_HOME to a labsjdk-ee
$ mx build
$ (stty cols 80 && mx helloworld -g)
Remove the `experimental option(s)` section
Remove the `# Printing compilation-target` and `# Printing native-library` lines
Replace the absolute paths at the end to start with `/home/janedoe/helloworld`
-->

```
================================================================================
GraalVM Native Image: Generating 'helloworld' (executable)...
================================================================================
[1/8] Initializing... (2.0s @ 0.19GB)
[1/8] Initializing... (4.4s @ 0.29GiB)
Builder configuration:
- Java version: 26+12, vendor version: GraalVM CE 26-dev+12.1
- Graal compiler: optimization level: 2, target machine: x86-64-v3
- C compiler: gcc (linux, x86_64, 15.2.1)
- Java version: 26+13, vendor version: Oracle GraalVM 26-dev+13.1
- Graal compiler: optimization level: 2, target machine: x86-64-v3, PGO: ML-inferred
- C compiler: gcc (linux, x86_64, 13.3.0)
- Assertions: enabled, system assertions: enabled
- 1 user-specific feature(s):
- com.oracle.svm.thirdparty.gson.GsonFeature
Expand All @@ -36,53 +48,61 @@ GraalVM Native Image: Generating 'helloworld' (executable)...
- Assertions: disabled (class-specific config may apply), system assertions: disabled
--------------------------------------------------------------------------------
Build resources:
- 14.69GiB of memory (47.0% of system memory, using all available memory)
- 20 thread(s) (100.0% of 20 available processor(s), determined at start)
[2/8] Performing analysis... [******] (3.4s @ 0.40GB)
3,297 types, 3,733 fields, and 15,247 methods found reachable
1,066 types, 36 fields, and 415 methods registered for reflection
58 types, 59 fields, and 52 methods registered for JNI access
0 downcalls and 0 upcalls registered for foreign access
- 30.00GiB of memory (48.0% of system memory, capped at 30GiB)
- 32 thread(s) (88.9% of 36 available processor(s), determined at start)
[2/8] Performing analysis... [*******] (3.7s @ 0.58GiB)
2,140 types, 1,939 fields, and 8,997 methods found reachable
775 types, 35 fields, and 244 methods registered for reflection
49 types, 35 fields, and 48 methods registered for JNI access
52 resource accesses registered with 107B total size
4 native libraries: dl, pthread, rt, z
[3/8] Building universe... (1.0s @ 0.60GB)
[4/8] Parsing methods... [*] (0.4s @ 0.62GB)
[5/8] Inlining methods... [****] (0.2s @ 0.59GB)
[6/8] Compiling methods... [**] (3.7s @ 0.66GB)
[7/8] Laying out methods... [*] (0.7s @ 0.60GB)
[8/8] Creating image... [**] (2.3s @ 0.65GB)
5.24MB (21.86%) for code area: 8,788 compilation units
7.67MB (32.01%) for image heap: 90,323 objects and 55 resources
9.43MB (39.34%) for debug info generated in 0.3s
11.05MB (46.13%) for other data
23.96MB in total image size, 13.31MB in total file size
[3/8] Building universe... (0.9s @ 0.74GiB)
[4/8] Parsing methods... [*] (1.6s @ 0.72GiB)
[5/8] Inlining methods... [***] (0.5s @ 0.66GiB)
[6/8] Compiling methods... [***] (9.9s @ 0.81GiB)
[7/8] Laying out methods... [*] (1.1s @ 0.67GiB)
[8/8] Creating image... [**] (2.5s @ 0.90GiB)
2.86MiB (21.13%) for code area: 4,078 compilation units
3.56MiB (26.33%) for image heap: 63,478 objects and 1 resource
6.01MiB (44.40%) for debug info generated in 0.4s
7.11MiB (52.55%) for other data
13.53MiB in total image size, 6.88MiB in total file size
--------------------------------------------------------------------------------
Top 10 origins of code area: Top 10 object types in image heap:
791.32kB java.base/java.util 1.41MB byte[] for code metadata
363.66kB java.base/java.lang 1.21MB byte[] for string data
323.39kB java.base/java.text 838.53kB java.base/java.lang.String
241.87kB java.base/java.util.stream 633.02kB o.g.n.~e/c.o.s.c.h.Dyna~anion
229.23kB java.base/java.util.regex 431.58kB heap alignment
214.23kB java.base/java.util.concurrent 428.26kB java.base/java.lang.Class
166.60kB o.g.n.~e/c.o.svm.core.code 323.23kB java.base/j.util.HashMap$Node
153.78kB java.base/java.time.format 284.47kB byte[] for general heap data
152.90kB java.base/java.math 232.06kB java.base/java.lang.Object[]
142.02kB o.g.n.~e/c.o.s.c.genscavenge 183.10kB java.base/j.u.HashMap$Node[]
2.32MB for 146 more packages 1.70MB for 966 more object types
342.94KiB java.base/java.util 820.99KiB byte[] for string data
289.94KiB java.base/java.lang 750.97KiB byte[] for code metadata
270.50KiB o.g.n.~e/c.o.svm.core.code 347.48KiB java.base/java.lang.String
189.67KiB o.g.n.~e/c.o.s.c.genscavenge 217.34KiB o.g.n.~e/c.o.s.c.h.Dyna~anion
129.09KiB java.base/j.util.concurrent 209.51KiB java.base/java.lang.Class
83.00KiB o.g.n.~e/c.o.s.c.j.functions 184.75KiB java.base/j.u.HashMap$Node
81.76KiB java.base/java.util.stream 115.53KiB java.base/char[]
76.73KiB o.g.n.~e/com.oracle.svm.core 107.66KiB java.base/j.i.u.SoftR~nceKey
60.32KiB o.g.n.~e/c.o.svm.core.thread 105.09KiB java.base/java.lang.Object[]
58.18KiB o.g.n.~e/c.o.svm.graal.stubs 88.63KiB java.base/j.u.c.Concu~p$Node
1.25MiB for 119 more packages 700.05KiB for 585 more object types
Use '--emit build-report' to create a report with more details.
--------------------------------------------------------------------------------
Security report:
- Binary includes Java deserialization.
- CycloneDX SBOM with 5 component(s) is embedded in binary (406B). 6 type(s) could not be associated to a component.
- Advanced obfuscation not enabled; enable with '-H:AdvancedObfuscation=""' (experimental support).
--------------------------------------------------------------------------------
Recommendations:
G1GC: Use the G1 GC ('--gc=G1') for improved latency and throughput.
PGO: Use Profile-Guided Optimizations ('--pgo') for improved throughput.
FUTR: Use '--future-defaults=all' to prepare for future releases.
HEAP: Set max heap for improved and more predictable memory usage.
CPU: Enable more CPU features with '-march=native' for improved performance.
--------------------------------------------------------------------------------
0.9s (6.1% of total time) in 54 GCs | Peak RSS: 1.82GB | CPU load: 13.25
1.3s (4.8% of total time) in 88 GCs | Peak RSS: 2.14GiB | CPU load: 18.03
--------------------------------------------------------------------------------
Build artifacts:
/home/janedoe/helloworld/gdb-debughelpers.py (debug_info)
/home/janedoe/helloworld/helloworld (executable)
/home/janedoe/helloworld/helloworld.debug (debug_info)
/home/janedoe/helloworld/sources (debug_info)
================================================================================
Finished generating 'helloworld' in 14.2s.
Finished generating 'helloworld' in 25.5s.
```

## Build Stages
Expand Down Expand Up @@ -160,9 +180,9 @@ The memory limit and number of threads used by the build process.
More precisely, the memory limit of the Java heap, so actual memory consumption can be higher.
Please check the [peak RSS](#glossary-peak-rss) reported at the end of the build to understand how much memory was actually used.
The actual memory consumption can also be lower than the limit set, as the GC only commits memory that it needs.
By default, the build process uses the dedicated mode (which uses 85% of system memory) in containers or CI environments (when the `$CI` environment variable is set to `true`), but never more than 32GB of memory.
By default, the build process uses the dedicated mode (which uses 85% of system memory) in containers or CI environments (when the `$CI` environment variable is set to `true`), but never more than 30GiB of memory.
Otherwise, it uses shared mode, which uses the available memory to avoid memory pressure on developer machines.
If less than 8GB of memory are available, the build process falls back to the dedicated mode.
If less than 8GiB of memory are available, the build process falls back to the dedicated mode.
Therefore, consider freeing up memory if your machine is slow during a build, for example, by closing applications that you do not need.
It is possible to override the default behavior and set relative or absolute memory limits, for example with `-J-XX:MaxRAMPercentage=60.0` or `-J-Xmx16g`.
`Xms` (for example, `-J-Xms9g`) can also be used to ensure a minimum for the limit, if you know the image needs at least that much memory to build.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,13 @@ public final class MemoryUtil {
private static final int MIN_AVAILABLE_MEMORY_THRESHOLD_GB = 8;

/*
* Builder uses at most 32GB to avoid disabling compressed oops (UseCompressedOops).
* Deliberately use GB (not GiB) to stay well below 32GiB when relative maximum is calculated.
* Builder uses at most 30GiB to avoid disabling compressed oops (UseCompressedOops).
* UseCompressedOops seems currently disabled on my machine at values > (32 GiB - 32 MiB), so we
* use 30 GiB to have some margin. The actual logic at
* https://github.com/openjdk/jdk/blob/jdk-26+10/src/hotspot/share/runtime/arguments.cpp#L1429
* depends on various factors so it seems unwise to hardcode that exact limit.
*/
public static final long MAX_HEAP_BYTES = 32_000_000_000L;
public static final long MAX_HEAP_BYTES = 30 * GiB_TO_BYTES;

public static List<String> heuristicMemoryFlags(HostFlags hostFlags, List<String> memoryFlags) {
/*
Expand Down Expand Up @@ -146,7 +149,7 @@ public static Pair<Long, String> maxMemoryHeuristic(long totalMemorySize, boolea
// Ensure max memory size does not exceed upper limit
if (maxMemory > MAX_HEAP_BYTES) {
maxMemory = MAX_HEAP_BYTES;
reason = percentageOfSystemMemoryText(maxMemory, totalMemorySize) + ", capped at 32GB";
reason = percentageOfSystemMemoryText(maxMemory, totalMemorySize) + ", capped at 30GiB";
}

// Handle memory flags
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,8 @@ public record HostFlags(
boolean hasGCTimeRatio,
boolean hasExitOnOutOfMemoryError,
boolean hasMaximumHeapSizePercent,
boolean hasUseParallelGC) {
boolean hasUseParallelGC,
boolean hasUseCompressedOops) {

public List<String> defaultMemoryFlags() {
List<String> flags = new ArrayList<>();
Expand All @@ -372,6 +373,12 @@ public List<String> defaultMemoryFlags() {
*/
flags.add("-XX:+ExitOnOutOfMemoryError");
}
if (hasUseCompressedOops) {
/*
* To print a warning if the max heap size is too large for compressed oops.
*/
flags.add("-XX:+UseCompressedOops");
}
return flags;
}
}
Expand Down Expand Up @@ -574,6 +581,7 @@ private HostFlags gatherHostFlags() {
boolean hasGCTimeRatio = false;
boolean hasExitOnOutOfMemoryError = false;
boolean hasUseParallelGC = false;
boolean hasUseCompressedOops = false;

ProcessBuilder pb = new ProcessBuilder();
sanitizeJVMEnvironment(pb.environment(), Map.of());
Expand Down Expand Up @@ -604,6 +612,8 @@ private HostFlags gatherHostFlags() {
hasMaximumHeapSizePercent = true;
} else if (line.contains(" UseParallelGC ")) {
hasUseParallelGC = true;
} else if (line.contains(" UseCompressedOops ")) {
hasUseCompressedOops = true;
}
}
}
Expand All @@ -623,7 +633,8 @@ private HostFlags gatherHostFlags() {
hasGCTimeRatio,
hasExitOnOutOfMemoryError,
hasMaximumHeapSizePercent,
hasUseParallelGC);
hasUseParallelGC,
hasUseCompressedOops);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,33 +25,45 @@
package com.oracle.svm.hosted;

public class ByteFormattingUtil {
private static final double BYTES_TO_KB = 1000d;
private static final double BYTES_TO_MB = 1000d * 1000d;
private static final double BYTES_TO_GB = 1000d * 1000d * 1000d;
// "123.12KiB".length() = 9, holds as long as it's not >= 1000GiB
private static final int MAX_WIDTH = 9;
public static final String RIGHT_ALIGNED_FORMAT = "%" + MAX_WIDTH + "s";

private enum Unit {
KiB(1024L),
MiB(1024L * 1024L),
GiB(1024L * 1024L * 1024L);

private final long value;

Unit(long value) {
this.value = value;
}
}

// We want to respect MAX_WIDTH and keep it concise,
// so we prefer to show 0.99MiB than 1010.00KiB (length 10).
public static String bytesToHuman(long bytes) {
assert bytes >= 0;
if (bytes < BYTES_TO_KB) {
return plainBytes(bytes, "B");
} else if (bytes < BYTES_TO_MB) {
return toHuman(bytes / BYTES_TO_KB, "kB");
} else if (bytes < BYTES_TO_GB) {
return toHuman(bytes / BYTES_TO_MB, "MB");
if (bytes < 1_000) {
return bytes + "B";
} else if (bytes < 1_000 * Unit.KiB.value) {
return toHuman(bytes, Unit.KiB);
} else if (bytes < 1_000 * Unit.MiB.value) {
return toHuman(bytes, Unit.MiB);
} else {
return bytesToHumanGB(bytes);
}
}

public static String bytesToHumanGB(long bytes) {
return toHuman(bytes / BYTES_TO_GB, "GB");
return toHuman(bytes, Unit.GiB);
}

private static String toHuman(double value, String unit) {
return "%.2f%s".formatted(value, unit);
private static String toHuman(long value, Unit unit) {
String string = "%.2f%s".formatted((double) value / unit.value, unit);
assert string.length() <= MAX_WIDTH || value >= 1000L * Unit.GiB.value;
return string;
}

private static String plainBytes(long value, String unit) {
assert 0 <= value && value < BYTES_TO_KB;
return "%d%s".formatted(value, unit);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ public class ProgressReporter {
public static final String DOCS_BASE_URL = "https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/BuildOutput.md";
private static final double EXCESSIVE_GC_MIN_THRESHOLD_MILLIS = TimeUtils.secondsToMillis(15);
private static final double EXCESSIVE_GC_RATIO = 0.5;
// Use a leading space like in the rest of Native Image output
private static final String BYTES_TO_HUMAN_FORMAT = " " + ByteFormattingUtil.RIGHT_ALIGNED_FORMAT;

private final NativeImageSystemIOWrappers builderIO;

Expand Down Expand Up @@ -598,7 +600,7 @@ public void printCreationEnd(int imageFileSize, int heapObjectCount, long imageH
Timer archiveTimer = getTimer(TimerCollection.Registry.ARCHIVE_LAYER);
stagePrinter.end(imageTimer.getTotalTime() + writeTimer.getTotalTime() + archiveTimer.getTotalTime());
creationStageEndCompleted = true;
String format = "%9s (%5.2f%%) for ";
String format = BYTES_TO_HUMAN_FORMAT + " (%5.2f%%) for ";
l().a(format, ByteFormattingUtil.bytesToHuman(codeAreaSize), ProgressReporterUtils.toPercentage(codeAreaSize, imageFileSize))
.doclink("code area", "#glossary-code-area").a(":%,10d compilation units", numCompilations).println();
int numResources = 0;
Expand Down Expand Up @@ -632,7 +634,7 @@ public void printCreationEnd(int imageFileSize, int heapObjectCount, long imageH
recordJsonMetric(ImageDetailKey.NUM_COMP_UNITS, numCompilations);
l().a(format, ByteFormattingUtil.bytesToHuman(otherBytes), ProgressReporterUtils.toPercentage(otherBytes, imageFileSize))
.doclink("other data", "#glossary-other-data").println();
l().a("%9s in total image size", ByteFormattingUtil.bytesToHuman(imageFileSize));
l().a(BYTES_TO_HUMAN_FORMAT + " in total image size", ByteFormattingUtil.bytesToHuman(imageFileSize));
if (imageDiskFileSize >= 0) {
l().a(", %s in total file size", ByteFormattingUtil.bytesToHuman(imageDiskFileSize));
}
Expand Down Expand Up @@ -723,14 +725,15 @@ private void printBreakdowns() {
int numHeapItems = heapBreakdown.getSortedBreakdownEntries().size();
long totalCodeBytes = codeBreakdown.values().stream().mapToLong(Long::longValue).sum();

p.l().a(String.format("%9s for %s more packages", ByteFormattingUtil.bytesToHuman(totalCodeBytes - printedCodeBytes), numCodeItems - printedCodeItems))
p.l().a(String.format(BYTES_TO_HUMAN_FORMAT + " for %s more packages", ByteFormattingUtil.bytesToHuman(totalCodeBytes - printedCodeBytes), numCodeItems - printedCodeItems))
.jumpToMiddle()
.a(String.format("%9s for %s more object types", ByteFormattingUtil.bytesToHuman(heapBreakdown.getTotalHeapSize() - printedHeapBytes), numHeapItems - printedHeapItems))
.a(String.format(BYTES_TO_HUMAN_FORMAT + " for %s more object types", ByteFormattingUtil.bytesToHuman(heapBreakdown.getTotalHeapSize() - printedHeapBytes),
numHeapItems - printedHeapItems))
.flushln();
}

private static String getBreakdownSizeString(long sizeInBytes) {
return String.format("%9s ", ByteFormattingUtil.bytesToHuman(sizeInBytes));
return String.format(BYTES_TO_HUMAN_FORMAT + " ", ByteFormattingUtil.bytesToHuman(sizeInBytes));
}

private void printRecommendations() {
Expand Down