AppCDSを使ってみる
はじめに
AppCDS(Application Class Data Sharing)はCDS(Class Data Sharing)の拡張であり、Java 10以降で利用可能です。この機能を使うことで、アプリケーションでロードされるクラスを共有アーカイブから読み込むようになり、その起動時間を削減することができます。また、複数のJVMプロセス間でこの共有アーカイブからロードされるデータをシェアすることでメモリフットプリントを削減することが可能となるようです。
このブログでは以下のことを目指します。
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させて)共有アーカイブを作成するのはどうすれば良いんだろう...