プログラミング
Raspberry Pi 5 上での JavaFX 27 ネイティブイメージ
JavaFX 27 Native Image on a Raspberry Pi 5 (ennerf.github.io)
要約
この記事では、GraalVM Native Image を使用して JavaFX アプリケーションを Raspberry Pi 5 上でネイティブ実行する手法について解説しています。ネイティブイメージ化により、起動時間や初回表示時間が大幅に短縮され、ユーザーエクスペリエンスが向上することが、ベンチマーク結果と共に示されています。また、JavaFX をネイティブイメージ化するための既存ツール(Gluon Substrate、BellSoft Liberica NIK)の課題と、それらを克服するために開発された新しいツールキット StaticFX についても紹介されています。
全文翻訳
Raspberry Pi 5 上での JavaFX 27 ネイティブイメージ
当初、GraalVM Native Image を JavaFX をモバイルターゲットにデプロイする手段として使用していました。しかし、モバイルアプリがデスクトップアプリよりもキビキビと動作することに気づき、GUI および CLI アプリケーションをすべてAhead-of-Time (AOT) コンパイルに移行しました。GUI はほとんどの場合、解釈モードで実行されます(ユーザーが JIT しきい値に到達するには数千回クリックする必要があるでしょう)。AOT を使用すると、起動時間と初回訪問時間が最大 90% 削減され、ユーザーエクスペリエンスが著しく向上します。
表 1. Raspberry Pi 5 上での AtlantaFX サンプラー比較
| AtlantaFX Sampler (RPi5) | jlink (JIT) | jlink + CDS (JIT) | Native Image (AOT) |
|---|---|---|---|
| Distribution size | 139 MB | 155 MB (+12%) | 124 MB (-11%) |
| Time to first window | 3.6 s | 2.7 s (-26%) | 0.5 s (-86%) |
| First visit: HTMLEditor (WebView) | 2.39 s | 2.31 s (-3%) | 0.22 s (-91%) |
| First visit: Overview (FXML) | 1.90 s | 1.50 s (-21%) | 0.32 s (-83%) |
| Private memory after startup | 224 MB | 244 MB (+9%) | 164 MB (-27%) |
| Private memory after all pages | 1424 MB | 1396 MB (-2%) | 634 MB (-55%) |
実際、ハイエンドの Ryzen 9 9950X デスクトップで jlink + CDS を使用して AtlantaFX サンプラーを起動すると、小さな Raspberry Pi 5 上で AOT コンパイルされた同じアプリケーション(0.5 秒、ベンチマーク参照)よりも 2.5 倍以上時間がかかります(1.3 秒)。全体の実行は、FXML、WebView、MediaPlayer、AWT インテグレーションを含むすべてのページを網羅した短いビデオウォークスルーでキャプチャされています。
JavaFX は、すべてが設定されていれば、ネイティブイメージで非常にうまく機能しますが、複雑なアプリケーションを初めて実行するのは、通常、簡単な経験ではありません。この急峻な初期ハードルが、JavaFX、そしてデスクトップ Java 全般に、ネイティブイメージとの非互換性や動作不良という評判をもたらしてしまいました。この原因の大きな部分は、ネイティブレイヤーを隠すために舞台裏で多くの「マジック」を実行する複雑で特殊なツール群にあると思います。何かが壊れると、多くの Java 開発者がデバッグ方法を知らないようなエラーが発生します。
この記事では、これらのツールの内部構造を解明し、最新のエンタープライズ Oracle GraalVM を最新の JavaFX 27 リリースとともにデスクトップターゲットで実行するために、なぜ独自のツールキットを構築したのかを示します。
3つのアプローチ
Quarkus や Micronaut のような、Ahead-of-Time コンパイルと制御されたサーバー環境向けに設計されたフレームワークとは異なり、JavaFX はネイティブイメージよりもはるかに古くから存在する、大規模で高度に動的な GUI フレームワークであり、さまざまなオペレーティングシステムを持つ任意のクライアントマシンで実行する必要があります。そのスタック全体を機能させるには、リンカー修正、置換、および約1000のエントリメタデータを持つ専用のソリューションが必要です。Gluon の Substrate と BellSoft の Liberica NIK が確立された2つの選択肢であり、私たちは StaticFX を3番目の選択肢としてリリースしました。
Gluon Substrate
Gluon の Substrate は、Windows、macOS、Linux、およびモバイルターゲットの iOS や Android を含む、ほぼあらゆるものに JavaFX をポートできる複雑なツールチェーンです。JavaFX の有無にかかわらず実行可能なファイル、共有ライブラリ、静的ライブラリを作成でき、リソースを処理し、カスタム C 拡張機能を追加し、OS 固有のバンドルやインストーラーをビルドし、SSH を介して小さなターゲットにリモートデプロイすることもできます。
それらをすべて機能させるには、GraalVM ディストリビューションからモバイルツーリング、専用の Maven および Gradle プラグインに至るまで、ほぼすべてのレイヤーで変更されたビルドとカスタムツールが必要です。その結果は非常に印象的なスタックであり、Gluon は JavaFX がどこでも実行できるようにする多くのアップストリーム変更に貢献したことで称賛に値します。
しかし、移動ターゲットの数が多いため、壊れやすい組み合わせとなり、簡単に破綻する可能性があります。Gluon によると、「iOS、Android、AOT コンパイラ、JVM コンポーネント、または JDK の新しいリリースごとにパッチと調整が必要」であったため、彼らは OpenJDK Mobile に方向転換しています。これは、メタデータが不要になり、ユーザーエクスペリエンスを大幅に簡素化するはずの、オープンワールドモデルを備えた、より標準的な JDK + Leyden アプローチです。
とはいえ、既存のツールは引き続きメンテナンスアップデート(ドキュメント参照)を受け取っており、膨大な基盤となる複雑さを考慮すると、比較ユーザーフレンドリーです。しかし、Gluon が現在サポートしている最新バージョンは GraalVM 23 Community Edition (CE) と JavaFX 21 であり、新しい JavaFX リリースを使用するためのサポートされている方法はありません。
私たちは Gluon のツールを内部で1〜2年間使用しており、モバイルターゲットでは今でも参照しています。JavaFX はモバイルでは当初予想していたよりもはるかにうまく機能しており、フル 2D および 3D レンダリング、スタイリングやレイアウトのホットリロード(Mobile Scope 参照)を備えた 5 つのターゲットすべてにデプロイできるのは非常に便利です。
ライセンスモデルに関する一般的な混乱があります。ライセンスが必要なのはリッチコンポーネントライブラリのみであり、ビルドツールはすべてのプラットフォームで無料で使用できます。
BellSoft’s Liberica NIK
BellSoft は、より焦点を絞ったアプローチを取り、デスクトップアプリ専用の GraalVM CE ディストリビューションである Liberica NIK (full) を作成しました。すぐに何かを実行したい場合は、NIK が開始するのに最も簡単な方法です。
このディストリビューションには、JavaFX および Swing/AWT モジュール、および両方の対応するメタデータがバンドルされています。ユーザーは GRAALVM_HOME を設定するだけで、GraalVM の標準の native-maven-plugin で使用できます。標準の Oracle GraalVM でさえ静的 AWT アーカイブをバンドルしていますが、ユーザーは必要なメタデータを自分で生成する必要があります。
執筆時点では、NIK (full) は LTS バージョンの GraalVM 25 CE と JavaFX 25、および 4 つの主要なデスクトップアーキテクチャでの GPU アクセラレーションパイプラインをサポートしています。現在、ソフトウェアパイプラインまたは linux-aarch64 ターゲットはサポートしていません。Gluon も BellSoft も、ネイティブイメージでのメディアおよび Web モジュールはサポートしていません。
HEBI’s StaticFX
私たちは約2年間、デスクトップアプリケーションで Liberica NIK を使用しており、確かにうまく機能していました。しかし、一部のアプリケーションではミリ秒以下のレイテンシを測定しており、コミュニティエディションのエスケープ解析が HotSpot よりも割り当てをはるかに少なくしか排除せず、GC の動作の違いも相まって、不正確な測定値に遭遇しました。私たちの最悪の割り当てソースは、Math::pow の単純な new double[]{a,b} であることが判明しましたが、これは HotSpot では決して割り当てられません。
Oracle GraalVM (旧 Enterprise Edition) が 2023 年に GFTC ライセンスの下で無料で使用可能になったため、より良いエスケープ解析、すべてのプラットフォームでの G1 GC、およびプロファイルガイド付き最適化 (PGO) のようなその他のエンタープライズ限定機能を取得するために切り替えたいと考えました。
また、Python や C++ のような他の言語に高性能 JavaFX 可視化をネイティブ共有ライブラリとして公開する hebi-charts のために、より新しい JavaFX バージョンにアップグレードする必要がありました。私たちの目標は、JavaFX 26 で導入されたヘッドレスモードとソフトウェアパイプラインを使用して、linux-aarch64 デバイスで SSH 経由でグラフィックスや画像を生成できるようにすることでした。
Substrate も Liberica NIK もこの特定の組み合わせをサポートしていないため、StaticFX を作成することになりました。ある意味では、JavaFX を JDK から分離したことが、ベンダーサポートを必要とせずにライブラリとして自由に更新できるという利点になりました。
ビルドシステムを引き継ぐのではなく、StaticFX は標準ツールチェーン上の通常の GraalVM Feature として実行され、jfx ソースを一切変更せずにネイティブイメージに必要なすべての構成を渡すことができます。また、静的リンクが利用できない場所では動的リンクもサポートしているため、メディア、Web、および Swing の相互運用性を含むすべての JavaFX モジュールをサポートできます。
これは 2 つのアーティファクトで構成されます: バージョン固有の静的アーカイブとメタデータ用の jfx-static-libs、および比較的バージョンに依存しないグルー用の jfx-static-feature です。GraalVM はクラスパス上のアーティファクトから自動的に設定ファイルを検出するため、ユーザーは org.openjfx jars の隣に 2 つの依存関係を追加するだけで済みます。
<dependency>
<groupId>us.hebi.graalvm</groupId>
<artifactId>jfx-static-feature</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>us.hebi.graalvm</groupId>
<artifactId>jfx-static-libs</artifactId>
<version>${