Skip to content

Spring Boot 应用启动优化:Class Data Sharing (CDS) 与 AOT Cache 详解 🚀

引言:为什么需要启动优化?

想象一下这样的场景:你开发的 Spring Boot 应用在生产环境中需要频繁重启(比如滚动更新、故障恢复),每次启动都需要等待 10-30 秒甚至更长时间。这不仅影响用户体验,还可能导致服务不可用时间过长。

IMPORTANT

Java 应用启动慢的根本原因在于 JVM 需要重复执行以下操作:

  • 加载和解析类文件
  • 执行字节码验证
  • JIT 编译热点代码
  • 初始化应用上下文

Class Data Sharing (CDS) 和 AOT Cache 正是为了解决这些痛点而生的技术!

什么是 Class Data Sharing (CDS)?

CDS 是 JVM 提供的一项功能,它的核心思想是:将类的元数据预先处理并存储在共享归档文件中,避免重复的类加载和解析工作

CDS 的工作原理

CDS 的核心优势

启动时间优化

通过预处理类元数据,CDS 可以显著减少应用启动时间,特别是对于大型 Spring Boot 应用效果明显。

内存占用优化

多个 JVM 实例可以共享同一份类数据,减少整体内存占用。

实战:如何在 Spring Boot 中使用 CDS

步骤 1:提取应用并进行训练运行

首先,我们需要将 JAR 文件提取到目录中,然后进行训练运行:

bash
# 提取 Spring Boot 应用
java -Djarmode=tools -jar my-app.jar extract --destination application

# 进入提取的目录
cd application

# 进行训练运行,生成 CDS 归档文件
java -XX:ArchiveClassesAtExit=application.jsa \ 
     -Dspring.context.exit=onRefresh \ 
     -jar my-app.jar

NOTE

  • ArchiveClassesAtExit=application.jsa:指定生成的归档文件名
  • spring.context.exit=onRefresh:让应用在上下文刷新后立即退出,避免完整启动

步骤 2:使用 CDS 归档启动应用

bash
# 使用 CDS 归档文件启动应用
java -XX:SharedArchiveFile=application.jsa \ 
     -jar my-app.jar

完整的 Kotlin Spring Boot 示例

让我们看一个实际的 Spring Boot 应用示例:

kotlin
@SpringBootApplication
class MyApplication {
    
    @Bean
    fun applicationRunner(): ApplicationRunner {
        return ApplicationRunner { args ->
            println("🚀 应用启动完成!")
            // 在训练模式下,应用会在此处退出
        }
    }
}

fun main(args: Array<String>) {
    runApplication<MyApplication>(*args)
}
kotlin
@RestController
class HelloController {
    
    @GetMapping("/hello")
    fun hello(): String {
        return "Hello from CDS optimized application! 🎉"
    }
    
    @GetMapping("/health")
    fun health(): Map<String, String> {
        return mapOf(
            "status" to "UP",
            "timestamp" to Instant.now().toString()
        )
    }
}

自动化脚本示例

为了简化 CDS 的使用,我们可以创建一个自动化脚本:

点击查看完整的 CDS 自动化脚本
bash
#!/bin/bash

APP_JAR="my-app.jar"
EXTRACT_DIR="application"
CDS_ARCHIVE="application.jsa"

echo "🔧 开始 CDS 优化流程..."

# 清理之前的文件
rm -rf $EXTRACT_DIR
rm -f $CDS_ARCHIVE

# 步骤 1: 提取应用
echo "📦 提取应用文件..."
java -Djarmode=tools -jar $APP_JAR extract --destination $EXTRACT_DIR

# 步骤 2: 进入提取目录
cd $EXTRACT_DIR

# 步骤 3: 训练运行生成 CDS 归档
echo "🏃 执行训练运行..."
java -XX:ArchiveClassesAtExit=$CDS_ARCHIVE \
     -Dspring.context.exit=onRefresh \
     -jar $APP_JAR

# 步骤 4: 验证归档文件是否生成
if [ -f "$CDS_ARCHIVE" ]; then
    echo "✅ CDS 归档文件生成成功: $CDS_ARCHIVE"
    echo "🚀 现在可以使用以下命令启动优化后的应用:"
    echo "java -XX:SharedArchiveFile=$CDS_ARCHIVE -jar $APP_JAR"
else
    echo "❌ CDS 归档文件生成失败"
    exit 1
fi

AOT Cache:CDS 的继任者

从 Java 24 开始,AOT Cache 通过 JEP 483 成为 CDS 的继任者,提供更强大的优化能力。

AOT Cache vs CDS 对比

特性CDSAOT Cache
支持版本Java 8+Java 24+
优化范围类元数据共享类元数据 + JIT 编译结果
性能提升中等显著
配置复杂度简单中等

AOT Cache 实战使用

AOT Cache 的使用流程比 CDS 稍微复杂一些,但提供了更好的性能:

bash
# 步骤 1: 提取应用
java -Djarmode=tools -jar my-app.jar extract --destination application
cd application

# 步骤 2: 记录 AOT 配置
java -XX:AOTMode=record \ 
     -XX:AOTConfiguration=app.aotconf \ 
     -Dspring.context.exit=onRefresh \
     -jar my-app.jar

# 步骤 3: 创建 AOT 缓存
java -XX:AOTMode=create \ 
     -XX:AOTConfiguration=app.aotconf \ 
     -XX:AOTCache=app.aot \ 
     -jar my-app.jar

# 步骤 4: 使用 AOT 缓存启动应用
java -XX:AOTCache=app.aot -jar my-app.jar

TIP

AOT Cache 不仅包含类元数据,还包含 JIT 编译的结果,因此能提供更显著的性能提升。

性能对比与实际效果

让我们通过一个实际的性能测试来看看效果:

kotlin
@Component
class StartupTimeRecorder {
    
    private val startTime = System.currentTimeMillis()
    
    @EventListener
    fun onApplicationReady(event: ApplicationReadyEvent) {
        val startupTime = System.currentTimeMillis() - startTime
        println("🕐 应用启动耗时: ${startupTime}ms")
        
        // 记录内存使用情况
        val runtime = Runtime.getRuntime()
        val usedMemory = (runtime.totalMemory() - runtime.freeMemory()) / 1024 / 1024
        println("💾 内存使用: ${usedMemory}MB")
    }
}

典型性能提升数据

性能提升参考

根据实际测试,使用 CDS 或 AOT Cache 通常可以获得:

  • 启动时间:减少 20-40%
  • 内存占用:减少 10-20%
  • 首次响应时间:减少 15-30%

最佳实践与注意事项

1. 选择合适的技术

2. 部署策略建议

WARNING

重要提醒:当应用更新时,必须重新生成 CDS 归档或 AOT 缓存文件!

bash
# Dockerfile
FROM openjdk:21-jdk-slim

COPY my-app.jar /app/
WORKDIR /app

# 生成 CDS 归档
RUN java -Djarmode=tools -jar my-app.jar extract --destination application && \
    cd application && \
    java -XX:ArchiveClassesAtExit=application.jsa \
         -Dspring.context.exit=onRefresh \
         -jar my-app.jar

# 使用 CDS 启动
CMD ["java", "-XX:SharedArchiveFile=application/application.jsa", "-jar", "my-app.jar"]
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  template:
    spec:
      initContainers:
      - name: cds-generator
        image: my-app:latest
        command: ["/bin/sh", "-c"]
        args:
        - |
          java -Djarmode=tools -jar my-app.jar extract --destination /shared/application
          cd /shared/application
          java -XX:ArchiveClassesAtExit=application.jsa -Dspring.context.exit=onRefresh -jar my-app.jar
        volumeMounts:
        - name: shared-data
          mountPath: /shared
      containers:
      - name: app
        image: my-app:latest
        command: ["java"]
        args: ["-XX:SharedArchiveFile=/shared/application/application.jsa", "-jar", "my-app.jar"]
        volumeMounts:
        - name: shared-data
          mountPath: /shared
      volumes:
      - name: shared-data
        emptyDir: {}

3. 监控和验证

创建一个简单的监控端点来验证优化效果:

kotlin
@RestController
class OptimizationController {
    
    private val startupTime = ManagementFactory.getRuntimeMXBean().startTime
    
    @GetMapping("/optimization/stats")
    fun getOptimizationStats(): Map<String, Any> {
        val runtime = Runtime.getRuntime()
        val uptime = System.currentTimeMillis() - startupTime
        
        return mapOf(
            "uptime" to "${uptime}ms",
            "totalMemory" to "${runtime.totalMemory() / 1024 / 1024}MB",
            "usedMemory" to "${(runtime.totalMemory() - runtime.freeMemory()) / 1024 / 1024}MB",
            "freeMemory" to "${runtime.freeMemory() / 1024 / 1024}MB",
            "cdsEnabled" to isCdsEnabled(),
            "aotCacheEnabled" to isAotCacheEnabled()
        )
    }
    
    private fun isCdsEnabled(): Boolean {
        return System.getProperty("java.vm.info")?.contains("sharing") == true
    }
    
    private fun isAotCacheEnabled(): Boolean {
        return ManagementFactory.getRuntimeMXBean().inputArguments
            .any { it.startsWith("-XX:AOTCache") }
    }
}

总结

CDS 和 AOT Cache 是 Spring Boot 应用性能优化的重要工具:

CDS 适用场景

  • Java 8-23 环境
  • 需要稳定可靠的启动优化
  • 容器化部署场景

AOT Cache 适用场景

  • Java 24+ 环境
  • 追求最佳性能表现
  • 可以接受稍微复杂的配置

CAUTION

使用这些技术时要注意:

  • 应用更新后必须重新生成归档文件
  • 在容器环境中需要合理设计构建流程
  • 建议在生产环境部署前进行充分测试

通过合理使用 CDS 或 AOT Cache,你的 Spring Boot 应用将获得显著的启动性能提升,为用户提供更好的体验!🎉