AppCDS @ OpenJDK

OpenJDK and AppCDS

It was announced at this year's JavaOne 2017 that features from the closed repository of Oracle JDK will be merged into OpenJDK. As part of this, AppCDS (Application Class Data Sharing) was entered on 11/27 (11/28 in Japan time) in OpenJDK. 78b2ecdd3c4b). Today I would like to move this function and try it out. ~~ Anyway, I want you to do even one of the crashes. ~~

That said, the Oracle JDK already comes from JDK 8u40, so anyone who knows it already knows it.

Premise

$ hg clone http://hg.openjdk.java.net/jdk/hs
$ cd hs
$ bash configure --with-extra-cflags="-Wno-error=deprecated-declarations -Wno-error=nonnull -Wno-error=maybe-uninitialized" --disable-warnings-as-errors
$ make images
$ ${PWD}/build/linux-x86_64-normal-server-release/images/jdk/bin/java -version
openjdk version "10-internal"
OpenJDK Runtime Environment (build 10-internal+0-adhoc.fedora.hs)
OpenJDK 64-Bit Server VM (build 10-internal+0-adhoc.fedora.hs, mixed mode)

What is CDS (Class Data Sharing)?

(System) You can create a file that contains read-only metadata for classes called "shared archives" that are dumped by loading the classes used from the JAR file into the JVM private internal representation. It is a mechanism that the startup time is shortened because it is faster to memory map and restore this file (shared archive) than to load the class from 1 when starting the JVM.

As the name implies, this "shared archive" can be shared between multiple JVMs, so sharing it among multiple JVM processes also reduces the footprint (dynamic memory footprint). Shared archives are installed by default, depending on how you install the JRE. You can also make it yourself.

CDS targeted system JAR files, but AppCDS also targeted the user's application code. Properly, CDS was only the target class of the bootstrap class loader, but AppCDS can additionally archive the target classes of the built-in system class loader (also called application class loader), platform class loader and custom class loader.

Try AppCDS

Originally, it is good to see the effect when the class is executed in a large number of applications, but since there is nothing that can be easily prepared, I created it with the default value of Spring Initializr Let's improve the startup time of the demo app demo-0.0.1-SNAPSHOT.jar.

The default looks like the following.

$ java -cp target/demo-0.0.1-SNAPSHOT.jar org.springframework.boot.loader.JarLauncher

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.5.8.RELEASE)
:
2017-12-01 06:10:17.856  INFO 27529 --- [           main] com.example.demo.DemoApplication         : Started DemoApplication in 1.103 seconds (JVM running for 1.841)
:

1.103 seconds is the baseline.

Make a list of classes to archive

To create a shared archive, first extract the classes used by the following command.

$ java -Xshare:off -XX:+UseAppCDS -XX:DumpLoadedClassList=demo.list -cp target/demo-0.0.1-SNAPSHOT.jar org.springframework.boot.loader.JarLauncher
:
2017-12-01 06:09:04.430  INFO 27451 --- [           main] com.example.demo.DemoApplication         : Started DemoApplication in 320.159 seconds (JVM running for 454.536)
:

Get the list demo.list of the usage classes extracted while running the application. Since it is being extracted, it takes a long time to start up like a fool. The contents are a list of classes as follows.

$ cat demo.list
java/lang/Object
java/lang/String
:
org/springframework/boot/loader/JarLauncher
org/springframework/boot/loader/ExecutableArchiveLauncher
:

Of course, it is also possible to add it by hand.

Create a shared archive

Now that we have a list of classes, load it once, dump it, and create a shared archive.

$ /home/fedora/workspace/hs/build/linux-x86_64-normal-server-release/images/jdk/bin/java -Xshare:dump -XX:+UseAppCDS -XX:SharedClassListFile=demo.list -XX:SharedArchiveFile=demo.jsa -XX:+IgnoreUnverifiableClassesDuringDump -cp target/demo-0.0.1-SNAPSHOT.jar
narrow_klass_base = 0x0000000800000000, narrow_klass_shift = 3
Allocated temporary class space: 1073741824 bytes at 0x00000008c0000000
Allocated shared space: 3221225472 bytes at 0x0000000800000000
Loading classes to share ...
Loading classes to share: done.
Rewriting and linking classes ...
Rewriting and linking classes: done
Number of classes 1866
    instance classes   =  1789
    obj array classes  =    69
    type array classes =     8
Updating ConstMethods ... done.
Removing unshareable information ... done.
Scanning all metaspace objects ...
Allocating RW objects ...
Allocating RO objects ...
Relocating embedded pointers ...
Relocating external roots ...
Dumping symbol table ...
Dumping String objects to closed archive heap region ...
Dumping objects to open archive heap region ...
Relocating SystemDictionary::_well_known_klasses[] ...
Removing java_mirror ... done.
mc  space:      8800 [  0.0% of total] out of     12288 bytes [ 71.6% used] at 0x0000000800000000
rw  space:   5381944 [ 22.2% of total] out of   5382144 bytes [100.0% used] at 0x0000000800003000
ro  space:   9586008 [ 39.5% of total] out of   9588736 bytes [100.0% used] at 0x0000000800525000
md  space:      6160 [  0.0% of total] out of      8192 bytes [ 75.2% used] at 0x0000000800e4a000
od  space:   8581008 [ 35.3% of total] out of   8581120 bytes [100.0% used] at 0x0000000800e4c000
st0 space:    634880 [  2.6% of total] out of    634880 bytes [100.0% used] at 0x00000007bff00000
oa0 space:     81920 [  0.3% of total] out of     81920 bytes [100.0% used] at 0x00000007bfe00000
total    :  24280720 [100.0% of total] out of  24289280 bytes [100.0% used]

Get the shared archive demo.jsa.

Check if it's faster

Let's check if it became faster by loading the shared archive that we created.

$ /home/fedora/workspace/hs/build/linux-x86_64-normal-server-release/images/jdk/bin/java -Xshare:on -XX:+UseAppCDS -XX:SharedArchiveFile=demo.jsa -cp target/demo-0.0.1-SNAPSHOT.jar org.springframework.boot.loader.JarLauncher
:
2017-12-01 06:20:39.487  INFO 28908 --- [           main] com.example.demo.DemoApplication         : Started DemoApplication in 0.992 seconds (JVM running for 1.414)
:

It's 0.992 seconds, which is about 10% faster, which is a good feeling. In terms of absolute numbers, AppCDS works better with more classes due to its characteristics, so it may be more effective in applications with many dependencies.

See what's really going on

You can check if class loading is done from a shared archive or library (class file). Class loading can be logged with -Xlog: class + load = info.

AppCDS enabled

You can see that both the core library and the main class are loaded from shared objects file.

$ /home/fedora/workspace/hs/build/linux-x86_64-normal-server-release/images/jdk/bin/java -Xlog:class+load=info -Xshare:on -XX:+UseAppCDS -XX:SharedArchiveFile=demo.jsa -cp target/demo-0.0.1-SNAPSHOT.jar org.springframework.boot.loader.JarLauncher
[0.003s][info][class,load] opened: /home/fedora/workspace/hs/build/linux-x86_64-normal-server-release/images/jdk/lib/modules
[0.055s][info][class,load] java.lang.Object source: shared objects file
[0.055s][info][class,load] java.io.Serializable source: shared objects file
[0.055s][info][class,load] java.lang.Comparable source: shared objects file
:
[0.098s][info][class,load] org.springframework.boot.loader.Launcher source: shared objects file
[0.098s][info][class,load] org.springframework.boot.loader.ExecutableArchiveLauncher source: shared objects file
[0.098s][info][class,load] org.springframework.boot.loader.JarLauncher source: shared objects file
:

AppCDS disabled

You can see that it is reading from the library file unlike when it is enabled.

$ /home/fedora/workspace/hs/build/linux-x86_64-normal-server-release/images/jdk/bin/java -Xlog:class+load=info -Xshare:off -cp target/demo-0.0.1-SNAPSHOT.jar org.springframework.boot.loader.JarLauncher
[0.003s][info][class,load] opened: /home/fedora/workspace/hs/build/linux-x86_64-normal-server-release/images/jdk/lib/modules
[0.013s][info][class,load] java.lang.Object source: jrt:/java.base
[0.014s][info][class,load] java.io.Serializable source: jrt:/java.base
[0.014s][info][class,load] java.lang.Comparable source: jrt:/java.base
:
[0.131s][info][class,load] org.springframework.boot.loader.Launcher source: file:/home/fedora/workspace/startup/demo/target/demo-0.0.1-SNAPSHOT.jar
[0.131s][info][class,load] org.springframework.boot.loader.ExecutableArchiveLauncher source: file:/home/fedora/workspace/startup/demo/target/demo-0.0.1-SNAPSHOT.jar
[0.131s][info][class,load] org.springframework.boot.loader.JarLauncher source: file:/home/fedora/workspace/startup/demo/target/demo-0.0.1-SNAPSHOT.jar
:

Summary

There are other small options available, but at first glance it seems that the features available in the Oracle JDK are coming, as announced. A simple demo app that uses the framework reduced the amount by about 10%, but an app with a larger number of classes may be more effective. How about using it during development?

I wonder if it will crash if it is combined with AOT strangely.

[Extra edition] Make it faster

/home/fedora/workspace/hs/build/linux-x86_64-normal-server-release/images/jdk/bin/java -Xlog:all=off -XX:+TieredCompilation -XX:TieredStopAtLevel=1 -XX:+UseSerialGC -Xverify:none -Xshare:on -XX:+UseAppCDS -XX:SharedArchiveFile=demo.jsa -cp target/demo-0.0.1-SNAPSHOT.jar org.springframework.boot.loader.JarLauncher
2017-12-01 07:14:41.301  INFO 29775 --- [           main] com.example.demo.DemoApplication         : Started DemoApplication in 0.811 seconds (JVM running for 1.178)

It was 0.811 seconds! I wanted to try a tiny custom runtime that completely removed unnecessary platform modules with jlink, but I didn't have time, so that's it for today!

In addition to these runtime ideas, there is also a way to keep the JVM running and running it in order to shorten the JVM startup time. There are various approaches, but examples of implementation are nailgun and falchion that implements non-disruptive deployment using SO_REUSEPORT. It may be interesting to take a look at .com / kawasima / falchion).

Recommended Posts

AppCDS @ OpenJDK
OpenJDK installation
OpenJDK 11 installation memo
OpenJDK 11 Flight Recorder