Why architecture matters here
Startup time is a first-class concern in the modern deployment world, and the JVM's historical weakness there has real costs. In a serverless or autoscaling environment, a slow cold start means requests queue behind a JVM that is still parsing classes; in a microservice fleet that scales up and down constantly, every new pod pays the full startup tax; in CLI tools and short-lived batch jobs, startup can dominate total runtime. A large fraction of that startup is class loading — reading and parsing thousands of class files — and it is work the JVM repeats identically on every single launch. CDS attacks the repetition directly: parse once at build or training time, reuse forever.
Memory footprint is the second architectural payoff, and it matters most exactly where the JVM is weakest: dense multi-tenant hosts. Without CDS, each JVM process holds its own private copy of the parsed metadata for the core classes — the same String, HashMap, and framework classes, duplicated in every process's metaspace. With a shared archive mapped read-only, the operating system's page cache holds one physical copy and maps it into every process, so the marginal metaspace cost of the archived classes for the second, third, and tenth JVM is close to zero. On a host packing dozens of JVMs, this shared metaspace is a significant memory saving that also reduces GC pressure on metaspace.
The reason CDS is an architecture topic and not just a flag is the validation contract that makes it safe. A shared archive is a snapshot of parsed classes tied to a specific classpath, JDK version, and set of relevant JVM options. If any of those changes, the archived metadata might no longer be correct — a class on the classpath could have been replaced by a different version. So the JVM validates the archive against the current environment at startup and, on any mismatch, silently disables sharing and falls back to loading classes normally. This fail-safe design means CDS never causes wrong behavior, but it also means a misconfigured deployment can silently get zero benefit — the archive is present, sharing is off, and nobody notices the startup went back to slow.
The final architectural point is that CDS has evolved from a fragile manual ritual into something close to automatic, and knowing which mode you are using shapes the whole operational story. The original AppCDS required three manual steps — run the app once to produce a class list, run it again to dump an archive from that list, then run for real with the archive — each pinned to an exact classpath. Dynamic CDS collapses this by letting the JVM auto-dump an archive of application classes on normal exit, layered on top of the JDK's default archive, so a single training run produces a usable archive. Later JDKs ship a default CDS archive out of the box and Project Leyden is pushing toward capturing even more (linked, and eventually JIT-compiled, state) ahead of time. The trajectory is clear: more of the JVM's repeated startup work is being shifted from every-run to once-ahead-of-time, and CDS is the foundation that trajectory builds on.
The architecture: every piece explained
Top row: the dump phase that produces the archive. A dump-time JVM runs the application through a representative workload (a training run, or just a normal exit under dynamic CDS). As it runs it records a class list — every class that got loaded, in order. For those classes the JVM does the expensive work up front: parse and link, building the internal metadata (the klass structures, constant pool, method data) and resolving symbolic references where it safely can. It writes all of that — the parsed, partly-linked representation — into a shared archive file (a .jsa) on disk. This archive is the reusable artifact: the JVM's internal form of those classes, frozen.
Middle row: the run phase that consumes it. A run-time JVM starts with sharing enabled (-Xshare:auto by default, -Xshare:on to require it) and the archive named. Instead of opening jars and parsing, it memory-maps the archive into its address space — the metadata is already in the right shape, so mapping replaces parsing. The mapped region becomes shared metaspace: because it is read-only, the OS backs every process's mapping with the same physical pages, so all JVMs on the host sharing this archive share one copy of the class metadata. Before trusting the mapping the JVM validates it: the current classpath, JDK version, and relevant options must match what the archive was dumped against, or the archive is rejected.
Bottom-left: automation and layering. Dynamic CDS removes the manual class-list-then-dump ritual by having the JVM automatically dump an archive of the application classes it loaded when the process exits (-XX:ArchiveClassesAtExit), layered on top of the base JDK archive. This turns archive creation into a single run rather than a three-step dance, which is what makes CDS practical in a build pipeline. Bottom-right: the fallback path — if a needed class is not in the archive (it was loaded after dump time, or the archive was partially rejected), the JVM simply loads it the normal way from the jar. CDS is always an accelerator layered over the normal class loader, never a replacement, so a miss costs nothing but the normal load.
Bottom strip: the operational surface. CDS pays off only when the archive is actually used, so the numbers that matter are archive freshness (does the archive still match the deployed classpath and JDK, or has a dependency bump invalidated it), the hit rate (what fraction of loaded classes came from the archive versus fell back to jar loading), and classpath stability (because the validation contract ties the archive to an exact classpath, anything that reorders or changes it silently disables sharing). Keeping these healthy is the difference between a real startup win and a dormant .jsa file doing nothing.
End-to-end flow
Trace a Spring-style microservice deployed as a container image. At build time the pipeline does a training run: it starts the fully-assembled application with -XX:ArchiveClassesAtExit=app.jsa, exercises it briefly (hits a health endpoint, touches the main code paths), and exits cleanly. During that run the JVM loaded several thousand classes — the JDK core, the Spring framework, the application's own classes — and on exit dynamic CDS wrote app.jsa containing the parsed, linked metadata for all of them, layered over the JDK's built-in default archive. The image ships with this archive baked in alongside the jars.
Now a pod starts in production. The run-time JVM launches with -XX:SharedArchiveFile=app.jsa and -Xshare:auto. Before loading anything the normal way, it validates the archive: it checks that the classpath matches what was recorded at dump time and that the JDK version and the archive-relevant options line up. They do — same image, same jars, same JDK — so the JVM memory-maps app.jsa into its address space. As the application initializes and references HashMap, Spring's ApplicationContext, and the app's own controllers, each of those classes is already present in the mapped shared metaspace: no jar open, no bytecode parse, no verification. The mapped metadata is used directly, and startup is measurably faster — often 20-40% off cold-start time for a class-heavy framework app.
The memory-sharing payoff appears when the host runs many pods of this service. Each pod's JVM maps the same app.jsa, and because the mapping is read-only the OS page cache holds one physical copy of the archived class metadata and maps it into all of them. The tenth pod's archived-class metaspace costs almost no additional physical memory. On a node packing many instances of the same service — a common autoscaling shape — this shared metaspace is a real per-node memory saving on top of the startup win, and it lightens metaspace GC because the shared classes never need collecting.
Now the fragile edge. A later build bumps a dependency, changing one jar on the classpath. The image is rebuilt, but suppose the training-run step was skipped and the old app.jsa shipped unchanged. At pod start the JVM validates the archive against the new classpath, detects the mismatch, and — because the default is -Xshare:auto — silently disables sharing and loads every class the normal way from the jars. The pod still runs correctly (fallback guarantees that), but the startup is back to full cost and nobody gets an error. This is the classic CDS failure: not a crash, but a silent loss of benefit. The fix is twofold — regenerate the archive as part of every build so it always matches the shipped classpath, and monitor a startup metric or class-load count so a regression to slow starts is visible.
The -Xshare:on versus -Xshare:auto choice is exactly the lever for that visibility. With auto, a mismatched or missing archive falls back silently — safe, but it hides regressions. With on, the JVM fails to start if it cannot map and validate the archive, which is precisely what you want in a controlled deployment where the archive should always match: a build that forgot to regenerate the archive fails loudly at deploy time instead of quietly shipping slow pods. The trade is between robustness (auto never blocks a start) and enforcement (on guarantees you actually get the benefit you built), and the right choice depends on whether a silent slow start or a failed start is the worse outcome for that service. For a fleet where startup latency is an SLO, on in staging plus a startup-time alert in production is the combination that keeps CDS honest.