Java 13のDynamic CDSで想像以上に起動速度が速くなった

Java 13で、読み込み済みのクラスデータを終了時に保存するDynamic CDSが導入されました。
https://openjdk.java.net/jeps/350

で、試してみたら結構起動速度が変わっていました。

Micronautで試してみます。

追記: Java 10でApplication Class-Data Sharingが入っているので、自分でクラスリストを作ってダンプすれば同等のことができていたのだけど、面倒さが低減された感じです。LTSであるJava 11ではAppCDSを使うといいと思います。

追記2: 自分でダンプしたほうがかなり速かった
Dynamic CDSよりJava10からある自力ダンプの方が起動が速い - きしだのHatena

準備

インストールは全部SDKMAN!で行います。(Windowsの場合はCygwinかWSLを使います)

$ curl -s "https://get.sdkman.io" | bash

ターミナルを開き直すとSDKMAN!が使えるようになります。
まずはJDKを。Micronautは現時点ではJDK13でビルドできないので、JDK12を使います。

$ sdk use java 12.0.2-open

次にMicronautをインストールします。

$ sdk use micronaut 1.2.4

Micronautのアプリケーションを作成します。

$ mn create-app myapp

できたフォルダに移動。

$ cd myapp

ビルドします。テストでこけることがあるので、テストをスキップ。

$ ./gradlew -x test build

実行してみます。

$ java -jar build/libs/myapp-0.1.jar 
21:02:22.782 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 1103ms. Server Running: http://localhost:8080

終了はctrl+cで。

Dynamic CDSを試してみる

Dynamic CDSでクラスデータを保存するときは-XX:ArchiveClassesAtExitをつけてアーカイブファイル名を指定します。

$ java -XX:ArchiveClassesAtExit=mn.jsa -jar build/libs/myapp-0.1.jar 
myapp $ java -XX:ArchiveClassesAtExit=mn.jsa -jar build/libs/myapp-0.1.jar 
22:09:12.011 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 1681ms. Server Running: http://localhost:8080
^C22:09:14.530 [Thread-2] INFO  io.micronaut.runtime.Micronaut - Embedded Application shutting down
[4.593s][warning][cds] Skipping io/micronaut/http/client/$DefaultHttpClientConfiguration$DefaultConnectionPoolConfigurationDefinition: Not linked
[4.593s][warning][cds] Skipping io/reactivex/internal/operators/mixed/ObservableSwitchMapCompletable: Not linked
[4.593s][warning][cds] Skipping io/reactivex/internal/operators/completable/CompletableTimeout: Not linked
...

18MBのファイルができています。

$ ls -l -h mn.jsa
-r--r--r--  1 kishida  staff    18M 10 17 22:09 mn.jsa

このシェア用アーカイブファイルを利用するときは-XX:SharedArchiveFileをつけてアーカイブファイルを指定します。

$ java -XX:SharedArchiveFile=mn.jsa -jar build/libs/myapp-0.1.jar 

結果はあとでまとめますが、結構起動が速くなりました。

結果

ということで結果です。
Javaの標準クラスに関してはDefault CDSとしてJDK12から用意されるようになっていますが、これを使わないものも試してみました。
-Xshare:offをつけるとDefault CDSファイルが使われなくなります。

$ java -Xshare:off -jar build/libs/myapp-0.1.jar

もうひとつ、Java 9から導入されているAOTも試してみます。今回はjava.baseモジュールだけAOTをかけてみます。

$ jaotc --output=libjava.base.so --module java.base

これを使うときは-XX:AOTLibraryをつけてライブラリファイルを指定します。

$ java -XX:AOTLibrary=./libjava.base.so -XX:SharedArchiveFile=mn.jsa -jar build/libs/myapp-0.1.jar

6回連続で起動して2番目以降の平均を計測値にします。
また、Micronautの起動時間はmainメソッド以降の実行時間なので、mainメソッドの最初に

System.out.println(ManagementFactory.getRuntimeMXBean().getUptime());

をつけてJVM自体の起動時間も計測しています。

package myapp;

import io.micronaut.runtime.Micronaut;
import java.lang.management.ManagementFactory;

public class Application {

    public static void main(String[] args) {
System.out.println(ManagementFactory.getRuntimeMXBean().getUptime());
        Micronaut.run(Application.class);
    }
}
JVM起動 アプリケーション起動
No CDS 149.6 1028.6
Default CDS 98.6 1009.2
Dynamic CDS 89.2 688.8
AOT+DynCDS 144 625.6

ということでこんな結果に。
Default CDSではアプリケーション起動時間はあまり変わりませんがJVMの起動時間が短くなっています。そしてDynamic CDSでアプリケーションのクラスデータも再利用すると、アプリケーション起動時間がかなり縮まっていますね。
残念なのはAOTで、アプリケーション起動時間が縮まっているけどJVM起動時間がすこしのびて、トータルでは結局同じくらいの起動時間になっています。
f:id:nowokay:20191017224337p:plain