skip to content

What is Class Data Sharing (CDS), and how does it speed up JVM / Spring Boot startup without going native?

level: juniorimportance: should knowfreq 25%

answer

  1. Snapshot parsed classes -> memory-map at start
  2. AppCDS = your + library classes, not just JDK
  3. Still a normal JVM: reflection/proxies/JIT intact
  4. ~2x startup, no closed-world unlike native
  5. spring.context.exit=onRefresh training run

basics

~20 s

CDS stores parsed class metadata in an archive file that the JVM memory-maps at startup instead of re-parsing and re-verifying each class. A Spring Boot app starts roughly twice as fast while staying a normal JVM app.

solid answer

~50 s

Class Data Sharing takes the in-memory representation of loaded classes (parsed bytecode, verification data, method metadata) and writes it to an on-disk archive. On the next start the JVM memory-maps that archive instead of finding, reading, parsing and verifying every class again, so startup drops sharply — typically around 2x for a Spring Boot app. AppCDS extends this from JDK classes to your application and library classes. Crucially, the app is still a fully dynamic JVM: reflection, dynamic proxies, JIT, agents and conditional beans all work unchanged. That makes CDS a low-risk middle ground between a plain JVM and a GraalVM native image — you get faster startup and lower per-instance memory with none of native image's closed-world constraints or long build times. Spring Boot 3.3+ has first-class support, and CDS composes with AOT for further gains.

code

java · 13 lines
java
// Nothing special in code — CDS is a runtime/JVM concern.
// A standard Spring Boot entry point is all CDS needs:
@org.springframework.boot.autoconfigure.SpringBootApplication
public class Application {
    public static void main(String[] args) {
        org.springframework.boot.SpringApplication.run(Application.class, args);
    }
}
// Training run (loads classes, then exits before serving):
//   java -Dspring.context.exit=onRefresh \
//        -XX:ArchiveClassesAtExit=application.jsa -jar app.jar
// Production run (uses the archive):
//   java -XX:SharedArchiveFile=application.jsa -jar app.jar

go deeper

for a junior

Know that CDS caches loaded class metadata in a file the JVM maps at startup, giving faster startup while staying a normal JVM.

for a middle

Should distinguish AppCDS/dynamic CDS from the old static JDK archive and name the archive-then-use flow.

for a senior

Frames CDS as the low-risk alternative to native image and knows Spring Boot's training-run integration.

for a principal

Can position CDS in a startup/footprint strategy (CDS + AOT vs native) and reason about ops constraints like classpath/JDK stability.

## The problem CDS solves When a JVM starts an application it must, for every class it touches: locate the `.class` bytes on the classpath, read them, **parse** them into internal structures, **verify** them (bytecode safety checks), and build metadata (constant pool, method tables, etc.). For a large Spring Boot app that is thousands of classes on every single start — pure repeated work, identical run to run. **Class Data Sharing (CDS)** does that work once, snapshots the resulting in-memory class metadata into an **archive file** (conventionally `*.jsa`), and on later starts the JVM **memory-maps** the archive directly into its address space. Memory-mapping is near-instant and the mapped region can be shared read-only across multiple JVM processes on the same host, which also lowers total memory. ### Static CDS vs AppCDS vs Dynamic CDS - **Static CDS** (very old) archived only core JDK classes. Modern JDKs (13+) even ship a default JDK archive out of the box. - **Application CDS (AppCDS)** extends archiving to your *application* and *third-party library* classes — this is what matters for Spring Boot, where most classes are Spring/your code, not `java.*`. - **Dynamic CDS** (JDK 13, JEP 350) removes the tedious old step of hand-writing a class list: the JVM simply records the classes actually loaded during a run and dumps them at exit. This is the mechanism Spring Boot uses. ### What you actually gain - Startup: commonly ~2x faster for Spring Boot (exact numbers vary by app). - Memory: shared, mapped metadata reduces per-instance footprint. ### What you keep (the key selling point vs native image) CDS is **still an ordinary HotSpot JVM**. Everything dynamic works: reflection, `java.lang.reflect.Proxy` / CGLIB proxies, runtime classpath scanning, `@Conditional` beans decided at runtime, Java agents, full JIT compilation and profile-guided optimization. There is **no closed-world assumption** and **no long AOT/native build** — you don't need reachability metadata or `reflect-config.json`. Compared to a **GraalVM native image**, CDS starts slower (native is tens of milliseconds) but is far cheaper to adopt, keeps peak throughput via JIT, and imposes none of native's restrictions. ### How you enable it (dynamic CDS, the common path) 1. **Training run** — produce the archive from a real (or Spring-Boot-orchestrated) run: `-XX:ArchiveClassesAtExit=application.jsa`. 2. **Production run** — use it: `-XX:SharedArchiveFile=application.jsa`. JDK 19+ collapses both into one flag, `-XX:+AutoCreateSharedArchive -XX:SharedArchiveFile=application.jsa`. ### Spring Boot 3.3+ integration Spring Boot adds a **training-run mode** via `-Dspring.context.exit=onRefresh`, which starts the context, loads all the classes needed to bootstrap, then exits before serving traffic — perfect for populating an archive. Paketo/Spring Boot buildpacks can do the whole training-run-and-archive step at image-build time via `BP_JVM_CDS_ENABLED=true`. ### Gotchas - The **classpath and JDK version must match** between the training run and the production run; a mismatch invalidates the archive. - CDS works best with an **extracted/exploded** jar layout (stable classpath) rather than surprises from the nested fat-jar classloader; Spring Boot's `java -Djarmode=tools -jar app.jar extract` produces exactly that layout. - CDS speeds up **startup**, not steady-state throughput (JIT still governs peak performance). - `-Xshare:auto` (default) silently runs without the archive if it can't be used; `-Xshare:on` fails loudly instead — useful to prove CDS is actually engaged.

  • Does CDS make the app as fast to start as a GraalVM native image?
    No. Native image starts in tens of milliseconds; CDS is roughly 2x faster than a cold JVM but still on the order of hundreds of ms to seconds. CDS trades some startup speed for zero closed-world constraints and no long build.
  • Does CDS improve peak throughput?
    Not really. It shortens startup and can lower memory, but steady-state throughput is still driven by the JIT, which behaves the same as a normal JVM.

saying these in an interview costs you the question

  • Claiming CDS makes the app native / removes the JVM
  • Saying CDS needs reflect-config.json like native image
  • Believing CDS boosts steady-state throughput rather than startup
  • Thinking CDS only archives JDK classes (that is old static CDS, not AppCDS)

context