Java 10 Application Class-Data Sharing (AppCDS) Tutorial
Introduction
Every time a Java application starts, the JVM loads, parses and verifies hundreds or thousands of classes before the application can do any real work. Class-Data Sharing (CDS) saves this work by storing the processed classes in an archive file, which later runs simply map into memory. Before Java 10, CDS in OpenJDK covered only the JDK's own classes. Java 10 (JEP 310) extended it to the application's classes, as Application Class-Data Sharing (AppCDS). This tutorial explains AppCDS with examples. We will first see how class-data sharing works and what it improves. We will then create an archive for a sample application step by step, check that classes are loaded from it, and look at the startup times. Lastly, we will see how later Java versions made CDS easier to use.
(Note - To understand how the JVM loads classes, read how Java code runs: bytecode, class loader and JITClick to read: how Java code runs: bytecode, class loader and JIT.) How class-data sharing works To load a class, the JVM reads its bytes from a JAR file, parses them into its internal data structures, and verifies the bytecode. The result is the same every time the same class is loaded, yet every JVM does this work again on every start. With CDS -
A sample application
We will use a small application which uses logging, collections, streams, lambdas and the date-time API, so that it loads a realistic number of JDK classes. It is compiled and packaged in
Step 1: Create a list of the classes to archive
First, the application is run once with
OUTPUT of the above command
The application runs as usual, and
This does not run the application; it only creates
OUTPUT of the above command
Note - In Java 10 itself, AppCDS also had to be switched on with
Checking that classes come from the archive
The output looks the same with or without the archive, so how do we know the archive was used? Logging class loading with
OUTPUT of the above command
Counting the lines which say
OUTPUT of the above command
Almost every class the application needs, over a thousand of them, was mapped from the archive instead of being loaded and verified. If the archive cannot be used - for example because the class path or the JDK has changed - the JVM prints a warning and loads the classes normally, so the application still works.
How much faster is startup?
Below are the times to run the sample application, measured on a Windows laptop with JDK 24 as the average of several runs. The exact numbers depend on the machine, but the pattern is typical -
(Note - To understand how the JVM loads classes, read how Java code runs: bytecode, class loader and JITClick to read: how Java code runs: bytecode, class loader and JIT.) How class-data sharing works To load a class, the JVM reads its bytes from a JAR file, parses them into its internal data structures, and verifies the bytecode. The result is the same every time the same class is loaded, yet every JVM does this work again on every start. With CDS -
- The classes are loaded once, and the JVM's internal representation of them is written to an archive file (a
.jsafile). - Later runs memory-map the archive, so the classes are ready to use without reading, parsing or verifying them.
- Faster startup, since less work is done before
main()runs. - A smaller memory footprint when several JVMs run on the same machine, because the read-only part of the archive is mapped from the same file and shared between the processes.
app.jar -
The sample application
//App.java
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.logging.Logger;
import java.util.stream.Collectors;
public class App {
public static void main(String[] args) {
Logger log = Logger.getLogger("App");
List<String> names = List.of("Tom", "Harry", "Sam");
String joined = names.stream().map(String::toUpperCase).sorted().collect(Collectors.joining(", "));
String date = LocalDate.of(2018, 3, 20).format(DateTimeFormatter.ofPattern("dd MMM yyyy"));
System.out.println(joined + " | Java 10 released on " + date);
}
}
javac -d classes App.java jar --create --file app.jar --main-class App -C classes .
-XX:DumpLoadedClassList, which writes the name of every class it loads to a file. -Xshare:off makes sure all classes are loaded normally during this run -
java -Xshare:off -XX:DumpLoadedClassList=app.classlist -cp app.jar App
HARRY, SAM, TOM | Java 10 released on 20 Mar 2018
app.classlist now lists the JDK and application classes it used, one per line, including our App class.
Step 2: Create the shared archive
Next, -Xshare:dump loads every class in the list and writes them to the archive file named by -XX:SharedArchiveFile. The class path must be the same one the application will be run with, since the archive records it -
java -Xshare:dump -XX:SharedClassListFile=app.classlist -XX:SharedArchiveFile=app.jsa -cp app.jar
app.jsa. The archive is specific to the JDK build which created it - after upgrading the JDK, create it again.
Step 3: Run the application with the archive
java -XX:SharedArchiveFile=app.jsa -cp app.jar App
HARRY, SAM, TOM | Java 10 released on 20 Mar 2018
-XX:+UseAppCDS in steps 2 and 3. From Java 11 it is always enabled, and the option is no longer needed.
-Xlog:class+load shows where each class came from. With the archive, our App class is loaded from the shared objects file rather than from app.jar -
java -Xlog:class+load -XX:SharedArchiveFile=app.jsa -cp app.jar App | findstr /C:"] App "
[0.060s][info][class,load] App source: shared objects file
shared objects file gives the number of classes loaded from the archive (use grep -c instead of find /c on Linux and macOS) -
java -Xlog:class+load -XX:SharedArchiveFile=app.jsa -cp app.jar App | find /c "shared objects file"
1160
| How the application was run | Time |
|---|---|
No class-data sharing (-Xshare:off) - like Java 9 and earlier without a CDS archive | about 370 ms |
| Default CDS archive of JDK classes only (the default since Java 12) | about 255 ms |
AppCDS archive of JDK and application classes (-XX:SharedArchiveFile=app.jsa) | about 245 ms |
- Compared with no class-data sharing, the archive cut startup time by about a third.
- Most of the gain in this small application comes from the JDK classes, which modern JDKs already share by default. The more classes an application has of its own - a framework-based application such as a Spring Boot service loads thousands - the more AppCDS adds on top of the default archive.
- AppCDS helps most for applications which start often or must start fast, such as command-line tools, serverless functions and containers which are scaled up and down.
- Java 12 (JEP 341) - the JDK ships with a default CDS archive of its own classes, which is used automatically. This is the "default" row in the table above.
- Java 13 (JEP 350) - dynamic archiving. Running the application once with
-XX:ArchiveClassesAtExit=app.jsacreates the archive when the application exits, replacing steps 1 and 2. - Java 19 -
-XX:+AutoCreateSharedArchive -XX:SharedArchiveFile=app.jsacreates the archive if it is missing or out of date, and uses it otherwise. - Java 24 (JEP 483) - the AOT cache, which builds on CDS to also store classes in their loaded and linked state, for an even faster startup.
-XX:DumpLoadedClassList, built an archive with -Xshare:dump, and ran the application with -XX:SharedArchiveFile. We then confirmed with -Xlog:class+load that the classes came from the archive, and compared the startup times. Lastly, we saw how later Java versions made CDS easier to use. In the next tutorial, we will look at time-based release versioningClick to read tutorial: Java 10 Time-Based Release Versioning and Runtime.Version, which Java adopted along with the six-month release cadence in Java 10.Continue Reading Java 9 & Java 10 Tutorials
Java 10 Time-Based Release Versioning and Runtime.VersionClick to read tutorial: Java 10 Time-Based Release Versioning and Runtime.Version Java 10 API Additions: copyOf, toUnmodifiableList & MoreClick to read tutorial: Java 10 API Additions: copyOf, toUnmodifiableList & More Overview of New Features in Java 10Click to read tutorial: Overview of New Features in Java 10 Java var Keyword: Local Variable Type InferenceClick to read tutorial: Java var Keyword: Local Variable Type Inference Java 10 Removed javah: Generate JNI Headers with javac -hClick to read tutorial: Java 10 Removed javah: Generate JNI Headers with javac -h Java 10 Unicode Locale Extensions: Currency, Week, RegionClick to read tutorial: Java 10 Unicode Locale Extensions: Currency, Week, Region Java 9 Modules Tutorial | Java Platform Module System (JPMS)Click to read tutorial: Java 9 Modules Tutorial | Java Platform Module System (JPMS)
Java 10 Time-Based Release Versioning and Runtime.VersionClick to read tutorial: Java 10 Time-Based Release Versioning and Runtime.Version Java 10 API Additions: copyOf, toUnmodifiableList & MoreClick to read tutorial: Java 10 API Additions: copyOf, toUnmodifiableList & More Overview of New Features in Java 10Click to read tutorial: Overview of New Features in Java 10 Java var Keyword: Local Variable Type InferenceClick to read tutorial: Java var Keyword: Local Variable Type Inference Java 10 Removed javah: Generate JNI Headers with javac -hClick to read tutorial: Java 10 Removed javah: Generate JNI Headers with javac -h Java 10 Unicode Locale Extensions: Currency, Week, RegionClick to read tutorial: Java 10 Unicode Locale Extensions: Currency, Week, Region Java 9 Modules Tutorial | Java Platform Module System (JPMS)Click to read tutorial: Java 9 Modules Tutorial | Java Platform Module System (JPMS)