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