AppCDS 应用类数据共享

理解 AppCDS,是实现云原生 Java 优化的重要技能。

AppCDS vs CDS

flowchart LR
    subgraph CDS
        A["JDK 类"] --> B["共享归档"]
    end
    
    subgraph AppCDS
        C["JDK 类"] --> D["共享归档"]
        E["应用类"] --> D
    end
    
    style D fill:#00b894
特性CDSAppCDS
共享 JDK 类
共享应用类
共享第三方库
归档创建自动需要手动创建

AppCDS 工作流程

flowchart TB
    A["步骤 1:运行应用\n收集类列表"] --> B["步骤 2:创建共享归档"]
    B --> C["步骤 3:使用共享归档"]
    C --> D["加速启动"]

步骤一:收集类列表

# 收集应用运行时的类
java -XX:+UseAppCDS \
     -XX:DumpLoadedClassList=classes.lst \
     -cp myapp.jar \
     -jar myapp.jar

步骤二:创建共享归档

# 创建包含应用类的共享归档
java -XX:+UseAppCDS \
     -XX:SharedClassListFile=classes.lst \
     -XX:SharedArchiveFile=app.jsa \
     -cp myapp.jar \
     -jar myapp.jar

步骤三:使用共享归档

# 运行应用,使用共享归档
java -XX:+UseAppCDS \
     -XX:SharedArchiveFile=app.jsa \
     -cp myapp.jar \
     -jar myapp.jar

AppCDS 参数

核心参数

参数说明
-XX:+UseAppCDS启用 AppCDS(JDK 9+ 默认)
-XX:DumpLoadedClassList导出类列表文件
-XX:SharedClassListFile指定类列表文件
-XX:SharedArchiveFile指定共享归档文件

归档控制

参数说明
-XX:ArchiveClassesAtExit应用退出时创建归档
-XX:IncludeAllAppCDS包含所有类

完整示例

示例一:Spring Boot 应用

# 1. 运行应用,收集类
java -XX:+UseAppCDS \
     -XX:DumpLoadedClassList=spring.lst \
     -jar myapp.jar

# 2. 创建归档
java -XX:+UseAppCDS \
     -XX:SharedClassListFile=spring.lst \
     -XX:SharedArchiveFile=spring.jsa \
     -jar myapp.jar

# 3. 使用归档启动
java -XX:+UseAppCDS \
     -XX:SharedArchiveFile=spring.jsa \
     -jar myapp.jar

示例二:多模块应用

# 1. 收集所有模块的类
java -XX:+UseAppCDS \
     -XX:DumpLoadedClassList=all.lst \
     -cp "module1.jar:module2.jar:module3.jar" \
     com.example.MainClass

# 2. 创建归档
java -XX:+UseAppCDS \
     -XX:SharedClassListFile=all.lst \
     -XX:SharedArchiveFile=modules.jsa \
     -cp "module1.jar:module2.jar:module3.jar" \
     com.example.MainClass

# 3. 使用归档
java -XX:+UseAppCDS \
     -XX:SharedArchiveFile=modules.jsa \
     -cp "module1.jar:module2.jar:module3.jar" \
     com.example.MainClass

AppCDS 与 Gradle

build.gradle 配置

// build.gradle
plugins {
    id 'java'
    id 'application'
}

application {
    mainClass = 'com.example.MyApp'
}

task createAppCDS(type: JavaExec) {
    // 创建归档
    args '-XX:+UseAppCDS',
         '-XX:DumpLoadedClassList=classes.lst',
         '-cp', sourceSets.main.runtimeClasspath.asPath,
         application.mainClass
}

AppCDS 与 Maven

pom.xml 配置

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-antrun-plugin</artifactId>
    <executions>
        <execution>
            <id>create-appcds</id>
            <phase>package</phase>
            <configuration>
                <target>
                    <!-- 收集类 -->
                    <exec executable="java">
                        <arg value="-XX:+UseAppCDS"/>
                        <arg value="-XX:DumpLoadedClassList=classes.lst"/>
                        <arg value="-jar">${project.build.directory}/${project.build.finalName}.jar</arg>
                    </exec>
                </target>
            </configuration>
        </execution>
    </executions>
</plugin>

Docker 中的 AppCDS

Dockerfile 示例

# 多阶段构建

# 构建阶段
FROM eclipse-temurin:17 AS builder
COPY myapp.jar /app/
WORKDIR /app

# 创建 AppCDS 归档
RUN java -XX:+UseAppCDS \
         -XX:DumpLoadedClassList=classes.lst \
         -jar myapp.jar && \
    java -XX:+UseAppCDS \
         -XX:SharedClassListFile=classes.lst \
         -XX:SharedArchiveFile=app.jsa \
         -jar myapp.jar

# 运行阶段
FROM eclipse-temurin:17-jre-alpine
COPY --from=builder /app/app.jsa /app/
COPY --from=builder /app/myapp.jar /app/
WORKDIR /app

CMD ["java", "-XX:+UseAppCDS", \
     "-XX:SharedArchiveFile=/app/app.jsa", \
     "-jar", "/app/myapp.jar"]

动态类加载

问题

AppCDS 只共享启动时加载的类,动态加载的类无法共享。

解决方案

# 使用 -XX:+IncludeAllAppCDS
java -XX:+UseAppCDS \
     -XX:+IncludeAllAppCDS \
     -XX:SharedArchiveFile=app.jsa \
     -cp myapp.jar \
     -jar myapp.jar

性能对比

启动时间对比

场景无 AppCDS有 AppCDS提升
Spring Boot3.5 秒1.5 秒57%
微服务2.0 秒0.8 秒60%
CLI 工具0.5 秒0.2 秒60%

内存占用对比

JVM 实例无 AppCDS有 AppCDS
1100MB40MB
2100MB40MB
3100MB40MB

AppCDS 的限制

1. 类加载器限制

AppCDS 只支持默认类加载器。

2. 动态类加载

动态加载的类需要特殊处理。

3. 归档大小

归档太大会影响性能。

最佳实践

1. 定期更新归档

# 应用更新后重新创建归档
java -XX:+UseAppCDS \
     -XX:DumpLoadedClassList=classes.lst \
     -jar myapp-new.jar && \
java -XX:+UseAppCDS \
     -XX:SharedClassListFile=classes.lst \
     -XX:SharedArchiveFile=app-new.jsa \
     -jar myapp-new.jar

2. 排除不必要的类

# 只包含必要的类
java -XX:+UseAppCDS \
     -XX:DumpLoadedClassList=classes.lst \
     -cp myapp.jar \
     -jar myapp.jar

# 编辑类列表,移除不需要的类
# vi classes.lst

3. 验证归档

# 验证归档是否被使用
java -Xlog:class+load=info \
     -XX:+UseAppCDS \
     -XX:SharedArchiveFile=app.jsa \
     -jar myapp.jar 2>&1 | grep "shared"

AppCDS 与 GraalVM

AppCDS 和 GraalVM 是互补的技术:

特性AppCDSGraalVM
启动时间减少 50-60%减少 90%+
峰值性能不影响可能略低
二进制大小不变显著增大
兼容性完全兼容需要适配

未来发展

Project Leyden

Project Leyden 将进一步改进类数据共享:

  • 更好的归档压缩
  • 更智能的类选择
  • 与 AOT 编译集成