CDS and AppCDS in Hotspot

CDS is an easy-to-use and robust HotSpot JVM feature that can help improve startup performance. In this article, we will learn about the background, architecture, and how to use and debug CDS.

What is CDS?

CDS was a feature added to the HotSpot JVM during an update release as a part of JDK 5. It has been enhanced and expanded upon in subsequent releases of the JDK. The goal of CDS is to reduce the startup time of the JVM by loading from a pre-processed archive of Java classes and JVM metadata that is used during the initialization process.

This article will cover the benefits of CDS, how it works, how to use CDS in your Java applications, and how to debug and troubleshoot CDS.

JVM Initialization

During initialization, the HotSpot JVM must load and initializes a set of core classes, for example, many of the classes located within the java.lang package. This process changes little regardless of the application(s) being run on the JVM. This repetitive process represents an opportunity for optimization.

Starting with JDK 5, the -Xshare:dump command could be used to create a pre-processed archive of the core classes HotSpot would typically need to load at startup. This shared archive is located in: $JAVA_HOME/lib/server/classes.jsa (Windows: $JAVA_HOME/bin/server/classes.jsa).

On initialization, the HotSpot JVM, if directed, would search this directory for the shared archive, and, if found, load the archive into a read-only memory-mapped location. This saves time as loading the pre-processed archive is faster than loading the classes as steps like; decompressing the file from an archived format, verifying it, and generating bytecode are skipped.

Default CDS Archive

With the JDK 12 release, an architecture specific default CDS archive is provided for 64-bit builds of JDK images. This circumvents the requirement to run the -Xshare:dump command to take advantage of CDS that previous versions of the JDK required. The default CDS archive is located in: $JAVA_HOME/lib/server/classes.jsa (Windows: $JAVA_HOME/bin/server/classes.jsa).

About "Class Data-Sharing"

Historically "CDS" was an acronym that stood for Class Data-Sharing, and is still frequently used in documentation on CDS. This name related to how the pre-processed archive was memory-mapped and shared with other JVM processes running on the same machine. This would reduce the overall memory footprint of the JVM processes.

This feature of CDS is no longer actively supported as memory costs have decreased significantly, and the availability of memory increased significantly. Additionally, the deployment model for applications running on servers is to be in tightly fitted containers, where the JVM is the only running process, which negates the advantage of sharing the memory mapping. Subsequently, "CDS" no longer stands for Class Data-Sharing.

Using CDS

CDS is controlled by the -Xshare:<value> argument which accepts the following values;

  • auto : Enables CDS to be used when a shared achieve is present. DefaultNote: auto was made the default value starting with JDK 12 for all 64-bit builds and assumes the shared archive is located at: $JAVA_HOME/lib/server/classes.jsa (Windows: $JAVA_HOME/bin/server/classes.jsa)
  • on : Requires CDS to be used. If the JVM encounters an issue while attempting to load the shared archive, the JVM will print an error message and exit.Note: This should only be used for testing purposes and never in a production setting.
  • off : Disables CDS.
  • dump : Generates a CDS archive.

Performance Benefits of CDS

The performance gains from CDS are about 33% when tested using a simple "Hello World" application, as seen in the below test results:

$ time java -Xshare:off HelloWorld
Hello world!
java -Xshare:off HelloWorld  0.08s
$ time java -Xshare:on HelloWorld
Hello world!
java -Xshare:on HelloWorld  0.05s

With -Xshare:off the execution time of the application was 0.10 seconds, while with -Xshare:on it was 0.05 seconds.

The measurable performance benefits from using CDS for only core JDK classes become smaller as the size and complexity of the Java process being launched increases. This was one of the motivations for the introduction of AppCDS.

AppCDS

AppCDS was added to the HotSpot JVM as a part of the JDK 10 release with JEP 310. AppCDS aims to extend the benefits of CDS to include application classes. AppCDS have seen further enhancements, including major ones with the JDK 13 and 19 releases, that improve its performance and ease of use. AppCDS allows for a more consistent benefit of using CDS as the size and complexity of a Java application grows.

AppCDS supports the following locations:

  • Platform classes from the runtime image
  • Application classes from the runtime image
  • Application classes from the classpath
  • Application classes from the module path

Using AppCDS

As CDS only covered core Java classes included in the JDK, it was possible to include a pre-processed shared archive as a part of the JDK. This allowed developers to use CDS out of the box without any further interaction (for all platforms starting with JDK 12). However, to use AppCDS, some active intervention is required from the developer.

Generating a Dynamic Shared Archive

Added in JDK 13, the dynamic shared archive feature was designed to make the usage of AppCDS easier for most use cases and provide better support for applications that use user-defined class loaders. To generate a dynamic shared archive, you will need to use the -XX:ArchiveClassesAtExit=<name of archive file> command, which will generate a shared archive on application exit. A concrete example of using this command would look like this:

java -XX:ArchiveClassesAtExit=petclinic-dynamic-archive.jsa -jar target/spring-petclinic-2.5.1.jar

Note: The generation of a shared archive will have a significant performance impact during the JVM initialization and termination processes.

To use the generated archive on subsequent launches, the -XX:SharedArchiveFile=<name of archive file> will need to be used. Using the generated archive from the previous example would look like this:

java -XX:SharedArchiveFile=petclinic-dynamic-archive.jsa  -jar target/spring-petclinic-2.5.1.jar
Dynamic Archive without Default Archive

When a dynamic archive is generated, it will not include core classes that are included in the default archive. Instead, the dynamic archive will reference the default location for the default archive. Suppose the archive file is missing or corrupted. In that case, the JDK core classes and metadata will be loaded normally, negatively impacting startup impacting performance (or, if -Xshare:on is used, then the system will exit).

Autogenerating a Dynamic Archive

With the JDK 19 release JDK-8261455 introduced the ability for the JVM to automatically generated a shared archive with -XX:+AutoCreateSharedArchive. This command will tell the JVM to look for the shared archive defined with -XX:SharedArchiveFile; if the archive is not present or not in a valid state, an archive will be generated. A key advantage of this feature is that the same java command used to start an application can generate the archive, reducing maintenance work.

A new archive will be generated if the existing archive is corrupted, generated using an older JDK version, or if any of the dependent JARs have changed.

Static Archives

In most use cases, a dynamic archive will be sufficient. However, there might be cases where a static archive may be advantageous. Some such situations could be:

  • Storing additional symbol and string data
  • Slightly improved startup performance in some scenarios

To create a static archive, first, a classlist must be generated using the -XX:DumpLoadedClassList=<classlist name> command. During this process, CDS must also be turned off (-Xshare:off). Continuing the above example of using the Spring Boot Petclinic app, to create a classlist, run the following command:

java -Xshare:off -XX:DumpLoadedClassList=petclinic.classlist -jar target/spring-petclinic-2.5.1.jar

Next, the shared archive must be generated using the -XX:SharedArchiveFile=<name of archive file> command with -Xshare:dump and the generated classlist from the previous step -XX:SharedClassListFile=<classlist name>, like in this example:

java -Xshare:dump -XX:SharedArchiveFile=petclinic-static-archive.jsa -XX:SharedClassListFile=petclinic.classlist

To use the generated archive on subsequent launches, the -XX:SharedArchiveFile=<name of archive file> will need to be used. Using the generated archive from the previous example would look like the is:

java -XX:SharedArchiveFile=petclinic-static-archive.jsa -jar target/spring-petclinic-2.5.1.jar
Static Archive of Shared Classes

Below is an example of using the static archive feature to create an archive of shared classes/libraries that are used across multiple Java application:

To include classes from hello.jar and hi.jar, the .jar files must be added to the classpath specified by the -cp parameter.

Create a list of all classes used by the Hello application and another list for the Hi application:

   java -XX:DumpLoadedClassList=hello.classlist -cp common.jar:hello.jar Hello

 

   java -XX:DumpLoadedClassList=hi.classlist -cp common.jar:hi.jar Hi

Create a single list of classes used by all the applications that will share the shared archive file.

Linux and macOS The following commands combine the files hello.classlist and hi.classlist into one file, common.classlist:

   cat hello.classlist hi.classlist > common.classlist

Windows The following commands combine the files hello.classlist and hi.classlist into one file, common.classlist:

   type hello.classlist hi.classlist > common.classlist

Create a shared archive named common.jsa that contains all the classes in common.classlist:

   java -Xshare:dump -XX:SharedArchiveFile=common.jsa -XX:SharedClassListFile=common.classlist -cp common.jar:hello.jar:hi.jar

The classpath parameter used is the common class path prefix shared by the Hello and Hi applications.

Run the Hello and Hi applications with the same shared archive:

   java -XX:SharedArchiveFile=common.jsa -cp common.jar:hello.jar:hi.jar Hello
   java -XX:SharedArchiveFile=common.jsa -cp common.jar:hello.jar:hi.jar Hi

Source

Static Archive with Default CDS Archive

Unlike with a dynamic archive, a static archive does include core JDK classes. So if the default CDS archive has been deleted or corrupted on the system, this would not impact a JVM process at startup that is referencing a static archive.

 

Debugging CDS

CDS, is designed to be failsafe, and should seamlessly work in the background. CDS, when used with the default setting of -Xshare:auto, should if the shared archive is missing, corrupted, or invalid, fall back to loading the classes from the file system.

There, however, might be occasions where CDS does indeed cause the JVM to crash on startup. This section will cover common issues that cause CDS to fail, as well as problems that can occur while trying to create a CDS archive and how to diagnose and debug these issues.

Error Messages

The most ready form of debugging would be when CDS encounters an issue at startup that causes the JVM to exit. This should only happen when -Xshare:on is used. If the JVM encounters an error when trying to load a shared archive when -Xshare:auto is set (default), then the JVM will silently ignore the error and load classes normally. For this reason, -Xshare:on is discouraged in production settings.

Invalid or Missing Shared Archive

If the shared archive defined by -XX:SharedArchiveFile can not be found, the HotSpot JVM will print this message to the console:

An error has occurred while processing the shared archive file.
Specified shared archive not found (<name of the archive>).
Error occurred during initialization of VM

If the shared archive defined by -XX:SharedArchiveFile is corrupted or invalid HotSpot JVM will print this message to the console:

An error has occurred while processing the shared archive file.
The shared archive file has a bad magic number.
Error occurred during initialization of VM
Unable to use shared archive.

If the default CDS archive file is missing or corrupted HotSpot JVM will print this message to the console:

An error has occurred while processing the shared archive file.
Specified shared archive not found (<JAVA_HOME>/Contents/Home/lib/server/classes.jsa).
Error occurred during initialization of VM
Unable to use shared archive.

Invalid or Missing Classlist

When generating a static shared archive and the classlist defined by -XX: SharedClassListFile is not found the following error will be printed:

Error occurred during initialization of VM
Loading classlist failed: No such file or directory

When generating a static shared archive and the classlist defined by -XX: SharedClassListFile is corrupted, the following error will be printed:

An error has occurred while processing class list file HelloMessage-test.classlist 1:9.
Unknown input:
Invalid format
        ^
Error occurred during initialization of VM
class list format error.

Archive Generation Report

When generating a shared archive the JVM process will by default print out a lot of diagnostic information to the console. This information can be used to see which classes and libraries are or are not being added to the shared archive.

Classes/libraries being added to shared archive:

[0.007s][info][class,load] java.lang.Object source: jrt:/java.base
[0.007s][info][class,load] java.io.Serializable source: jrt:/java.base
[0.007s][info][class,load] java.lang.Comparable source: jrt:/java.base

Classes/libraries that were not able to be added to the shared archive:

[14.078s][warning][cds] Pre JDK 6 class not supported by CDS: 49.0 jdk/internal/reflect/GeneratedMethodAccessor55
[14.078s][warning][cds] Skipping org/springframework/beans/NotReadablePropertyException: Not linked

Generated Classlist

When generating a static archive, the classlist file can be inspected to see which classes and other JVM metadata will be added to the shared archive, as noted in the comments at the top of the generated file, this file should not be modified by hand. Below is a sample of what a classlist file would look like:

# NOTE: Do not modify this file.
#
# This file is generated via the -XX:DumpLoadedClassList=<class_list_file> option
# and is used at CDS archive dump time (see -Xshare:dump).
#
java/lang/Object
java/io/Serializable
java/lang/Comparable
java/lang/CharSequence
java/lang/constant/Constable
java/lang/constant/ConstantDesc
java/lang/String
java/lang/reflect/AnnotatedElement
java/lang/reflect/GenericDeclaration
java/lang/reflect/Type
java/lang/invoke/TypeDescriptor
java/lang/invoke/TypeDescriptor$OfField

Debug Logging

There are a couple of options for configuring HotSpot to output additional logging to console to better detail CDS's internal behavior.

Debug CDS Logging

  • -Xlog:cds=debug: Can be used during both the generation and loading of a shared archive to give detailed stats on classes and additional metadata being added to the shared archive, or during

  • -Xlog:cds+lambda=debug: Like -Xlog:cds=debug can be used at both archive generation and loading time. This option provides additional information specifically concerning CDS' handling of lambdas.

Debug Class Loading Logging

When loading an shared archive, the JVM argument -verbose:class can be used to see which classes are being loaded from the shared archive, or through HotSpot's normal class loading process. If a class is being loaded the from a shared archive it will report it's source as shared objects file. Like in this example output below:

[0.008s][info][class,load] java.lang.Object source: shared objects file
[0.009s][info][class,load] java.io.Serializable source: shared objects file

If the class is not being loaded from a shared archive, it will report its source location like here with java.lang.Object and java.io.Serializable being loaded from the module jrt:/java.base:

[0.007s][info][class,load] java.lang.Object source: jrt:/java.base
[0.007s][info][class,load] java.io.Serializable source: jrt:/java.base

More Learning

Last update: September 14, 2021


Back to Tutorial List