Spring Boot 4.1 + Java AOT Cache:把 Agent 服务冷启动和暖机做成可交付指标
今天聊一个偏 Java 运行时但很适合 Agent 服务的话题:AOT Cache。很多 Agent 性能优化只盯模型延迟,但线上 p99 抖动经常来自冷 pod、首次请求、类加载、Spring 容器初始化和 JIT 暖机。Spring Boot 4.1 已经把 Java 25+ AOT Cache 写进参考文档,适合我们把 Agent 冷启动从“看日志感觉快了”变成 CI/CD 里的制品、训练和指标。
分享日期:2026-07-01
主题:Java 25+ / JDK 26 / Project Leyden / Spring Boot 4.1.0 / AOT Cache / Spring AI 2.0.0 / Spring Cloud 2025.1.2 / Agent Cold Start
版本背景:截至 2026-07-01,Spring Boot 官方项目页显示4.1.0,Spring AI 官方项目页显示2.0.0,Spring Cloud 官方项目页显示2025.1.2;Spring Cloud 兼容矩阵仍标注2025.1.xOakwood 对应 Spring Boot4.0.x,真实项目组合应以 BOM、Release Notes 和 start.spring.io 生成结果为准。
1. 为什么今天值得关注
过去几周我们连续覆盖了 Spring AI 2.0 的 Tool Calling、Tool Search、Chat Memory、Structured Output、多模态、MCP、评估观测和安全补丁 Agent。今天换一个更偏 Java 运行时的热点:AOT Cache 如何改善 Spring Boot Agent 服务的冷启动和暖机。
很多团队做 Agent 时,第一反应是关注模型延迟:
- 模型首 token 慢不慢。
- RAG 向量检索慢不慢。
- 工具调用链路慢不慢。
- 多模态文件上传和转录慢不慢。
这些都重要,但线上还有一个更基础的延迟来源:新的 Java 实例刚启动时,本身还没有完成类加载、链接、Spring 容器初始化和 JIT 暖机。在 Kubernetes 自动扩缩容、蓝绿发布、金丝雀发布、Serverless scale-to-zero、抢占式节点迁移等场景里,这个问题会直接变成 p99 和 p999 的尾延迟。
Agent 服务比普通 CRUD 服务更容易踩中冷启动问题:
| 冷启动阶段 | 普通服务影响 | Agent 服务额外影响 |
|---|---|---|
| JVM 启动 | 类加载、链接、初始化 | AI SDK、HTTP client、JSON schema、工具定义更多 |
| Spring 容器刷新 | Bean 创建、自动配置 | ChatClient、Advisor、VectorStore、ToolCallback 初始化 |
| 首次请求 | Controller、序列化、数据库连接 | 首次构造 prompt、工具 schema、模型 client、RAG 检索 |
| 暖机期 | JIT 尚未达到稳定状态 | prompt 组装、结构化输出转换、工具执行路径都可能偏慢 |
一句话:AOT Cache 不是让大模型回答更快,而是让承载 Agent 的 Java/Spring 进程更快进入可服务状态,并更快接近稳定性能。
2. 版本坐标与官方事实
今天这篇分享基于以下官方事实:
- Project Leyden:OpenJDK 项目页说明,该项目的主要目标是改善 Java 程序的启动时间、达到峰值性能的时间和内存占用。项目页列出 JDK 24 已交付 AOT Class Loading & Linking,JDK 25 已交付 AOT Command-Line Ergonomics 与 AOT Method Profiling,JDK 26 已交付 AOT Object Caching with Any GC。
- JEP 514 / JDK 25:
-XX:AOTCacheOutput=app.aot简化了创建 AOT cache 的命令,把常见训练和缓存创建流程收敛成更易使用的一步。 - JEP 515 / JDK 25:AOT Method Profiling 把训练运行中收集到的方法执行 profile 放入 AOT cache,让生产运行更快进入有效 JIT 优化状态。
- JEP 516 / JDK 26:AOT Object Caching with Any GC 让 AOT cache 能与任意垃圾回收器协同,包括低延迟 ZGC,目标是避免在启动尾延迟和 GC 尾延迟之间二选一。
- Spring Boot 4.1.0 AOT Cache 文档:Spring Boot 支持 Java 25+ 的 AOT cache;如果使用 Java 25 之前的版本,需要使用 CDS。文档明确建议在可行时优先使用 AOT cache。
- Spring Boot JVM AOT 文档:Spring 的 AOT 生成初始化代码可以和 JVM AOT cache 组合使用,但它有固定 classpath、运行时 Bean 图不能变化等限制。
- Spring AI 2.0.0:官方项目页列出 Chat、Embedding、Structured Outputs、Tool Calling、Observability、Evaluation、Chat Memory、RAG、Spring Boot auto-configuration 和 starters 等能力。
这决定了今天的边界:本文讨论的是 Java/Spring Agent 服务的启动与暖机治理,不讨论模型推理本身的加速。模型调用、向量库、工具服务和网络仍然要用各自的缓存、限流、熔断和观测手段治理。
3. AOT Cache、Spring AOT、Native Image、CRaC/CDS 怎么区分
先把几个容易混淆的概念拆开。
| 技术 | 解决什么 | 适合 Agent 场景 | 主要代价 |
|---|---|---|---|
| JVM AOT Cache | 加快类加载、链接和暖机 | 需要保留 JVM/JIT 动态能力,同时改善冷启动 | 需要训练运行,缓存和 Java 版本、应用版本绑定 |
| Spring AOT | 生成 Spring 初始化代码,减少运行时推断 | Bean 图稳定、配置可在构建/训练前确定的服务 | classpath 固定,运行时 profile/conditional bean 有限制 |
| GraalVM Native Image | 极低冷启动、更小内存 | 极端冷启动、Serverless、边缘部署 | 构建更重,反射/动态代理/资源配置更严格 |
| CRaC | 从 checkpoint 恢复运行时状态 | 想保存完整已启动状态的服务 | 连接、线程、外部资源恢复语义复杂 |
| CDS | Java 25 前的类数据共享方案 | 旧 JDK 或过渡方案 | 能力比 AOT cache 窄,Java 25+ 优先 AOT cache |
对多数 Spring Boot Agent 服务来说,务实路线是:
- 先测普通 JAR 冷启动和首请求延迟。
- 再使用 JVM AOT Cache。
- 如果 Bean 图稳定,再叠加 Spring AOT。
- 如果仍有极端冷启动诉求,再评估 Native Image 或 CRaC。
不要一上来就把所有 Agent 都改成 Native Image。Agent 服务经常接入第三方 SDK、模型客户端、动态工具、JSON schema、MCP client、向量库和 observability agent,运行时动态性比普通服务更强。AOT Cache 的价值在于它先保留 JVM 运行模型,同时改善冷启动和暖机。
4. Spring Boot 4.1 的最小 AOT Cache 流程
Spring Boot 文档给出的核心流程是:先把应用解包成 extracted form,再做训练运行生成 cache,最后启动时加载 cache。
java -Djarmode=tools -jar my-app.jar extract --destination application
cd application
java -XX:AOTCacheOutput=app.aot -Dspring.context.exit=onRefresh -jar my-app.jar
java -XX:AOTCache=app.aot -jar my-app.jar
几个关键点:
- AOT cache 需要和 extracted form 一起使用,否则没有效果。
- cache 文件只有在应用未更新且 Java 版本相同的情况下才能复用。
-Dspring.context.exit=onRefresh适合生成只覆盖容器刷新阶段的低风险启动 cache。- 如果想覆盖更多业务热路径,可以在训练运行里执行受控 smoke traffic,但必须避免外部副作用。
对 Agent 服务来说,可以把训练分成两档:
| 训练档位 | 训练内容 | 适用场景 |
|---|---|---|
| 启动档 | Spring 容器刷新、自动配置、Actuator、ChatClient Bean、工具 Bean 初始化 | 第一阶段,低风险、容易标准化 |
| 暖机档 | 调用只读 mock 模型、mock vector store、工具 schema 构造、结构化输出转换 | 需要进一步降低首请求延迟 |
第一版建议先做启动档。它能覆盖大部分 class loading、Spring Bean 初始化、自动配置和基础库路径,且不会碰真实模型和业务工具。
5. Agent 服务训练运行不能乱跑
AOT cache 依赖训练运行。训练运行越接近生产,cache 越有价值;但训练运行一旦触发真实副作用,就会制造新风险。
Agent 服务训练运行要避开这些动作:
- 调真实大模型产生费用。
- 调真实 RAG 向量库扫描大量数据。
- 调真实业务工具创建工单、退款、发短信、发邮件、部署发布。
- 写入生产数据库或消息队列。
- 读取真实敏感用户会话作为训练样本。
更稳的训练 profile 可以这样设计:
spring:
profiles:
active: aot-training
agent:
training:
enabled: true
use-mock-model: true
use-mock-vector-store: true
allow-write-tools: false
warmup-scenarios:
- chat-client-build
- tool-schema-render
- structured-output-conversion
- health-and-startup-actuator
再加一个只在训练 profile 启用的 warmup runner:
@Component
@Profile("aot-training")
class AgentAotWarmupRunner implements ApplicationRunner {
private final ChatClient chatClient;
private final AgentToolRegistry toolRegistry;
private final ObjectMapper objectMapper;
AgentAotWarmupRunner(ChatClient.Builder builder,
AgentToolRegistry toolRegistry,
ObjectMapper objectMapper) {
this.chatClient = builder.build();
this.toolRegistry = toolRegistry;
this.objectMapper = objectMapper;
}
@Override
public void run(ApplicationArguments args) throws Exception {
toolRegistry.describeReadOnlyTools();
var sample = new AgentWarmupResult(
"READY",
List.of("mock citation"),
false);
objectMapper.writeValueAsString(sample);
chatClient.prompt()
.system("Warm up the agent runtime with mock dependencies only.")
.user("Return a small readiness answer.")
.call()
.content();
}
}
record AgentWarmupResult(
String decision,
List<String> citations,
boolean requiresHumanReview) {
}
这个 runner 的目标不是验证模型质量,而是让关键类、schema、序列化、advisor、tool callback 等路径在训练运行中出现。真实质量评估仍然应该交给评估集和 CI。
6. Spring AOT 可以叠加,但要尊重限制
Spring Boot 的 JVM AOT 文档说明,使用 Spring AOT 生成的初始化代码有利于启动时间;AOT cache 和 Spring AOT 可以组合来进一步改善启动。但 Spring AOT 有明确限制:
- classpath 在构建时固定。
- 应用里的 Bean 定义不能在运行时变化。
@Profile和 profile-specific 配置有局限。- 会影响 Bean 创建的属性条件,例如某些
@ConditionalOnProperty或.enabled属性,不适合作为运行时切换 Bean 图的手段。
这对 Agent 服务有直接影响。
很多 Agent 原型会把模型、工具和 advisor 做成“运行时随便开关”:
agent:
tools:
refund:
enabled: false
shipment:
enabled: true
models:
active: openai
如果这些开关会改变 Bean 是否存在,就不适合在 Spring AOT 运行时随意切。更稳的设计是:
- 构建/训练前确定 Bean 图。
- 运行时开关只控制策略,不控制 Bean 存不存在。
- 工具授权通过服务端 policy 判断,而不是动态注册/注销 Bean。
- 每个部署 profile 生成自己的 AOT cache,不跨 profile 复用。
示例:
record AgentRuntimePolicy(
Set<String> enabledToolNames,
Set<String> writeToolApprovalRequired,
String activeModelProfile) {
boolean canExposeTool(String toolName) {
return enabledToolNames.contains(toolName);
}
}
工具 Bean 可以稳定存在,但是否对某个租户、用户、会话可见,由 policy 决定。这样更适合 AOT,也更适合安全审计。
7. 容器镜像里怎么放 app.aot
AOT cache 的产物应该被当成构建制品,而不是容器启动时临时生成。一个简化的镜像结构可以这样理解:
/workspace/application/ my-app.jar BOOT-INF/ org/ app.aot
启动命令:
java -XX:AOTCache=app.aot -jar my-app.jar
CI/CD 里要把这些信息固化成制品元数据:
{
"artifact": "order-agent-api",
"applicationVersion": "2026.07.01.1",
"javaVersion": "25.0.2",
"springBootVersion": "4.1.0",
"springAiVersion": "2.0.0",
"aotCache": "app.aot",
"trainingProfile": "aot-training",
"classpathHash": "sha256:...",
"trainingScenarioVersion": "2026-07-01"
}
如果应用 JAR、依赖、Java 版本、训练 profile、Spring profile 变化,就应该重新生成 cache。不要把 app.aot 当成可以跨版本共享的“通用加速包”。
8. Kubernetes 和 Spring Cloud 场景:启动快不等于可接流量
Agent 服务启动更快以后,还要做好流量治理。因为“JVM 进程起来了”和“Agent 能稳定处理请求”不是同一件事。
推荐的就绪条件:
| 条件 | 检查什么 |
|---|---|
| JVM/Spring ready | 应用上下文刷新完成,Actuator health ready |
| Agent runtime ready | ChatClient、Advisor、ToolRegistry、VectorStore client 初始化完成 |
| 外部依赖 ready | 模型 provider、向量库、工具服务、配置中心连接可用 |
| warmup ready | 训练路径或启动后 warmup 完成 |
| policy ready | 工具权限、租户策略、模型路由配置加载完成 |
Spring Cloud 在这里的价值不是“帮你生成 AOT cache”,而是负责分布式治理:
| Spring Cloud 能力 | 在 AOT Cache Agent 服务中的作用 |
|---|---|
| Gateway | 只把流量导到 ready 的 Agent 实例,做限流、鉴权和灰度 |
| Config | 管理非 Bean 图级别的 runtime policy,避免运行时改变 AOT Bean 图 |
| Circuit Breaker | 模型 provider、vector store、tool service 不稳定时隔离故障 |
| Kubernetes | readiness/liveness、滚动发布、HPA、PodDisruptionBudget |
| Stream / Task | 触发离线预热、训练报告、冷启动基线采集 |
| Contract | 升级 cache、JDK、Boot、AI starter 后验证跨服务 API |
版本上仍要注意:Spring Cloud 项目页显示当前入口 2025.1.2,但兼容矩阵写的是 2025.1.x Oakwood 对应 Boot 4.0.x。如果 Agent 服务使用 Boot 4.1.x,要以 BOM、release notes 和实际构建验证为准,不要只按“最新版本”硬拼。
9. 冷启动 SLO:不要只看本地启动日志
AOT Cache 落地是否有效,不能只看本地控制台“快了几秒”。建议把冷启动做成可观测指标。
第一组是 Spring/JVM 指标:
- 进程启动到
ApplicationStartedEvent。 - 进程启动到
ApplicationReadyEvent。 - Actuator
/health/readiness首次通过时间。 - AOT cache 是否启用。
- 训练 cache 版本与应用版本是否匹配。
第二组是 Agent 指标:
- 首次
ChatClient调用前的准备耗时。 - 首次工具 schema 渲染耗时。
- 首次 structured output 转换耗时。
- 首次 RAG advisor 初始化耗时。
- 首次请求的 time-to-first-token。
可以加一个很简单的启动观测:
@Component
class AgentStartupEvents {
private final MeterRegistry meterRegistry;
private final long processStartedAt = System.nanoTime();
AgentStartupEvents(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}
@EventListener(ApplicationReadyEvent.class)
void onReady() {
long readyNanos = System.nanoTime() - processStartedAt;
meterRegistry.timer("agent.startup.ready")
.record(readyNanos, TimeUnit.NANOSECONDS);
}
}
再用一个只读 warmup endpoint 或内部 runner 标记 Agent readiness:
record AgentWarmupReport(
boolean chatClientReady,
boolean toolsReady,
boolean vectorStoreReady,
Duration duration) {
}
目标不是追求单个数字,而是给发布、扩缩容和回滚提供基线:
- 普通 JAR 冷启动 p95 是多少。
- AOT cache 后冷启动 p95 是多少。
- Spring AOT + AOT cache 后 p95 是多少。
- 首次 Agent 请求 p95 是否下降。
- 内存占用是否变化。
- 出错时是否能自动回退到无 cache 启动。
10. CI/CD 流水线建议
一个适合 Agent 服务的流水线可以这样拆:
Flowchart LR Build[Build JAR] --> Extract[Extract Boot App] Extract --> Train[Training Run] Train --> Cache[Create app.aot] Cache --> Test[Start With AOT Cache] Test --> Eval[Agent Smoke + Evaluation] Eval --> Image[Build OCI Image] Image --> Canary[Canary Deploy] Canary --> Metrics[Cold Start Metrics]
每一关的失败条件要明确:
| 阶段 | 阻断条件 |
|---|---|
| Build | 依赖未锁定、SBOM 缺失、测试失败 |
| Extract | executable jar 无法 extracted form 运行 |
| Training | 训练 profile 触发真实写工具、真实模型费用或外部副作用 |
| Cache | app.aot 未生成、Java 版本不匹配、cache 过大异常 |
| Start | -XX:AOTCache=app.aot 启动失败或无效 |
| Smoke | /actuator/health/readiness 不通过、ChatClient 基础路径失败 |
| Eval | 高风险 Agent 样本失败、工具策略失败 |
| Canary | 冷启动和首请求 p95 退化超过阈值 |
训练运行本身也要版本化:
agent-aot-training:
version: 2026-07-01
scenarios:
- name: boot-context-refresh
sideEffects: none
- name: chat-client-build
sideEffects: none
- name: tool-schema-render
sideEffects: none
- name: structured-output-conversion
sideEffects: none
forbidden:
- real-model-call
- write-tool-call
- production-database-write
- external-notification
这能避免训练脚本慢慢演变成一个没人敢动的隐式流程。
11. 什么时候不要急着上 AOT Cache
AOT Cache 很值得试,但不是所有服务都要马上改。
不建议优先投入的场景:
- 服务常驻、实例很少重启,冷启动不是 SLO 问题。
- 主要延迟完全来自外部模型或第三方工具,启动时间占比很小。
- 运行时频繁改变 classpath 或动态加载插件。
- 每个租户都有完全不同的 Bean 图,训练与生产路径差异很大。
- 还没有基本启动指标、首请求指标和发布回归集。
更适合优先投入的场景:
- Agent API 随流量自动扩缩容,冷 pod 经常接请求。
- 内部工具 Agent 部署在 Serverless 或 scale-to-zero 平台。
- 蓝绿/金丝雀发布频繁,滚动期间 p99 抖动明显。
- 首次 ChatClient、RAG、Tool Calling 路径明显慢于稳定状态。
- 团队还不想承受 Native Image 的动态性约束。
12. 实战落地清单
如果团队今天就要开始,可以按这个顺序推进:
- 记录当前普通 JAR 的
ApplicationReadyEvent时间、首请求时间和首 token 时间。 - 升级到 Java 25+ 测试环境,确认 Spring Boot 4.1 应用能用 extracted form 启动。
- 用
-XX:AOTCacheOutput=app.aot -Dspring.context.exit=onRefresh生成第一版 cache。 - 用
-XX:AOTCache=app.aot启动,确认 cache 有效且 readiness 正常。 - 把
app.aot、JDK 版本、classpath hash、训练 profile 写入制品元数据。 - 对 Agent 服务补
aot-trainingprofile,确保训练不触发真实模型和写工具。 - 对首请求路径增加 smoke:ChatClient 构建、tool schema、structured output、vector client。
- 对比普通 JAR、AOT cache、Spring AOT + AOT cache 三组数据。
- 在 canary 阶段观察冷启动 p95、首请求 p95、内存和错误率。
- 只有在收益稳定后,再考虑更复杂的 training traffic、CRaC 或 Native Image。
13. 今日结论
Java 25+ 的 AOT Cache 和 Spring Boot 4.1 的支持,让 Java Agent 服务多了一个很务实的优化方向:在不放弃 JVM/JIT 动态能力的前提下,把冷启动和暖机前移到训练与构建阶段。
对 Spring AI Agent 来说,这件事的价值不在于“模型更快”,而在于:
- 新 pod 更快 ready。
- 首次 Agent 请求更接近稳定状态。
- 扩缩容和发布期间 p99 更可控。
- 训练、启动、制品、指标可以进入 CI/CD 和 SLO。
真正落地时要记住三条边界:
- AOT Cache 绑定应用版本、classpath、Java 版本和训练路径,不能跨版本乱复用。
- Spring AOT 可以叠加,但要求 Bean 图和运行时配置更稳定。
- Agent 训练运行必须无副作用,不能把真实模型调用、写工具和生产数据带进 cache 生成流程。
最终目标不是炫启动日志,而是让 Agent 服务回答一个生产问题:当流量突然进来、实例刚刚扩容、版本刚刚滚动时,我能多快、稳定、可观测地开始服务?
参考资料
- Spring Boot AOT Cache:AOT Cache :: Spring Boot
- Spring Boot Ahead-of-Time Processing With the JVM:Ahead-of-Time Processing With the JVM :: Spring Boot
- OpenJDK Project Leyden:Project Leyden
- JEP 514 Ahead-of-Time Command-Line Ergonomics:JEP 514: Ahead-of-Time Command-Line Ergonomics
- JEP 515 Ahead-of-Time Method Profiling:JEP 515: Ahead-of-Time Method Profiling
- JEP 516 Ahead-of-Time Object Caching with Any GC:JEP 516: Ahead-of-Time Object Caching with Any GC
- OpenJDK JDK 26 Builds:OpenJDK JDK 26.0.1 GA Release
- Spring AI 项目页:Spring AI
- Spring Boot 项目页:Spring Boot
- Spring Cloud 项目页:Spring Cloud
更多推荐


所有评论(0)