背景:一次冷启动慢到被运维吐槽
五月初我把一个内部配置中心客户端容器化,K8s 里副本从 0 扩容到 10 时,启动要 8 秒多,滚动发布期间总有几秒请求打不到。运维说你这 Java 应用太重了。我琢磨着试一把 Spring Boot 3 的原生镜像——GraalVM 把应用直接编译成机器码,启动该是毫秒级。
AOT 处理:编译期和运行期的分工
原生镜像的核心是把 Spring 的「运行时反射、动态代理、资源加载」提前到构建期通过 AOT(Ahead-Of-Time)处理掉。Spring Boot 3 的 spring-boot-starter-aot 会在构建时生成运行时提示文件(reflect-config.json、resource-config.json 等),告诉 GraalVM 哪些类需要保留反射信息。
踩坑:反射与资源配置
第一版编译直接失败,报错 ClassNotFoundException 和 ReflectiveAccessException。原因是我用了 FastJSON 做动态字段解析,GraalVM 静态分析抓不到那些运行时才出现的类。两处要补:
- 反射:用
@RegisterReflectionForBinding或RuntimeHintsRegistrar显式注册。 - 资源:
application.yml之外,模板文件得在@ImportRuntimeHints里登记,否则打包后读不到。
@Configuration
@ImportRuntimeHints(MyHints::register)
class MyHints {
static void register(HintsRegistrar r) {
r.resources().registerPattern("templates/*.ftl");
}
}
构建命令与耗时
用 Maven 插件(需 GraalVM 22.3+,JDK 17 基础):
mvn -Pnative native:build
# 或 Spring Boot 插件
mvn clean package -Pnative
构建本身很慢,我本机 16C 跑了 4 分 12 秒(JVM 包只要 22 秒)。但产出是一个 53MB 的可执行文件,不依赖 JRE。
启动与内存实测
| 指标 | JVM(JDK 17) | 原生镜像 |
|---|---|---|
| 启动时间 | 8.3s | 0.18s |
| RSS 常驻内存 | 480MB | 86MB |
| 首次请求就绪 | 9.1s | 0.4s |
扩容从 10 副本 0→10,整体就绪时间从 80 秒降到不到 4 秒,滚动发布零抖动。
先到这
《Spring Boot 原生镜像实战:构建 50MB 的可执行文件》这块我前前后后踩了不止一次。今天先写这些,后面想到新的再补。