このエントリは以下のエントリをベースにしています。
The original entry was written by Paul Wögerer (Researcher at Oracle Labs). Working on GraalVM native image generation (Substrate VM).
https://medium.com/graalvm/simplifying-native-image-generation-with-maven-plugin-and-embeddable-configuration-d5b283b92f57
このエントリでは2つの機能をご紹介します。 一つは最近GraalVMに追加された、ネイティブイメージの生成を簡単にするMavenプラグインです。ビルド時にネイティブイメージ生成を含めることができるため、コマンドラインユーティリティを手作業で呼び出す必要はありません。もう一つは、JARファイル内のライブラリの構成を手作業でやらずにすむためのnative-image.propertiesを見ていきます。
Building netty-plot with native-image-maven-plugin
以前のエントリでは、GraalVMネイティブイメージにおけるisolate (分離)と compressed reference (圧縮参照) を利用する方法を紹介しました。
Isolates and Compressed References: More Flexible and Efficient Memory Management via GraalVM
https://medium.com/graalvm/isolates-and-compressed-references-more-flexible-and-efficient-memory-management-for-graalvm-a044cc50b67e
https://logico-jp.io/2019/03/27/isolates-and-compressed-references-more-flexible-and-efficient-memory-management-via-graalvm/
GraalVMのnative-imageツールを使ってネイティブイメージにコンパイルされるuber-jar (fat jar) をMavenを使ってビルドしました。
Ahead-of-time Compilation
https://www.graalvm.org/docs/reference-manual/aot-compilation/
最近のGraalVMリリースでは、ネイティブイメージをMavenでビルドできるようになっています。そのため、uber-jarをビルドした後に別工程でnative-imageツールを実行する必要はありません。
GraalVM Downloads
https://www.graalvm.org/downloads/
このエントリでは、Mavenを使って簡単にネイティブイメージをビルドする方法をご紹介します。GraalVMを JAVA_HOME として使っている場合、以下のようにpom.xmlの pluginsセクションに追加するだけです。
| <plugin> | |
| <groupId>com.oracle.substratevm</groupId> | |
| <artifactId>native-image-maven-plugin</artifactId> | |
| <version>1.0.0-rc14</version> | |
| <executions> | |
| <execution> | |
| <goals> | |
| <goal>native-image</goal> | |
| </goals> | |
| <phase>package</phase> | |
| </execution> | |
| </executions> | |
| </plugin> |
以前のnative-netty-plotの例で言えば、Mavenでネイティブイメージを作成するための追記は上記だけで十分です。 mvn package を実行すればネイティブイメージができあがります。
Isolates for GraalVM Native Images
https://github.com/graalvm/graalvm-demos/tree/master/native-netty-plot
| [INFO] --- maven-assembly-plugin:3.1.0:single (assemble-all) @ netty-plot --- | |
| [INFO] Building jar: /home/pwoegere/OLabs/git/graalvm-demos/native-netty-plot/target/netty-plot-0.1-jar-with-dependencies.jar | |
| [INFO] | |
| [INFO] --- native-image-maven-plugin:1.0.0-rc14:native-image (default) @ netty-plot --- | |
| [INFO] ImageClasspath Entry: io.netty:netty-all:jar:4.1.30.Final:compile (file:///home/pwoegere/.m2/repository/io/netty/netty-all/4.1.30.Final/netty-all-4.1.30.Final.jar) | |
| [INFO] ImageClasspath Entry: net.objecthunter:exp4j:jar:0.4.8:compile (file:///home/pwoegere/.m2/repository/net/objecthunter/exp4j/0.4.8/exp4j-0.4.8.jar) | |
| [INFO] ImageClasspath Entry: org.jfree:jfreesvg:jar:3.3:compile (file:///home/pwoegere/.m2/repository/org/jfree/jfreesvg/3.3/jfreesvg-3.3.jar) | |
| [INFO] ImageClasspath Entry: com.oracle.substratevm:netty-plot:jar:0.1 (file:///home/pwoegere/OLabs/git/graalvm-demos/native-netty-plot/target/netty-plot-0.1.jar) | |
| [INFO] Executing: /home/pwoegere/OLabs/graalvm-ce-1.0.0-rc14/bin/native-image -cp /home/pwoegere/.m2/repository/io/netty/netty-all/4.1.30.Final/netty-all-4.1.30.Final.jar:/home/pwoegere/.m2/repository/net/objecthunter/exp4j/0.4.8/exp4j-0.4.8.jar:/home/pwoegere/.m2/repository/org/jfree/jfreesvg/3.3/jfreesvg-3.3.jar:/home/pwoegere/OLabs/git/graalvm-demos/native-netty-plot/target/netty-plot-0.1.jar -H:Class=com.oracle.svm.nettyplot.PlotServer | |
| Build on Server(pid: 14842, port: 37771) | |
| [netty-plot:14842] classlist: 1,223.63 ms | |
| [netty-plot:14842] (cap): 720.78 ms | |
| [netty-plot:14842] setup: 1,545.91 ms | |
| [netty-plot:14842] (typeflow): 4,931.65 ms | |
| [netty-plot:14842] (objects): 4,134.22 ms | |
| [netty-plot:14842] (features): 156.66 ms | |
| [netty-plot:14842] analysis: 9,423.44 ms | |
| [netty-plot:14842] universe: 226.76 ms | |
| [netty-plot:14842] (parse): 719.43 ms | |
| [netty-plot:14842] (inline): 1,107.06 ms | |
| [netty-plot:14842] (compile): 5,619.08 ms | |
| [netty-plot:14842] compile: 7,945.79 ms | |
| [netty-plot:14842] image: 896.87 ms | |
| [netty-plot:14842] write: 138.20 ms | |
| [netty-plot:14842] [total]: 21,498.25 ms | |
| [INFO] ------------------------------------------------------------------------ | |
| [INFO] BUILD SUCCESS | |
| [INFO] ------------------------------------------------------------------------ | |
| [INFO] Total time: 26.110 s | |
| [INFO] Finished at: 2019-03-15T13:18:16+01:00 | |
| [INFO] ------------------------------------------------------------------------ |
出力結果を見ると、プラグインがどのJARファイルをnative-imageコマンドに渡す必要があるか、実行可能メインクラスが何であるべきか (com.oracle.svm.nettyplot.PlotServer) を導き出したことがわかります。mvn package が完了したら、実行ファイル netty-plotがmavenプロジェクトのtargetディレクトリにあることがわかります。
Maven統合はほとんどのIDEで利用可能なので、MavenプラグインはつまりIDEを離れずにネイティブイメージを生成できる、ということに他なりません。

Plugin customization
<plugin> ノードの<configuration>を使い、イメージ作成のカスタマイズが可能です。現在以下のオプションが利用できます。
<mainClass>
プラグインは、イメージのmainクラスを判断するため、以下の順番でpom.xmlのいくつかの場所をデフォルトで探索します。
<maven-shade-plugin> <transformers> <transformer> <mainClass><maven-assembly-plugin> <archive> <manifest> <mainClass><maven-jar-plugin> <archive> <manifest> <mainClass>
上記の場所のいずれにおいても適切なmainクラスの定義が見つからない場合、プラグインの<configuration>ノード中に<mainClass>を追加して、カスタムのmainクラスを指定できます。
<imageName>
イメージのファイル名が明示的に設定されていない場合、native-imageツールが選択します(例えば上記のnetty-plotの例の場合、イメージのファイル名はnetty-plotです)。イメージファイル名を指定したい場合は、<imageName>パラメータを使う必要があります。
<buildArgs>
イメージ生成のためにnative-imageコマンドに追加オプションを渡したい場合、<buildArgs>パラメータを利用できます。
Example configuration
mainクラスとしてcom.acme.MeepMeepを使うアサーションを有効にしたイメージをビルドしたい場合、pom.xmlのnative-image-maven-pluginの定義に以下のように追記してください。
| <configuration> | |
| <mainClass>com.acme.MeepMeep</mainClass> | |
| <buildArgs>-ea</buildArgs> | |
| </configuration> |
Using maven-plugin with GraalVM Enterprise Edition:
JAVA_HOMEとしてGraalVM Enterprise Editionを使う場合、プラグインはエンタープライズ向け機能を有効してイメージを生成します。具体的には、 compressed referencesやその他の最適化が有効になった形で実行ファイルを自動的に生成します。
Oracle Labs GraalVM Download
https://www.oracle.com/technetwork/oracle-labs/program-languages/downloads/index.html
Isolates and Compressed References: More Flexible and Efficient Memory Management via GraalVM
https://medium.com/graalvm/isolates-and-compressed-references-more-flexible-and-efficient-memory-management-for-graalvm-a044cc50b67e
Using maven-plugin without GraalVM:
このプラグインを使うと、GraalVMを手元のシステムにインストールしなくてもイメージを作成できます。唯一の要求事項は、マシン上のJDKがJVMCI対応していることです。現時点では、イメージ生成はJava 8のみサポートしている(Java 11でもサポートするよう鋭意作業中です)ため、JVMCI対応のOpenJDK 8をダウンロードする必要があります。
jvmci-0.56
https://github.com/graalvm/openjdk8-jvmci-builder/releases
JVMCI対応のJDKをダウンロードし、JAVA_HOMEに設定したら、Mavenプロジェクトで上記のnative-image-maven-pluginのスニペットを利用でき、イメージ作成が機能します。動作する理由は、プラグインにより、Mavenがイメージ生成に必要なすべてのコンポーネントを直接Maven Centralからダウンロードするからです。この方式では、ほとんどの場合、新しいバージョンへの更新は、単に定義内のタグを変更することです。例えば、1.0-RC15がリリースされた場合には、プラグイン定義を次のように更新するだけで、それ以外の手順は不要です。Mavenが残りをよろしくやってくれます。
| <plugin> | |
| <groupId>com.oracle.substratevm</groupId> | |
| <artifactId>native-image-maven-plugin</artifactId> | |
| <version>1.0.0-rc15</version> | |
| ... | |
| </plugin> |
⚠ このシナリオで、 --language:や --tool:のようなマクロオプションの利用はできませんのでご注意ください。マクロオプションを使う場合、GraalVMそのものをJAVA_HOMEとして設定し、使用する必要があります。また、アップデートされたGraalVMにJVMCI対応JDKが依存する変更が入っている場合、JVMCI対応のJDKもアップデートする必要があります。つまり利用者が対応する必要がありますよ、ということです。
Composable native-image.properties
GraalVMの最近のリリース以降、ネイティブイメージのビルドについて別の重要な改善をしています。JARファイル中のMETA-INF/native-imageにnative-image.propertiesファイルを埋め込むことができるようになりました。native-imageツールは、このリソースの場所にあるすべてのファイルを自動的に処理し、それらを使用してnative-imageのコマンドライン引数を作成します。以前のブログエントリのnative-netty-plotの例では、すでにこの機能を利用しています。
Isolates for GraalVM Native Images
https://github.com/graalvm/graalvm-demos/tree/master/native-netty-plot
src/main/resources/META-INF/native-image/com.oracle.substratevm/netty-plotにnative-image.propertiesファイルを配置すれば、イメージの構築時に追加の引数は不要です。以下のコマンドを実行するだけで、イメージを作成できます。
native-image -jar target/netty-plot-0.1-jar-with-dependencies.jar
以前は、Nettyを含むイメージを作成する場合、常に以下のような感じで追加コマンドラインオプションが必要でした。
-H:ReflectionConfigurationResources=reflection-config.json --delay-class-initialization-to-runtime=io.netty.handler.codec.http.HttpObjectEncoder
動作するようになった理由は、こうした追加フラグをJARファイル自体に埋め込むことができるようになったからです。native-image.propertiesファイルを調べると、netty-plot-0.1-jar-with-dependencies.jarがイメージのクラスパスにあるときに、追加のフラグがnative-imageに渡されることがわかります。
| ImageName = netty-plot | |
| Args = --features=com.oracle.svm.nettyplot.PlotterSingletonFeature \ | |
| -H:ReflectionConfigurationResources=${.}/reflection-config.json \ | |
| --delay-class-initialization-to-runtime=io.netty.handler.codec.http.HttpObjectEncoder \ | |
| -H:+SpawnIsolates |
ここでもまた、Nettyを含むイメージを作成するために必要なよく知られたオプションを見つけることができます。上記のファイルでは${.}が使用されていますが、これはnative-image.propertiesファイルの便利な機能で、これを使うと、native-image.propertiesファイルが存在する場所を参照できます。この方法で、native-image.propertiesファイルの場所を基準にしてリソースの場所を簡単に指定できます。上記の場合、${.}をMETA-INF/native-image/com.oracle.substratevm/netty-plotに展開します。
Making Java libraries native-image compatible by default
正しく適用すれば、Javaライブラリの作者は上述のメカニズムを使ってライブラリにnative-image互換性を持たせることができます。Nettyの例では、com.oracle.svm.nettyplot.NettySubstitutionsからの置換に加え、以下を含むアーティファクトio.netty:netty-allがあれば、
META-INF/native-image/io.netty/netty-all/reflection-config.jsonMETA-INF/native-image/io.netty/netty-all/native-image.properties
Nettyはそのままnative-imageとともに利用できます。netty-plotの例のもう一つのnative-image.propertiesには、netty-plot自体が必要とするオプションのみが含まれています。
| ImageName = netty-plot | |
| Args = --features=com.oracle.svm.nettyplot.PlotterSingletonFeature -H:+SpawnIsolates |
これに加え、pom.xmlから依存関係com.oracle.substratevm:svmを取り除くことができるようになるでしょう。これはNettySubstitutionsが依存しているために現時点のみ必要としているからです。
| <dependency> | |
| <groupId>com.oracle.substratevm</groupId> | |
| <artifactId>svm</artifactId> | |
| <version>1.0.0-rc14</version> | |
| <scope>provided</scope> | |
| </dependency> |
💡 About provided
上記で使用したprovidedが重要なのは、イメージビルダー自体がcom.oracle.substratevm:svmを提供するイメージ構築中にのみ置換を必要とするためです。このスコープ定義を省略すると、Mavenは、uber-jarを作成するときにcom.oracle.substratevm:svmとcom.oracle.substratevm:svmのすべての推移的な依存関係をjarファイルに入れてしまいます。それは絶対に避けたいのです!
com.oracle.substratevm:svm:
https://search.maven.org/artifact/com.oracle.substratevm/svm/1.0.0-rc14/jar
io.netty:netty-allに native-images で利用する上で必要なすべての具体的な依存関係が含まれている場合、netty-plotへの上記の依存関係は以下のように簡単にできるでしょう。その理由は、netty-plot自体は公式のAPI (org.graalvm.nativeimage API) にのみ依存しているためです。
| <dependency> | |
| <groupId>org.graalvm.sdk</groupId> | |
| <artifactId>org.graalvm.sdk</artifactId> | |
| <version>1.0.0-rc14</version> | |
| <scope>provided</scope> | |
| </dependency> |
org.graalvm.nativeimage
https://www.graalvm.org/sdk/javadoc/org/graalvm/nativeimage/package-summary.html
Conclusion
このエントリはGraalVMネイティブイメージMavenプラグインを使って皆様のプロジェクトから簡単にネイティブイメージを作成できることをご紹介しました。また、JARファイルのMETA-INF/native-imageディレクトリに必要なネイティブイメージの細々とした設定をライブラリが埋め込む方法も調べました。
これらの2個の機能により、エコシステムがGraalVMのネイティブイメージやAhead of Time Compilation(事前コンパイル、AOTコンパイル)を活用できるようになります。GraalVMをダウンロードして試してみてください。
GraalVM Downloads
https://www.graalvm.org/downloads
見逃している良い機能を見たいとか、あるいは他に何かフィードバックすることがありましたら、ご連絡ください。GraalVMのGitHubリポジトリにIssueを残すとか、その他の方法でご連絡ください。
GraalVM GitHub Repository
https://github.com/oracle/graal
GraalVM Community
http://www.graalvm.org/community/