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.
$ 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)
(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.
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.
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.
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
.
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.
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
.
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
:
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
:
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.
/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).