GraalVM Native Image Quick Reference v1

原文はこちら。
The original article was written by Olya Gupalo (Member of Tech Staff for GraalVM at Oracle).
https://medium.com/graalvm/graalvm-native-image-quick-reference-4ceb84560fd8

これは旧版です。最新のNative Image Quick Reference v2をチェックしてください。

Native Image Quick Reference v2
https://medium.com/graalvm/native-image-quick-reference-v2-332cf453d1bc
https://logico-jp.io/2022/11/18/native-image-quick-reference-v2/

GraalVMのビジュアルクイックリファレンスの続きです。この記事では、最もよく使われ、重要なオプションかつ機能であるGraalVM Native Imageを取り上げます。

GraalVM quick reference
https://medium.com/graalvm/graalvm-quick-reference-b8d1dfe24241
https://logico-jp.io/2021/01/07/graalvm-quick-reference/

Native Imageを使うとアプリケーションをバイナリ、通常は実行可能バイナリにコンパイルできますが、共有ライブラリも作成できます。実行可能ファイルは自己完結型で、起動が速く、サイズも非常にリーズナブルなので、オンプレミスやクラウドサーバーのコンテナベースの展開に最適です。

以下はnative-imageコマンドの主要なオプションやコマンドを1ページにまとめたものです(画像をクリックするとPDFをダウンロードできます)。

Image for post
GraalVM Native Image quick reference (web version, for printing please use pdf)

このリファレンスはA4にきちんと収まりますので、壁に貼ったり、GraalVMでできること、オプションの使い方のリマインダーとして利用いただけます。印刷するのであればシャープに見えるPDF版をお使いください。USレターサイズのほうがお使いのプリンターの設定が楽な場合は、USレターサイズ版をお使いください。

PDF版
https://www.graalvm.org/uploads/quick-references/native-image-quick-reference-v2_A4.pdf
USレター版
https://www.graalvm.org/uploads/quick-references/native-image-quick-reference-v2_US_Letter.pdf

この記事ではクイックリファレンスの情報を確認し、より詳細に説明します。

native-imageを使い始めるには、GraalVMをインストールする必要があります。そして、updaterユーティリティを使ってnative-imageもインストールを忘れないでください。

Quick Start Guide
http://graalvm.org/java/quickstart/
GraalVM Updater
https://www.graalvm.org/reference-manual/graalvm-updater/

<graalvm-home>/bin/gu install native-image

コンパイルは、native-imageがローカルのネイティブバイナリに依存します。これはプラットフォームによって異なります。システム要件を満たすことで、native-imageの利用が容易になります。

Prerequisites
https://www.graalvm.org/reference-manual/native-image/#prerequisites

Building native images

With Native Imageを使うと、Javaバイトコードをネイティブ実行形式もしくはネイティブ共有ライブラリに変換できます。両方の場合において、イメージには少なくとも1個のエントリポイントのメソッドが存在している必要があります。

Native Executable

スタンドアロンのネイティブ実行形式のビルドがデフォルトの動作モードです。ネイティブイメージビルダーのエントリポイントメソッドは、文字列の配列としてコマンドライン引数を取るシグネチャ付きのJavaのmainメソッドにすることができます。エントリポイントの個数は任意です。

public static void main(String[] arg) { /* ... */ }

通常、全ての依存関係を持つJARファイルからJavaプロジェクトのネイティブ実行形式をビルドします。

native-image [options] -jar MyApp.jar [imagename]

Javaクラスファイルからネイティブ実行形式を生成することも可能ですが、別のパッケージやJAR、ZIPをクラスファイルで渡すのでなければ、これはあまり実用的ではありません。

native-image [options] -cp jar:com/package/**/myApp.jar MyApp [imagename]

同様に、-H:Class=ClassNameでmainメソッドを含む別のJavaクラスを指定し、ネイティブイメージビルダーのためのエントリポイントにできます。ネイティブ実行ファイルはプロジェクトのターゲットもしくはユーザーの現在の作業ディレクトリに格納されますが、例えば以下のようにして出力ディレクトリを変更できます。

native-image path/imagename Class

ネイティブイメージビルダーは、ビルド時に指定されたすべてのリソースをピックアップします。 -H:IncludeResources=com/package/**/fileName.xml のように、ファイル名にマッチする正規表現を使用します。同様に、 -H:IncludeResourceBundles=<カンマ区切りのリソースバンドルのリスト>の形で、リソースをバンドルで渡すこともできます。リソースへのアクセスについての詳細なガイドはこちらを参照してください。

Accessing Resources in Native Images
https://www.graalvm.org/reference-manual/native-image/Resources/

Native Imageではビルド時にクラス初期化をサポートしますが、特定のポリシーが適用されます。

Class Initialization in Native Image
https://www.graalvm.org/reference-manual/native-image/ClassInitialization/

コマンドラインから --initialize-at-build-time --initialize-at-run-time オプションを使って、ビルド時または実行時にどのクラスやパッケージを初期化するか選択できます。このオプションの後には、パッケージとクラス(そして暗黙のうちにすべてのスーパークラス)のカンマで区切られたリストが続きます。空の文字列はすべてのパッケージを指定します。

ビルドプロセスをより詳しく調べたいのであれば、デバッガをアタッチできます。

--debug-attach=[port]

IDEから接続すると、ビルダーはJavaプロセスなので、使い慣れたツールで実行をステップスルーしてください。

 --enable-all-security-services というオプションを使うと、全てのセキュリティサービスクラスをイメージに含めることができます。HTTPSのサポートを有効にするには、 --enable-https を渡してください( --enable-url-protocols で全てのURLプロトコルの有効化が可能です)。

その他の手軽なオプションとして、キャラクタセットのサポートを追加する -H:+AddAllCharsets 、全てのタイムゾーンの事前初期化のための -H:+IncludeAllTimeZones があります。これは生成されるバイナリのサイズが大きくなる可能性がありますが、例えばアプリケーションで必要とするタイムゾーンの正確な組み合わせがわからない場合には、便利な解決策です。

--install-exit-handlers オプションにはシグナルハンドラが含まれており、Dockerコンテナのようなコンテナ環境用のネイティブイメージを生成するのに適しています。

コンテナベースのデプロイのためのもう一つのオプションが、 --static --libc=glibc | musl で、これを使うと追加のライブラリの依存関係なしで静的リンクイメージをビルドできます。

Static Native Images
https://www.graalvm.org/reference-manual/native-image/StaticImages/

 -H:+StaticExecutableWithDynamicLibC を渡せば、libcを動的にリンクする静的イメージをビルドすることもできます。これはdistroless Dockerイメージと組み合わせて使うのが一般的です。

--language:java|js|python|ruby|llvm|wasm により、ネイティブイメージビルダーに特定の言語エンジンをロードするよう指示することで、“polyglot” なネイティブイメージをビルドすることもできます。

Native Shared Library

ネイティブ共有ライブラリのビルドができる機能により、あなたのコードをネイティブアプリケーションで有用にするための別の可能性が開かれます。

Create a Shared Library
https://www.graalvm.org/reference-manual/native-image/ImplementingNativeMethodsInJavaWithSVM/#create-a-shared-library

Java APIをネイティブ共有ライブラリにコンパイルできるので、別のJavaクラスファイルやC、C++といった別の言語からも呼び出すことができます。これが興味深いものになり得る理由は、以下の記事をご一読ください。

The many ways of polyglot programming with GraalVM
https://medium.com/graalvm/3-ways-to-polyglot-with-graalvm-fb28c1542b45
https://logico-jp.io/2020/12/01/the-many-ways-of-polyglot-programming-with-graalvm/

ネイティブ共有ライブラリのエントリポイントになる静的メソッドには、@CEntryPointというアノテーションがついている必要があります。エントリポイントメソッドはstaticでなければならず、オブジェクトではないパラメータと戻り値型のみ持つことができます。エントリポイントメソッドのパラメータの一つは、IsolateThreadもしくはIsolate型である必要があります。エントリポイントの個数は任意です。

@CEntryPoint 
static int add(IsolateThread thread, int a, int b) { 
    return a + b;
}

メインクラスを指定しないJARファイルの共有ライブラリを生成するには、ライブラリ名を指定してください。

$ native-image --shared -jar jarfile [libraryname]

Native Imageは、ヘッダファイルlibraryname.hと、Linuxではlibraryname.so、macOSではlibraryname.dylibというライブラリを生成します。このライブラリを他のJavaコードから読み込むには、loadまたはloadLibraryメソッドを使用します。

System.loadLibrary("libraryname");

C++プログラムなどからライブラリを利用する場合は、生成されたヘッダファイルをインクルードしてください。

#include <libraryname.h>

より多くの例や網羅的なドキュメントがウェブサイトで公開されています。

Implementing Native Methods in Java with Native Image
https://www.graalvm.org/reference-manual/native-image/ImplementingNativeMethodsInJavaWithSVM/

Configuring the build

ネイティブイメージビルダーは静的解析を実行して全ての可能性ある実行パスを把握し、その到達可能性のあるデータをイメージに書き込みます。そのため、動的なJava機能の中には、ビルドプロセス中に設定が必要なものがあります。例えば、実行時にロードされる任意のreflective callやリソースを、構成ファイルの形やイメージビルド時に特定のオプションを渡すことにより、ネイティブイメージビルダーに知らせる必要があります。

構成ファイルの作成にあたっては、GraalVMで提供されるJavaエージェントを適用すると便利です。これは、JVM上でアプリケーションを実行する際に動的機能のすべての使用状況を追跡し、それらをJSONファイルに書き出します。エージェントにこれらのファイルをクラスパス上の /META-INF/native-image/ の下に置くように指示すれば、native-image は自動的にそれらのファイルを拾い上げます。トレースエージェントでJavaプロセスを実行して、構成ファイルを生成します。

$ java -agentlib:native-image-agent=config-output-dir=/path/to/config-dir/ -jar MyApp.jar

ネイティブイメージビルダーに構成ファイルを異なる場所からピックアップさせるには、パスを指定する必要があります。

$ native-image -H:ConfigurationFileDirectories=/path/to/config-dir/ -jar MyApp.jar

詳細は以下のドキュメントをごらんになることを推奨します。

Native Image Build Configuration
https://www.graalvm.org/reference-manual/native-image/BuildConfiguration/

JVM上で実行する場合と同様、実行時にイメージのメモリ利用を管理・制御できます。

$ ./imagename -Xmx<m> -Xmn<m>

デフォルトのヒープ設定をビルド時に構成するには、 -R:MaxHeapSize=<m> -R:MaxNewSize=<m> オプションの組み合わせを使って、最大ヒープサイズとYoung世代のサイズの設定をそれぞれ事前に設定します。

Optimizing the executables

ネイティブイメージは既に十分に最適化されたバイナリですが、GraalVM Enterprise Editionで利用可能なプロファイルガイド付き最適化を使うとさらに最適化できます。

$ native-image - pgo-instrument MyApp
$ ./myapp
$ native-image - pgo profile.iprof MyApp

実行時にJavaヒープを管理するために、Native Imageは異なるGC実装を提供します:デフォルトはシリアルGCですが、アダプティブかつGCの一時停止時間が長くなりにくいG1 GCが利用できます。G1 GCは --gc=G1 オプションをつけることで有効化できます。

Debugging

ネイティブイメージのビルドプロセスで問題に遭遇した場合、設定の間違い、クラスの初期化戦略の誤解、より動的な言語機能のいくつかをサポートする際の問題など、いくつかの原因が考えられます。ほとんどの場合、あなたが使用している依存関係や、あなたのコードとライブラリのコードの間で発生します。もちろん、native-imageの問題である可能性もあります。

いずれにしても、コンソールに表示されるエラーメッセージは通常、何をすべきかを示唆しています。--verboseオプションで冗長な出力を有効にしても何の役に立つ情報が出てこない場合、いくつかのオプションが犯人を特定するのに役立つ可能性があります。

--trace-class-initializationどのクラスやクラスオブジェクトが初期化されたかをトレースする
-H:+TraceClassInitialization=package.class.Name特定のクラスの初期化パスをトレースする
-H:+PrintClassInitialization静的解析で検出された初期化されたクラスを出力する
./imagename -XX:+PrintGC -XX:+VerboseGCGCログを出力する
-ggdbでデバッグできるようにデバッグシンボルをつけてネイティブイメージをビルド

何か他で失敗した場合は、GitHub リポジトリにIssueを立てるか、GraalVM コミュニティの slack に連絡してください。

Native Imageに関連するオープンなIssue
https://github.com/oracle/graal/issues?q=is%3Aissue+is%3Aopen+label%3Anative-image
GraalVM CommunityのSlack招待ページ
https://www.graalvm.org/slack-invitation

ネイティブ実行形式ファイルのサイズが大きすぎると思われる場合や、どのパッケージやクラス、メソッドが含まれているかが気になる場合、イメージビルド時に診断データをダンプファイルに集めて、後でGraalVMダッシュボードツールを使って可視化してください。

GraalVM Dashboard
https://www.graalvm.org/docs/tools/dashboard/

$ native-image -H:+DashboardAll -H:DashboardDump=<path> -jar MyApp.jar
Image for post
Code Size Breakdown window in GraalVM Dashboard

Conclusion

このクイックリファレンスで、GraalVMのnative-imageユーティリティで最もよく使われるオプションを概説してきました。

是非クイックリファレンスをダウンロードして印刷ください。このクイックリファレンスが、Native Imageテクノロジーのよく使われ、最も有用なオプションを思い出させるのであればうれしく思います。

GraalVM Native Image Quick Reference
https://www.graalvm.org/uploads/quick-references/native-image-quick-reference-v2_A4.pdf

GraalVMを入手して、コードのネイティブイメージをビルドしてみてください。コンテナや小さなクラウドのインスタンスのような制約のある環境で実行する場合、起動時間やメモリフットプリントの少なさが本当に大きな違いになります。

GraalVM Downloads
https://www.graalvm.org/downloads/

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト /  変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト /  変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト /  変更 )

%s と連携中