Dev Study
← Spring コース

59. 起動を速くする方向と失うもの

依存を中に入れた(fat JAR)は、起動のたびにの読み込みと 集めを行います( の起動と、 の回)。この仕事を前倒しするほど起動は短くなりました。自己申告の起動時間は、fat JAR 約0.6秒、 をほどいて置く展開で約0.54秒、で約0.25秒、 再起動後に別環境で測った で約0.04秒です。

名前の似た2つは別物です。AOT キャッシュは JVM の機能で、学習用に1回走らせてクラスの読み込み結果を約53MBのファイルに残します。 は がビルド時に Bean 定義のコードを生成する仕組みです。native image は JVM を持たない実行ファイルで、ビルドは66〜68秒、ピークメモリは4.18〜4.29GB、実行時にクラスは足せず、残るの対象は事前に申告します。

期待と違う結果もありました。リファレンスは AOT キャッシュがメモリの削減にも役立つと書きますが、この測り方ではアイドル時の (実メモリ使用量)は288〜304MBで、無しの265〜287MBより減りませんでした。また fat JAR のままキャッシュを指定しても効きません。どれも がほぼ無いアプリの値で、起動後の処理性能は測っていません。

点線の付いた語はタップすると説明が出ます。用語集を開く →

実行時の仕事をビルド時へ移す段階(Started in の自己申告・各7回。Boot 4.1.1 / JDK 25。Bean がほぼ無い最小アプリの値)
fat JAR(実行可能 JAR)のまま0.599〜0.637 秒
入れ子の JAR をローダ経由で読む
JAR を展開しておく
展開0.534〜0.551 秒
払うもの: 配るものが1ファイルでなくなる
クラスの読み込み結果を持ち越す
展開 + CDS0.354〜0.370 秒
払うもの: 学習用の実行、約32MBのファイル
展開 + AOT キャッシュ0.252〜0.259 秒
払うもの: 学習用の実行、約53MBのファイル、JAR を変えたら作り直し
Bean 定義集めもビルド時に済ませる(Docker 再起動後の系列)
展開 + Spring AOT + AOT キャッシュ0.176〜0.204 秒
払うもの: どの Bean を作るかがビルド時に固定される
JVM を持たない実行ファイルにする
native image0.035〜0.045 秒
払うもの: ビルド 66〜68秒・ピーク 4.18〜4.29GB、実行時にクラスを足せない
  • ・上の3段は同じ系列、Spring AOT と native image は Docker の再起動をはさんだ後の系列です(native image は最終イメージの中で測定)。厳密な横並びではありません
  • ・AOT キャッシュ(JVM の機能)と Spring AOT(Spring がビルド時に行うコード生成)は、名前は似ていますが別物です
  • ・CDS と AOT キャッシュは JVM がそのまま動くので、実行時にクラスを読み込む仕組みは失いません
  • ・native image の成果物は 86,707,304 バイトで、fat JAR(約19.9MB)より大きくなりました。JRE は要りません

サンプルコード(フレームワーク環境が必要なため表示のみ)

// Spring AOT(spring-boot:process-aot)がビルド時に生成したソースの実物(Boot 4.1.1・全文)
// target/spring-aot/main/sources/com/example/demo/HelloController__BeanDefinitions.java
package com.example.demo;

import org.springframework.aot.generate.Generated;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;

/**
 * Bean definitions for {@link HelloController}.
 */
@Generated
public class HelloController__BeanDefinitions {
  /**
   * Get the bean definition for 'helloController'.
   */
  public static BeanDefinition getHelloControllerBeanDefinition() {
    RootBeanDefinition beanDefinition = new RootBeanDefinition(HelloController.class);
    beanDefinition.setInstanceSupplier(HelloController::new);
    return beanDefinition;
  }
}

// 実行時にクラスパスを走査して @RestController を探す代わりに、
// ビルド時に見つけた結果が「HelloController::new を呼べ」という普通の Java コードになっている。
// この最小アプリで生成されたのは、Java ソース 53本、クラス 75個、
// reachability-metadata.json(147,945 バイト)と native-image.properties。

図やコードに出てくる用語(タップで説明)

サンプルコードで身につける(5本) →

学んだ概念を、素朴な例から実務寄りの例まで5つの具体例で確認します。

公式ドキュメントで詳しく ↗