bigywater.tech
Java · 393

Native Image:把 Java 应用编译成原生可执行文件

Native Image 把 Java 程序提前编译成独立的原生可执行文件。这篇用真实构建量出它的收益(启动与内存)、代价(构建时间、封闭世界的反射限制、构建时初始化),以及峰值性能与 JIT 相比的取舍。

5 ms vs 40 ms
原生镜像 Hello 与同一 JDK 上 java -cp 的启动中位数
ClassNotFoundException
没有登记反射元数据时,原生镜像里 Class.forName 的结果(JVM 上正常)
216 vs 829
stream 负载每毫秒完成次数:原生镜像与 JVM + C2

构建与体积

实测 · 本机执行输出
  native-image 退出码 0,构建用时 15.8 s
    GraalVM Native Image: Generating 'hello' (executable)...
    Garbage collector: Serial GC (max heap size: 80% of RAM)
    3,256 types,   3,721 fields, and  15,151 methods found reachable
    4.99MB (38.57%) for code area:     8,666 compilation units
    7.67MB (59.31%) for image heap:   88,441 objects and 55 resources
    Top 10 origins of code area:                                Top 10 object types in image heap:
    884.22kB svm.jar (Native Image)                               1.19MB byte[] for java.lang.String
    Finished generating 'hello' in 15.1s.
  可执行文件 hello:12.3 MiB(Hello.class 只有 835 字节;它把 JDK 里用到的部分、堆镜像与运行时一起编进去了)
  运行 ./hello:hello 0

启动与内存

实测 · 本机执行输出
  ./hello:中位数 5 ms(最快 5,最慢 8);峰值常驻内存 9.3 MiB
  java -cp out Hello:中位数 40 ms(最快 39,最慢 42);峰值常驻内存 99.6 MiB
  java -XX:TieredStopAtLevel=1 -cp out Hello:中位数 41 ms(最快 39,最慢 43)

反射与元数据

实测 · 本机执行输出
  JVM:Target 被反射创建了
  构建 Refl(没有任何反射配置)退出码 0,用时 15.6 s
  ./refl Target:Exception in thread "main" java.lang.ClassNotFoundException: Target …
  用 native-image-agent 在 JVM 上跑一遍:Target 被反射创建了;生成的文件 ['reachability-metadata.json']
  生成的配置里与 Target 相关的片段:{ "reflection": [ { "type": "Target", "methods": [ { "name": "<init>", "parameterTypes": [] } ] } ] }
  带上生成的配置重新构建 退出码 0,用时 15.9 s
  ./refl2 Target:Target 被反射创建了
  ./refl2 Hello(没有登记的类):Exception in thread "main" java.lang.ClassNotFoundException: Hello

构建时初始化

实测 · 本机执行输出
  默认构建(应用类默认在运行时初始化)的构建输出里没有静态初始化的打印;--initialize-at-build-time=InitTime 的构建输出里有:[静态初始化执行] T = 1791194181330
  ./init_rt 运行:[静态初始化执行] T = 1791194196219 | main 运行:现在 - T = 1500 ms
  ./init_bt 运行:main 运行:现在 - T = 18347 ms
  过 2 秒再运行 ./init_bt:main 运行:现在 - T = 21889 ms(T 与构建时相同,所以“现在 - T”越来越大)

峰值吞吐

实测 · 本机执行输出
  构建 Bench 用时 15.9 s,文件 12.4 MiB
  [原生镜像] stream 每毫秒 216.0 次(中位数);  poly2 每毫秒 435.7 次(中位数);  hash 每毫秒 291.8 次(中位数)
  [JVM + Graal JIT] stream 每毫秒 646.3 次(中位数);  poly2 每毫秒 418.9 次(中位数);  hash 每毫秒 234.3 次(中位数)
  [JVM + C2] stream 每毫秒 829.0 次(中位数);  poly2 每毫秒 1329.7 次(中位数);  hash 每毫秒 249.3 次(中位数)

分步演示

Native Image
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。GraalVM Community 25.0.2+10.1,macOS arm64,Xcode clang 21;构建机与运行机是同一台,默认 Serial GC、没有 PGO(社区版不支持)
    • 没有验证:Linux 上的静态链接与 musl 镜像、PGO(Oracle GraalVM)、G1 GC 的原生镜像(macOS 不支持)、Spring Boot / Quarkus 等框架的原生支持、较大应用的构建时间与内存、代理与资源(Proxy / getResource)配置、调试与诊断(JFR、堆转储)
    京ICP备2026067502号-1京公网安备11010502063487号