今天聊一个偏 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.x Oakwood 对应 Spring Boot 4.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 服务来说,务实路线是:

  1. 先测普通 JAR 冷启动和首请求延迟。
  2. 再使用 JVM AOT Cache。
  3. 如果 Bean 图稳定,再叠加 Spring AOT。
  4. 如果仍有极端冷启动诉求,再评估 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. 实战落地清单

如果团队今天就要开始,可以按这个顺序推进:

  1. 记录当前普通 JAR 的 ApplicationReadyEvent 时间、首请求时间和首 token 时间。
  2. 升级到 Java 25+ 测试环境,确认 Spring Boot 4.1 应用能用 extracted form 启动。
  3. 用 -XX:AOTCacheOutput=app.aot -Dspring.context.exit=onRefresh 生成第一版 cache。
  4. 用 -XX:AOTCache=app.aot 启动,确认 cache 有效且 readiness 正常。
  5. 把 app.aot、JDK 版本、classpath hash、训练 profile 写入制品元数据。
  6. 对 Agent 服务补 aot-training profile,确保训练不触发真实模型和写工具。
  7. 对首请求路径增加 smoke:ChatClient 构建、tool schema、structured output、vector client。
  8. 对比普通 JAR、AOT cache、Spring AOT + AOT cache 三组数据。
  9. 在 canary 阶段观察冷启动 p95、首请求 p95、内存和错误率。
  10. 只有在收益稳定后,再考虑更复杂的 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 服务回答一个生产问题:当流量突然进来、实例刚刚扩容、版本刚刚滚动时,我能多快、稳定、可观测地开始服务?

参考资料

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐