AppCDSを使ってみる

はじめに

AppCDS(Application Class Data Sharing)はCDS(Class Data Sharing)の拡張であり、Java 10以降で利用可能です。この機能を使うことで、アプリケーションでロードされるクラスを共有アーカイブから読み込むようになり、その起動時間を削減することができます。また、複数のJVMプロセス間でこの共有アーカイブからロードされるデータをシェアすることでメモリフットプリントを削減することが可能となるようです。
このブログでは以下のことを目指します。

  • CDSとAppCDSの概要を把握する
  • AppCDSの共有アーカイブの作り方とその使い方を学ぶ

CDSとAppCDS

JVMを起動する際には、バイトコードのロード、検証、リンク、クラスとインターフェースの初期化などが実行されます。ここで、コアなクラスやインターフェースに関してはJVMの更新が起きない限り、毎回同じプロセスが実行されることになります。CDSではそのプロセスの実行結果をファイルに共有アーカイブとしてダンプし、JVM起動時にその共有アーカイブがメモリにマッピングされることにより起動時間の短縮が可能となります。
また、共有アーカイブの名の通り、このアーカイブされたデータは複数のJVMのプロセスで共有されるようになるため、メモリフットプリントの削減にもつながります。Java 12以降ではこのCDSの機能はデフォルトで有効化されています。
CDSの共有アーカイブファイルは通常JREのインストール時にclasses.jsaと言うファイルがlib/server配下に作成されます。

AppCDSはこのCDSの拡張であり、コアなクラスやインタフェースに限らず、アプリケーションのクラスやインターフェースでのCDSを可能とします。

AppCDSを使ってみる

環境

実行環境は以下のとおりです。

$ java --version
openjdk 14.0.1 2020-04-14
OpenJDK Runtime Environment (build 14.0.1+7)
OpenJDK 64-Bit Server VM (build 14.0.1+7, mixed mode, sharing)

$ uname -srvmpio
Linux 5.3.0-53-generic #47~18.04.1-Ubuntu SMP Thu May 7 13:10:50 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux

$ lsb_release -a
LSB Version:    core-9.20170808ubuntu1-noarch:security-9.20170808ubuntu1-noarch
Distributor ID: Ubuntu
Description:    Ubuntu 18.04.4 LTS
Release:    18.04
Codename:   bionic

サンプルアプリを作成する

今回はSpring BootでサンプルのWebアプリを作成し、AppCDSの機能を使ってみたいと思います。

プロジェクトの作成

プロジェクトはSpring Initializrを使って作成します。
設定は以下の通り

Project: Maven Project
Language: Java
Spring Boot: 2.3.0
Packaging: Jar
Java: 14

Hello, CDSアプリ

作成した、プロジェクトに以下のコントローラーを一つ作ります。

@RestController
public class HelloController {

    @GetMapping("/hello")
    public String greeting(){
        return "hello, cds";
    }
}

起動して、cURLを叩くとテキストが返されます。

$ curl localhost:8080/hello
hello, cds

次のステップに進む前にデフォルトでアプリケーションが起動にかかる時間を確認しておきます。
アプリケーションを立ち上げた際のログを見てみると起動時間が出力されています。

2020-06-05 19:38:41.458  INFO 27372 --- [           main] dev.hirooka.demo.appdcs.Application      : Started Application in 1.554 seconds (JVM running for 1.832)

約1.5秒ほど起動に時間がかかっているようですね。AppCDSを使った際の差分を知りたいので、覚えておきましょう。

さて、下準備はここまでです。

アプリケーションの共有アーカイブを作成する

AppCDSを使うために共有アーカイブを作成します。以下のコマンドを実行しアプリケーションを起動します。

java -XX:ArchiveClassesAtExit=myappCDS.jsa  -cp target/demo.appdcs-0.0.1-SNAPSHOT.jar org.springframework.boot.loader.JarLauncher

-XX:ArchiveClassesAtExit=myappCDS.jsaオプションは Dynamic CDS Archivesと呼ばれるもので、アプリケーションの終了時に共有アーカイブを作成してくれます。アプリケーションを停止するとmyappCDS.jsaという任意の名前の共有アーカイブが作成されているのが確認できます。

ちなみに、Java 13より以前のバージョンでは、-XX:DumpLoadedClassListオプションなどで、ロードされるクラス一覧を作成し、その一覧を用いて共有アーカイブを作成する必要がありました。

共有アーカイブを利用してアプリを起動してみる

共有アーカイブの作成までできたので、そのアーカイブを利用してアプリを起動してみます。

$ java -Xshare:on -XX:SharedArchiveFile=myappCDS.jsa  -cp target/demo.appdcs-0.0.1-SNAPSHOT.jar org.springframework.boot.loader.JarLauncher
                                            .
                                            .
                                            .
2020-06-05 19:44:25.181  INFO 27793 --- [           main] dev.hirooka.demo.appdcs.Application      : Started Application in 1.331 seconds (JVM running for 1.615)

起動時間をAppCDSを利用しない場合と比べてみると約0.2秒ほど早くなっているのがわかります。
サンプルアプリは単純なものでしたが、アプリケーションが大きければ大きいほど、共有アーカイブとして保存されるクラスは多くなり、起動時間の短縮の幅も相対的に大きくなると思われます。

また、-Xlog:class+load:file=mycds.logオプションをつけてアプリを起動すると、ロードされるクラスをログ出力することが可能です。shared objects fileと言うログ出力が行われているクラスが、共有アーカイブからロードされているクラスとなります。
ログを確認すると以下のような出力が行われており、Springで提供されるクラスのいくつかが共有アーカイブからロードされていることがわかります。

$ less mycds.log

(抜粋)

[0.024s][info][class,load] org.springframework.boot.loader.Launcher source: shared objects file (top)

感想

CI/CDパイプラインなどにこのプロセスをうまく組み込んで、コンテナのイメージ内に共有アーカイブを含んで利用すれば起動は早くなるんだろうなぁとふんわり考えていたりします。
アプリケーションを起動せずに(もしくは起動してもすぐにexitさせて)共有アーカイブを作成するのはどうすれば良いんだろう...

参考資料