CDS and AppCDS: Class Data Sharing for Faster JVM Startup
A guide to Class Data Sharing in the JVM, covering how CDS and AppCDS work, how to create shared archives, and how they reduce startup time and memory footprint.
CDS and AppCDS: Class Data Sharing for Faster JVM Startup
Every time a JVM starts, it spends time loading the same classes over and over. The Java runtime itself, the standard library classes in java.base, and your own application classes that have not changed since the last run: all of them get loaded, verified, and laid out in memory from scratch. For short-lived processes or serverless functions where startup latency matters, this is a meaningful tax.
Class Data Sharing (CDS) addresses this by dumping the internal representation of already-loaded classes into a shared archive. The next JVM that starts can memory-map that archive and skip the loading and parsing step for those classes entirely. The result is faster startup and reduced memory footprint because multiple JVM processes can share the same class data in memory.
CDS has been in the JVM since Java 5. AppCDS, introduced in Java 8 update 191, extended it to include application classes. Understanding how to use both effectively can shave hundreds of milliseconds off your startup time.
This covers how CDS and AppCDS work under the hood, how to create and use shared archives, and what the practical tradeoffs are. For related JVM tuning, see the container memory limits post.
Introduction
Every JVM startup pays a tax in class loading time. Core JDK classes from java.base, your application classes, and every library on the classpath: all get parsed, verified, and laid out in memory from scratch on every process start. For short-lived processes like serverless functions, CLI tools that run for seconds, or microservices that restart frequently, this startup cost can dominate total execution time. A function that runs for 500ms but spends 400ms starting is the entire story of optimizing startup latency.
Class Data Sharing (CDS) addresses this by extracting the internal representation of already-loaded classes into a shared archive file. On subsequent JVM starts, the process memory-maps this file and skips the parsing step for archived classes entirely. Multiple JVM processes on the same machine can share the mapped pages in physical memory, reducing RSS per process. CDS has been in the JVM since Java 5, and AppCDS (introduced in Java 8) extended the mechanism to include application classes and libraries beyond the core JDK.
This post explains how CDS and AppCDS work under the hood, when they provide meaningful benefit versus when the maintenance overhead outweighs the gains, how to create and validate shared archives, and the practical tradeoffs of baking archives into container images versus generating them dynamically. For serverless and short-lived process scenarios, AppCDS is one of the highest-leverage JVM optimizations available.
When to Use
CDS and AppCDS help most when startup time is a real constraint, not just a theoretical one. Serverless functions, CLI tools that run for seconds, and microservices that restart often are where the gains show up. If your function runs for 500ms but takes 400ms to start, cutting startup to 100ms is the whole game. The class-loading cost gets paid on every invocation, so when invocation time is short, the tax is high.
The memory savings matter in container environments. When multiple JVM processes on the same node share the mapped archive file, the kernel can dedupe the physical pages. RSS drops for each process, which helps with container density and cost.
Use AppCDS when you own the JVM version end-to-end and can bake the archive into your container image. If your CI builds the archive once and ships it with the app, you get consistent wins without runtime overhead.
When NOT to Use
Skip CDS for long-running services. A JVM that starts in 2 seconds and runs for 8 hours gets almost nothing from a faster startup. The archive maintenance burden (regenerate on every JVM upgrade, store it somewhere, validate it in CI) is real cost for a negligible benefit when the startup time is a rounding error on total runtime.
Avoid AppCDS when your application loads classes dynamically in ways that are hard to predict. A static class list only captures what you knew about at archive creation time. If your framework loads plugins, generates classes at runtime, or exercises code paths that vary between runs, the archive is incomplete and your startup gains evaporate. Dynamic archives help here, but they introduce their own unpredictability.
The archive is tied to the exact JVM version and the exact set of classes. Upgrade a library and the archive may silently break, or worse, seem to work but produce wrong results. If you do not have a build pipeline that regenerates the archive with each release, the maintenance overhead will bite you.
How CDS Works
When the JVM loads a class, it parses the bytecode, performs verification, resolves constants, and creates an internal representation called a class file structure in the Metaspace. This parsing is repeatable. The same class bytecode produces the same internal structures every time.
graph TD
A[JVM Process 1<br/>First Start] --> B[Load java.base classes]
B --> C[Parse bytecode<br/>Verify types<br/>Build internal structures]
C --> D[Write class data to<br/>shared_archive.jsa]
D --> E[Exit]
F[JVM Process 2<br/>Subsequent Start] --> G[Memory-map<br/>shared_archive.jsa]
G --> H[Class data loaded<br/>from memory map]
H --> I[Skip parsing<br/>for archived classes]
I --> J[Start running<br/>faster]
CDS dumps the internal class metadata to a file on the first run. On subsequent runs, the JVM memory-maps this file directly, making the class data instantly available without parsing. Multiple JVM processes on the same machine can also share the mapped file in physical memory, reducing total memory consumption.
The shared archive is specific to the JVM version and the set of classes that were archived. A CDS archive created with Java 11 cannot be used with Java 17.
CDS vs AppCDS
CDS alone only archives the core JDK classes from java.base. AppCDS extends this to include your application classes and any library classes you choose to include.
CDS (Class Data Sharing):
- Ships with the JDK by default
- Archives only JDK core classes
- Automatic: the JDK includes a default archive for
java.base - Controlled by
-Xshare:{on,off,dump,auto}
AppCDS (Application Class Data Sharing):
- Extends CDS to include application classes
- Requires creating a custom archive with
-Xshare:dump - Specify which classes to archive with
-XX:SharedClassListFile - Archive path set with
-XX:SharedArchiveFile - Available since Java 8 update 191, became more flexible in Java 11+
Creating a Shared Archive
Creating a shared archive involves running the JVM with -Xshare:dump to produce the archive file, then referencing it at runtime.
# Step 1: Create a class list of classes to archive
# You can use -XX:+UseAppCDS and dump application classes
java -Xshare:off -XX:+UnlockCommercialFeatures \
-XX:+UseAppCDS \
-XX:DumpLoadedClassList=classes.lst \
-cp myapp.jar com.myapp.Main
# Step 2: Create the shared archive
java -Xshare:dump \
-XX:+UseAppCDS \
-XX:SharedClassListFile=classes.lst \
-XX:SharedArchiveFile=shared_archive.jsa \
-cp myapp.jar
# Step 3: Use the archive at runtime
java -Xshare:on \
-XX:SharedArchiveFile=shared_archive.jsa \
-cp myapp.jar com.myapp.Main
In Java 11 and later, the process is simpler because AppCDS is no longer a commercial feature.
# Java 11+: Create archive with class list
java -Xshare:off \
-XX:APP_CDS_CACHE_SIZE=64M \
-XX:+UseContainerSupport \
-XX:SharedClassListFile=classes.lst \
-XX:SharedArchiveFile=shared_archive.jsa \
-cp myapp.jar \
--module-path mods \
com.myapp.Main
# Java 11+: Runtime with archive
java -Xshare:on \
-XX:SharedArchiveFile=shared_archive.jsa \
-cp myapp.jar \
--module-path mods \
com.myapp.Main
Automatic Archive Creation
Java 13 introduced automatic CDS archives. The JVM can create an archive automatically on the first run if the directory is writable and the -Xshare:auto mode is enabled (the default).
# First run: creates archive automatically
java -Xshare:auto -cp myapp.jar com.myapp.Main
# Second run: uses the archive
java -Xshare:auto -cp myapp.jar com.myapp.Main
The archive is stored in java.io.tmpdir by default and includes the classes loaded during the first run. This is the simplest way to get CDS benefits with no configuration.
Production Failure Scenarios
| Failure Scenario | Symptoms | Root Cause | Solution |
|---|---|---|---|
| Incompatible archive | Error: ” Unable to use shared archive” | Archive created with different JVM version | Recreate the archive with current JVM version |
| Corrupted archive | Crash on startup when loading archive | Archive file modified after creation | Delete and recreate the archive |
| Class list mismatch | Classes missing from archive, no speedup | Classes loaded dynamically not in class list | Include all possible classes in the list |
| Read-only filesystem | Cannot create archive | No write access to archive directory | Use writable directory with -XX:SharedArchiveFile |
| Archive too old | Old classes used from archive | Archive not regenerated after code changes | Regenerate archive with each release |
Trade-off Analysis
Understanding when CDS helps and when it does not helps you decide where to invest effort.
| Aspect | Without CDS | With AppCDS |
|---|---|---|
| Startup time | Slower (full class loading) | Faster (memory-mapped archive) |
| Memory usage | Higher (each process loads own copy) | Lower (shared memory pages) |
| Disk space | None | Archive file size (typically 10-50MB) |
| Maintenance | None | Archive must be regenerated on JVM upgrade |
| Benefit magnitude | Baseline | 20-50% startup improvement for typical apps |
| Short-lived processes | Full cost every time | Amortized across runs |
CDS provides the most benefit for short-lived processes where startup time dominates total execution time. For long-running server applications, the startup savings are amortized over a longer runtime and matter less.
Implementation Snippets
Full Java 11+ AppCDS Workflow
#!/bin/bash
# build_and_archive.sh
APP_JAR="build/libs/myapp.jar"
ARCHIVE_FILE="myapp.jsa"
CLASS_LIST="classes.lst"
# Step 1: Run once to generate class list
echo "Generating class list..."
java -Xshare:off \
-XX:+UseAppCDS \
-XX:SharedClassListFile=${CLASS_LIST} \
-XX:SharedArchiveFile=${ARCHIVE_FILE} \
-cp ${APP_JAR} \
com.myapp.Main --help 2>/dev/null
# Step 2: Create the archive
echo "Creating shared archive..."
java -Xshare:dump \
-XX:+UseAppCDS \
-XX:SharedClassListFile=${CLASS_LIST} \
-XX:SharedArchiveFile=${ARCHIVE_FILE} \
-cp ${APP_JAR}
echo "Archive created at ${ARCHIVE_FILE}"
ls -lh ${ARCHIVE_FILE}
Using AppCDS in Kubernetes
# Dockerfile
FROM openjdk:17-slim AS builder
COPY build/libs/myapp.jar /app/
COPY create_archive.sh /app/
RUN chmod +x /app/create_archive.sh && \
/app/create_archive.sh
FROM openjdk:17-slim
COPY --from=builder /app/myapp.jsa /app/
COPY --from=builder /app/myapp.jar /app/
ENTRYPOINT ["java", "-Xshare:auto", "-XX:SharedArchiveFile=/app/myapp.jsa", "-jar", "/app/myapp.jar"]
Verifying Archive Usage
# Check if CDS is active
java -Xshare:on -XX:+PrintSharedArchiveInfo -cp myapp.jar com.myapp.Main 2>&1 | head -30
# Example output when archive is working:
# ...
# ...
# SharedArchiveInfo section:
# Base address: 0x0000000800000000
# Mapping: shared_archive.jsa
# ...
Dynamic Archive with Java 17+
Java 17 introduced the ability to create a dynamic archive that includes classes loaded at runtime, not just those in a static class list.
# Create a dynamic archive (Java 17+)
# First run: create archive
java -Xshare:auto \
-XX:ArchiveClassesAtExit=myapp_dynamic.jsa \
-cp myapp.jar \
com.myapp.Main
# Subsequent runs: use dynamic archive
java -Xshare:auto \
-XX:SharedArchiveFile=myapp_dynamic.jsa \
-cp myapp.jar \
com.myapp.Main
The dynamic archive captures whatever classes were actually loaded during the first run, which can be more complete than a static class list.
Observability Checklist
When using CDS/AppCDS in production, these points help you verify it is working correctly.
- Verify archive is loaded:
-XX:+PrintSharedArchiveInfo - Track startup time before and after archive creation
- Monitor RSS memory reduction with shared memory pages (
ps aux | grep java) - Check archive validity on JVM upgrade: recreate if version mismatch errors appear
- Store the archive in version control or build artifacts for reproducibility
- Use
-Xlog:class+load=infoto see which classes come from the archive - Monitor for archive corruption errors after system updates
- Test with
-Xshare:dumpperiodically to refresh archives for changed code
Security Notes
CDS archives are memory-mapped files. If the archive file is modified after creation, the JVM can crash or behave unpredictably when it tries to read corrupted structures. Protect archive files with the same care you give to native libraries.
CDS archives contain parsed class metadata including the structure of all fields and methods. This is generally not sensitive since it comes from class files which are already part of the JAR. However, be aware that the archive contains a complete picture of your application class structure.
CDS does not provide any security boundary. Any process with read access to the archive file can memory-map it. In multi-tenant environments, use file permissions to restrict access to archives if that is a concern.
Common Pitfalls / Anti-Patterns
Here are the most common mistakes when working with CDS and AppCDS.
Forgetting to recreate the archive on JVM upgrade. A CDS archive is tied to the exact JVM version and possibly the JVM implementation (HotSpot vs OpenJ9 vs others). Upgrading the JDK without regenerating the archive causes errors. Build the archive regeneration into your deployment pipeline.
Using -Xshare:auto without understanding what gets archived. With automatic archive creation, the archive only contains classes that were actually loaded during the first run. If your application has code paths that are not exercised on the first run, those classes are not archived and do not benefit from faster loading.
Not including dynamically loaded classes. Classes loaded via reflection, ClassLoader.newInstance, or agent premain are not in a static class list. Use dynamic archives (ArchiveClassesAtExit) to capture these.
Storing the archive in /tmp. The default location for automatic archives is java.io.tmpdir, which is often tmpfs and gets cleared on reboot. Store the archive in a persistent location or your application image.
Assuming archive helps on every workload. For long-running server processes where startup is a small fraction of total runtime, the overhead of managing the shared archive may not be worth it. CDS shines for short-lived processes.
Quick Recap Checklist
Use this checklist when deploying AppCDS for your application.
- Decide between static class list and dynamic archive for your use case
- Build archive creation into your CI/CD pipeline
- Verify the archive loads correctly with
-XX:+PrintSharedArchiveInfo - Store the archive alongside your application JAR or in the container image
- Plan to regenerate the archive on every JVM version upgrade
- Test startup improvement before and after in a representative environment
- Use
-Xshare:autofor simplest configuration if dynamic classes are okay - Monitor for shared archive errors in logs after system changes
- Protect archive files from modification after creation
- Consider dynamic archives (
ArchiveClassesAtExit) for complex classloading
Interview Questions
CDS (Class Data Sharing) archives only the core JDK classes from the java.base module. It has been in the JVM since Java 5 and uses a default archive that ships with the JDK. AppCDS (Application Class Data Sharing) extends this to include application classes and third-party library classes. You create a custom archive that contains exactly the classes your application needs, using a class list file or a dynamic archive. AppCDS was introduced as a commercial feature in Java 8 but became free to use in Java 11.
When the JVM memory-maps the archive file, the class metadata is loaded directly from the file system into the process address space using the operating system's mmap call. The data is not copied into heap memory; it is accessed directly from the mapped file pages. For startup, this means no parsing, verification, or allocation for archived classes. For memory usage, multiple JVM processes on the same machine can share the same physical memory pages for the archive, because the kernel can map the same file to multiple processes. This is called shared memory pages and is why RSS (Resident Set Size) for each JVM process can be lower when using CDS.
The internal class structure that the JVM creates when parsing a class depends on the JVM implementation and version. The JVM adds internal fields, changes how it lays out objects, and modifies the constant pool representation between versions. If you load a class using a structure defined by Java 11 and try to interpret it with a JVM built for Java 17, the internal structures do not match and the JVM cannot safely use the archived data. This is why archive files have a version marker and the JVM refuses to use an archive from an incompatible version.
Use a dynamic archive (created with -XX:ArchiveClassesAtExit) when your application loads classes dynamically through reflection, ServiceLoader, ClassLoader.newInstance, or bytecode manipulation tools. A static class list only includes classes you know about at archive creation time. If your framework or plugin system loads classes that are not known until runtime, those classes are not in a static list but will be captured in a dynamic archive. The tradeoff is that a dynamic archive is less predictable since the classes it contains depend on which code paths execute during the archive creation run.
If the archive file is corrupted (modified after creation), the JVM detects the mismatch when it tries to memory-map and use the archive, and it refuses to start with an error like "Unable to use shared archive." If the archive is deleted, the JVM falls back to normal class loading behavior (with a warning). Neither corruption nor deletion causes a security problem beyond the JVM refusing to start or falling back to slower behavior. The archive is not a security boundary.
A CDS archive (typically named with .jsa extension) contains: a header with version and flag information, a class data section with parsed class metadata for each archived class, a section containing the internal representation of archived class structures (field offsets, method tables, vtable shapes), and optionally a trailer with checksums. The archive does not contain the raw bytecode (that is already in the class files) but rather the JVM's internal parsed representation of each class. This parsed form is what allows fast loading—the JVM skips parsing and just memory-maps the pre-parsed structures.
When creating an AppCDS archive for a modular application, you use the --module-path to include your application modules. The archive captures classes from all modules on the module path. At runtime, the archive is loaded before the module system is fully initialized, so the archived classes are available before module resolution occurs. This means module-level encapsulation does not affect archived classes—they are loaded directly from the archive. However, if you use --module-path at runtime, it must include the same modules that were included when the archive was created.
A static class list is created by running the application with a flag that dumps loaded class names to a file, then using that list to create the archive. It only includes classes known at archive creation time. A dynamic archive (ArchiveClassesAtExit) captures whatever classes were actually loaded during the first run of the application. Use a static list when you need predictable, reproducible archives with known class coverage. Use a dynamic archive when your application loads classes dynamically (reflection, ClassLoader, bytecode manipulation) and you want to capture the actual runtime class loading behavior.
When multiple JVM processes on the same machine use the same CDS archive, the OS kernel can dedupe the physical memory pages containing the archived class data. Even though each JVM process has its own virtual address space mapping to the archive file, the kernel can use the same physical pages for all processes through copy-on-write sharing. This reduces the RSS (Resident Set Size) of each JVM process. For containers sharing the same host, this can significantly reduce overall memory consumption. The benefit increases with more JVM processes on the same machine.
Key AppCDS flags: -XX:+UseAppCDS enables AppCDS (commercial feature flag in Java 8, standard in Java 9+). -XX:SharedClassListFile specifies the file containing class list for archive creation. -XX:SharedArchiveFile specifies the path to the archive file. -XX:APP_CDS_CACHE_SIZE sets the size of the class data cache (default varies by JVM version). -XX:+PrintSharedArchiveInfo prints details about the archive when enabled. -XX:ArchiveClassesAtExit creates a dynamic archive on JVM exit. -Xshare:dump creates the archive during a dedicated dump run.
Classes not included typically means they were not loaded during the archive creation run. If your production environment loads classes through reflection, plugins, or dynamic ClassLoaders that were not exercised during the dump run, those classes are missing. Use -Xlog:class+load=info to see which classes load from the archive and which are loaded normally. Compare the class list in the dump output against what you expect. For dynamically loaded classes, use ArchiveClassesAtExit to capture the actual runtime class loading rather than relying on a static list.
When a JVMTI agent is active (either via -agentlib or -javaagent), class loading may be modified to support instrumentation, which can prevent AppCDS from working correctly because the agent can modify class bytecode at load time. If an agent premain is used to transform classes, those classes may not be included in the archive. For agents that use retransformation (redefining classes at runtime), the redefined classes may behave differently than archived versions. Test with your specific agent configuration to verify archive correctness.
CDS (without App) ships a default archive for java.base with the JDK in the lib/classlist.gz file. This archive is loaded automatically when -Xshare:auto or -Xshare:on is set. AppCDS extends this mechanism to include application classes. Both share the same underlying archive format and memory-mapping infrastructure. When AppCDS is enabled, application classes are archived alongside java.base classes in the same archive file or a separate archive. The java.base archive is version-matched to the JDK and not affected by application archive decisions.
Add -XX:+PrintSharedArchiveInfo to the JVM startup flags to see archive details. In your CI pipeline, run the application with the archive and check for "shared archive" in the output. Verify that key classes are listed as coming from the archive in -Xlog:class+load=info output. Run the startup time benchmark before and after to confirm improvement. Store the archive in your build artifacts and verify it loads correctly after deployment. After any JDK upgrade, regenerate the archive and verify it still works with the new JDK version.
AppCDS does not directly affect garbage collection. The archived class metadata is stored in the native memory region (not the Java heap) and is managed by the JVM's class loading subsystem, not the garbage collector. GC does not scan or collect archived class data because it is not in the heap. The only indirect effect is that faster startup may mean more frequent JVM restarts in short-lived processes, which could change GC pattern frequency, but the archive itself does not affect GC logic or heap sizing.
Yes, but you need to create separate archives for different class sets or use a dynamic archive that captures all classes loaded across all processes. If processes have very different classloading patterns, a single static archive may not cover all of them. The best approach for multi-process applications is to use -XX:ArchiveClassesAtExit on a representative first-run process to capture the most common classes, then verify coverage in other processes through -Xlog:class+load=info. For heterogeneous applications, consider whether the startup benefit justifies the maintenance complexity.
The archive itself is not affected by container limits—it is a static file loaded at startup. However, the JVM's memory usage and heap sizing (as described in the container memory limits section) still apply. The archived class data is loaded into native memory, not heap, so it does not count against -Xmx. If the container limit is very small (like less than 64MB), the JVM may have trouble starting even with AppCDS because of overhead requirements. Always test with the actual container limits your production uses.
GraalVM Native Image does not use AppCDS because it compiles the application ahead-of-time to a native executable with no JVM at runtime. AppCDS is a JVM feature requiring a runtime JVM. For native images, the equivalent optimization is the ahead-of-time compilation of all classes into the binary. If you are using GraalVM with a JVM (not native image), AppCDS works normally. The tradeoffs are different because native image startup is already fast; AppCDS provides less benefit compared to regular JVM startup improvement.
The class list file is one class name per line, using fully qualified names with slashes instead of dots (for example, java/lang/Object for java.lang.Object). You generate it by running the application with -XX:DumpLoadedClassList=classes.lst during a representative run. For complex applications, ensure you exercise all major code paths during that run to capture all relevant classes. Use a script that runs the application with different inputs to cover different dynamic loading paths. If your application has plugins or optional components, run with each plugin enabled to ensure their classes are captured.
Hermes is a JavaScript engine used in some environments (like React Native) and is not directly related to AppCDS. They are separate optimization mechanisms for different parts of the runtime. AppCDS archives Java classes; Hermes optimizes JavaScript execution through ahead-of-time compilation. If your application includes both Java and JavaScript components, AppCDS helps the Java side while Hermes helps the JavaScript side. There is no interaction between them in terms of archive sharing.
Further Reading
- JEP 310: Application Class-Data Sharing - Official JEP covering AppCDS design, dynamic archives, and the archive file format.
- CDS Archive Format and Internal Structure - OpenJDK documentation on how class data is serialized into the shared archive and how it is memory-mapped at runtime.
- AppCDS Implementation in OpenJDK - Technical details on AppCDS implementation including class list format, archive validation, and cache management.
- Dynamic Archiving with ArchiveClassesAtExit - Oracle documentation covering dynamic archive creation with Java 17+ and when to use ArchiveClassesAtExit vs static class lists.
- Memory Mapping Benefits in Container Environments - Linux kernel documentation on how mmap sharing works across processes and why shared memory pages reduce RSS in container environments.
Conclusion
CDS and AppCDS are one of the quieter performance wins in the JVM. They require minimal configuration, provide meaningful startup improvements, and reduce memory footprint through shared memory mapping. The only real cost is maintaining the archive across JVM upgrades, which is manageable with proper build pipeline integration.
For serverless functions, short-lived CLI tools, and microservices where startup latency matters, AppCDS is worth the setup effort. For long-running servers, the benefit is smaller but still positive. Start with -Xshare:auto and graduate to a static archive if you need reproducible, controlled class coverage.
Category
Related Posts
JVM in Containers: Cgroup Memory Limits and Heap Sizing
A guide to how the JVM detects container memory limits, configures heap accordingly, and avoids pitfalls when running Java in Docker and Kubernetes.
JVM Bytecode Verification: Type Checking and Stack Map Frames
A technical deep dive into the JVM bytecode verifier, covering type checking, stack map frames, the four verification stages, and what happens when verification fails.
Heap Walking and Allocation Tracking: TLABs and Heap Analysis
Understand how the JVM allocates memory with TLABs, how to track allocations with low overhead, and how heap walking tools analyze object graphs.