原生镜像到底换来什么、付出什么?启动快多少、内存省多少?为什么原生镜像里按名字反射会失败?profile 在原生镜像里为什么不能运行时再改?这篇用同一个应用对比 JVM、JVM 加 AOT、原生镜像。
普通构建 mvn package:11 秒;app.jar 18.9 MB
原生构建 mvn -Pnative native:compile:102 秒(含下载依赖之外的全部步骤);/usr/bin/time -l 报告的最大常驻内存(含子进程中最大的一个)约 2737 MB
原生可执行文件 target/app:74.3 MB(一个文件,不需要 JDK);Spring AOT 生成的东西(target/spring-aot/main):classes, resources, sources
生成的 Java 源文件 52 个,例如:['AopAutoConfiguration__BeanDefinitions.java', 'ApplicationAvailabilityAutoConfiguration__BeanDefinitions.java', 'AutoConfigurationPackages__BeanDefinitions.java', 'BoundConfigurationProperties__BeanDefinitions.java']构建 102 秒、峰值内存约 2737 MB;产物是 74.3 MB 的单文件;Spring AOT 先生成 52 个 Java 源文件。
JVM:java -jar app.jar:端口可连接 中位 1023 ms(范围 971~1213);第 1 次请求 38.0 ms;常驻内存 启动后 185 MB,2 次请求后 196 MB JVM + Spring AOT:java -Dspring.aot.enabled=true -cp target/classes:target/spring-aot/main/classes:依赖:端口可连接 中位 839 ms(范围 725~1910);第 1 次请求 43.3 ms;常驻内存 启动后 171 MB,2 次请求后 173 MB 原生镜像:./target/app:端口可连接 中位 28 ms(范围 28~1349);第 1 次请求 7.3 ms;常驻内存 启动后 63 MB,2 次请求后 73 MB
启动 1023 ms → 28 ms;请求后常驻内存 196 MB → 73 MB;只用 Spring AOT 的 JVM 模式也略快。
JVM:java -jar app.jar:
GET /runtime → java.vm.name=OpenJDK 64-Bit Server VM;org.graalvm.nativeimage.imagecode=null
GET /reflect?cls=demo.Hidden → loaded demo.Hidden → Hidden 实例
GET /reflect?cls=demo.Hinted → loaded demo.Hinted → Hinted 实例
启动时加 --spring.profiles.active=dev:GET /beans → devMarker 存在=true;activeProfiles=[dev]
原生镜像:./target/app:
GET /runtime → java.vm.name=Substrate VM;org.graalvm.nativeimage.imagecode=runtime
GET /reflect?cls=demo.Hidden → FAIL ClassNotFoundException: demo.Hidden
GET /reflect?cls=demo.Hinted → loaded demo.Hinted → Hinted 实例
启动时加 --spring.profiles.active=dev:GET /beans → devMarker 存在=false;activeProfiles=[dev]没有提示的反射类被裁掉;Bean 定义与 profile 在构建期定死。
JVM:java -jar app.jar:62376 / 55517 / 62004 请求/秒;中位 62004;p99 2.2 / 3.4 / 2.2 ms;压测后常驻内存 302 MB 原生镜像:./target/app:54315 / 55327 / 54197 请求/秒;中位 54315;p99 2.8 / 2.9 / 3.2 ms;压测后常驻内存 78 MB
稳定之后 JVM 的吞吐更高,原生镜像省的是启动时间与内存。