graalvm-native-image · git:20261028.082d2f4 · 2026-10-28 · sha256 88e50e978bc758d0
graalvm-native-image git:20261028.082d2f4A
Immutable. This exact content never changes and is served at /api/v1/blob/88e50e978bc758d0.
--- name: graalvm-native-image description: > GraalVM Native Image: closed-world reachability, dynamic-feature metadata, class initialization and image-heap state, CPU targeting, GC and PGO choices, observability, and fair comparison with HotSpot. Use when deciding whether AOT fits a workload, diagnosing build or runtime-only failures, or validating startup, footprint, latency, throughput, build-cost, portability, and security trade-offs. Does not cover Graal as a JVM JIT (graalvm-jit), JVM-preserving startup strategies (startup-cds-crac-leyden), or HotSpot warm-up mechanics (jit-compilation). --- # GraalVM Native Image ## Purpose Choose Native Image only when its measured lifecycle economics and runtime behavior fit the workload. Ordinary closed-world AOT avoids runtime compilation and can materially improve cold startup and initial footprint, but it exchanges HotSpot's adaptive optimization and dynamic runtime for a longer, more resource-intensive build, a closed-world compatibility contract, and different GC and diagnostic capabilities. Neither Native Image nor a warmed JVM has a universal throughput or latency advantage. Prevent binaries that pass a happy-path test but fail on unregistered dynamic access, capture build-host state in the image heap, require CPU features absent from production, exceed container memory, or win a benchmark only because the JVM was measured before steady state. ## Decision framework | Dominant constraint | Native Image becomes attractive when | Prefer HotSpot or validate very carefully when | | ----------------------- | --------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | | Cold start | cold starts are frequent and user-visible, or process lifetime is short | instances are long-lived and startup is outside the SLO | | Footprint/density | measured total RSS and fleet density improve at the required load | heap dominates RSS or native metadata/code offsets the saving | | Throughput/tail latency | the native build meets the SLO at target saturation, ideally with a representative PGO profile | adaptive optimization, large heaps, or mature HotSpot collectors are material | | Dynamic behavior | framework and libraries publish tested metadata; the dynamic surface is bounded | arbitrary plugins, runtime bytecode generation, agents, or unknown reflection are essential | | Delivery | reproducible native builds and per-platform artifacts fit the pipeline | build time, builder RAM, patch cadence, or target matrix dominates delivery cost | | Diagnostics | required JFR, `jcmd`, heap-dump, debugger, and profiler workflows work on the exact binary/platform | operations depend on unsupported HotSpot events or tooling | Estimate a break-even point instead of classifying only by workload label: ```text benefit per start × starts over artifact lifetime + density or latency value under representative load > added build, test, artifact, compatibility, and operational cost ``` Reject the migration if correctness coverage or rollback cannot be made credible, even when the startup number is compelling. ## Workflow Use the steps relevant to the current decision, diagnosis or artifact change. Reuse supplied builds, measurements and established acceptance criteria before repeating work. A justified decision to retain the JVM, or a diagnosis from adequate existing evidence, need not create a new binary, capture or canary. Test and rollout gates apply when producing or promoting a change. 1. **State the decision and version boundary.** Record GraalVM distribution and release, target OS/architecture/libc, framework/plugin versions, required dynamic features, workload lifetime, SLO, memory limit, and deployment CPU floor. Recheck flags against the installed release; Native Image options and edition-specific features change. Inspect the project's compiler release/toolchain and CI image; this skill's GraalVM 25.0/25.1 examples do not authorize a Java, framework, distribution, or experimental-feature upgrade. 2. **Reuse or build the framework-supported baseline.** Prefer the framework's AOT integration and Native Build Tools over a hand-written command. It may generate substitutions and reachability metadata that a raw agent run cannot infer. The native build itself can execute application initializers and build hooks. Use an isolated, authorized build environment without deployment secrets or unintended access to live dependencies; tracing-agent runs are not the only phase that can perform application effects. 3. **Audit dynamic behavior.** Combine library-provided metadata, framework-generated metadata, targeted manual entries, and tracing-agent runs over representative integration tests. The agent records observed accesses; it does not prove completeness. Use exact metadata handling and a test mode that reports or exits on missing registration. 4. **Audit class initialization and image-heap state.** Application classes normally initialize at runtime unless proven safe or explicitly configured for build time. Inspect the build report or `-H:+PrintClassInitialization`; move environment-, secret-, clock-, filesystem-, network-, locale-, or host-dependent work to runtime. Prefer ordinary lazy construction/DI over internal substitutions, but verify when construction actually runs; a method or DI boundary alone does not prevent framework/build code from evaluating it into the image heap. 5. **Select runtime and target deliberately.** Confirm whether the distribution supports the desired GC and PGO. Choose a portable `-march` floor for heterogeneous fleets, set a container- appropriate heap ceiling, and decide whether glibc, musl, or dynamic linking matches patching and compatibility requirements. 6. **Build reproducibly and inspect the result.** Pin the builder image/toolchain and inputs; retain the build report, effective arguments, SBOM, target CPU, debug-symbol policy, binary hash, build time, and peak builder RSS. Treat expert `-H:` flags as release-coupled and prove that each is accepted and still needed. Trace applied JAR configuration and plugin/profile overrides before accepting the intended optimization level; use [effective-option inspection](references/build-and-measurement.md#resolve-effective-build-options) when the command or measured behavior contradicts it. 7. **Test the produced artifact against its supported contract.** Exercise relevant success, invalid input, optional integrations, reflection/serialization/JNI/FFM, resources, locales/time zones, TLS/security providers, shutdown, signals, memory pressure, and the oldest deployment CPU. Run tests in the actual container/base image rather than only on the build host. For JSSE TLS, distinguish the embedded build-time trust store from an explicit runtime store; test rejection as well as acceptance and the intended certificate-update path. 8. **Measure both candidates under the same experiment.** Compare time to first successful response, startup distribution, idle and loaded RSS, allocation/GC behavior, throughput, latency percentiles near saturation, CPU per operation, image size/pull time, and build cost. Warm the JVM deliberately and report cold and steady-state results separately. Repeat across forks and preserve raw results. 9. **Canary with rollback.** Monitor correctness errors, OOM/GC behavior, CPU, saturation, tail latency, startup, and diagnostic coverage. Keep the JVM artifact deployable until native-only paths and incident procedures have survived production traffic. ## Non-negotiable rules - **Version-gate fallback advice.** GraalVM 25.1 removed fallback images; `--no-fallback` is deprecated and has no effect there and later. On older supported releases, use it to prevent a JVM-launching fallback artifact. Never infer “standalone” from the filename alone. - **Reachable is not the same as dynamically accessible.** Static analysis can include a class while omitting reflective members, JNI access, serialization constructors, proxies, foreign access, or resources. Prefer narrow conditional metadata and validate against the schema for the target release. Use a release-pinned published schema when the installation does not bundle it. For `typeReached`, verify activation before the first dynamic access; inclusion alone is insufficient. Broad `allDeclared*` entries can hide gaps and inflate the image. - **Treat agent output as evidence, not specification.** Merge runs from representative tests; review diffs; remove caller noise; retain deterministic hand-authored entries for intentional contracts. A health-check-only trace is not coverage. - **Do not blanket-initialize packages at build time.** Native Image initializes important runtime and JDK classes at build time and can automatically initialize application classes proven safe; other application classes remain runtime-initialized unless configured. Build-time state must be host-independent, non-secret, deterministic, and safe to persist for the artifact lifetime. - **Prefer public configuration over SVM internals.** `@TargetClass`, `@Alias`, and `@RecomputeFieldValue` are implementation APIs. Use a framework extension, public Feature API, runtime initialization, or code change first; pin and test the GraalVM release if substitution is unavoidable. - **Treat `-H:` options as unstable expert controls.** Discover them with the installed tool's expert-option help, capture effective arguments in CI, and retest after every upgrade. Do not silently carry copied flags across releases. - **Separate file size from runtime memory.** Platform-supported stripping or separate debug symbols can reduce shipped bytes. Executable packers such as UPX alter packaging, not live Java-heap demand, and can interfere with code signing, scanning, page sharing, startup, crash analysis, or platform policy; use only after measuring and validating the delivery environment. - **Compile for the fleet CPU floor.** Current AMD64 defaults use `x86-64-v3`; AArch64 defaults are release-specific. Use `-march=compatibility` for broad portability or an explicit fleet baseline, and test on the oldest supported machine. Use `-march=native` only for homogeneous compatible build and deployment CPUs. - **Set memory limits explicitly.** With no explicit maximum, the Serial runtime heap can use up to 80% of detected physical memory; that is a ceiling, not expected RSS. GC copying, thread stacks, code, image heap, native libraries, and allocator behavior also consume memory. Validate cgroup detection and leave headroom below the container limit. - **Treat PGO as workload-specific.** Oracle GraalVM provides PGO; Community Edition does not in the current JDK 25 line. Train on representative traffic, keep training and evaluation data separate, compare the same source/toolchain/build configuration, and retrain when traffic or hot code changes. Do not promise a fixed percentage improvement. - **Verify observability on the exact release and platform.** Native Image has JFR and `jcmd` support, but event and command coverage differs from HotSpot and platform support evolves. For example, Windows JFR recording arrived in GraalVM 25.1 while `jcmd` remains unavailable there in the current documentation. Enable only the required monitoring features and rehearse incident capture before production. - **Rebuild for security updates.** A native artifact embeds reachable JDK/runtime and often native library code. Track the builder JDK, toolchain, libc/linking mode, metadata repository, and base image; rebuild and redeploy when any embedded dependency requires a fix. A smaller reachable set is not a security guarantee. ## Failure triage ```text Build fails or is OOM-killed -> distinguish closed-world/unsupported feature from builder RAM or parallelism -> inspect reachability path, build report, peak RSS and container limit Binary fails only on one path -> enable exact metadata handling and missing-registration reporting -> distinguish absent metadata, inactive condition, and absent class/resource/native library Binary starts on CI but not production -> compare target CPU, OS/libc, linked libraries, class-initialized state and secrets/config RSS or tail latency violates the SLO -> separate Java heap, image heap, native allocations, stacks and GC transient headroom -> reproduce at target concurrency with explicit heap and the production GC ``` Use [troubleshooting](references/troubleshooting.md) for the detailed symptom matrix. Return the decision or diagnosed cause with the exact target/toolchain, supporting evidence, proposed configuration or fix, and native-artifact checks actually executed. Separate pending compatibility and performance measurements from verified results; a successful JVM test or native build alone does not establish runtime correctness or migration benefit. ## References - [Build and measurement recipes](references/build-and-measurement.md) — use while creating a reproducible artifact or performance comparison. - [Closed world and metadata](references/closed-world-and-metadata.md) — use when reasoning about reachability, initialization, TLS trust-store ownership, distribution features, or observability. - [Troubleshooting](references/troubleshooting.md) — use when a build, binary, or measurement fails. - [Native Image reference manual](https://www.graalvm.org/latest/reference-manual/native-image/) - [Native Image release notes](https://www.graalvm.org/release-notes/) - [Reachability Metadata repository](https://github.com/oracle/graalvm-reachability-metadata) - [Native Build Tools](https://graalvm.github.io/native-build-tools/latest/) - [GraalVM licensing FAQ](https://www.graalvm.org/faq/)