CLOVER🍀

That was when it all began.

GraalVMでネイティブイメージを作った時の、コマンドライン引数の扱いを確認する

これは、なにをしたくて書いたもの?

こちらの資料を見ていて、「あれ?ネイティブイメージでのヒープサイズってどうやって指定するんだろう?」と思いまして。

Quarkus入門 - 見習いプログラミング日記

SubstrateVMのヒープ設定 / Quarkus入門

GraalVM(SubstrateVM)を使ったネイティブイメージにした時って、実行ファイルに引数を指定するしかなくなるので、
javaコマンドの時に渡していた「-X」なオプションやシステムプロパティってどうなるんだろうという疑問がありました。

今回は、そのあたりを調べてみようかと。

環境

今回の環境は、こちら。

$ java -version
openjdk version "1.8.0_212"
OpenJDK Runtime Environment (build 1.8.0_212-20190420092731.buildslave.jdk8u-src-tar--b03)
OpenJDK GraalVM CE 19.0.0 (build 25.212-b03-jvmci-19-b01, mixed mode)


$ native-image --version
GraalVM Version 19.0.0 CE

サンプルプログラム

先に、簡単なプログラムを用意してみます。

アプリケーションの起動引数を出力するだけの、簡単なアプリケーションです。
PrintArgs.java

public class PrintArgs {
    public static void main(String... args) {
        System.out.println("Print program args:");

        for (String arg : args) {
            System.out.printf("  %s%n", arg);
        }
    }
}

こちらをコンパイルして

$ javac PrintArgs.java

実行。「-Xmx」とか入っていて引数の内容がやや作為的ですが、そのまま出力されます。

$ java PrintArgs -Xmx512M -Xms512M foo bar
Print program args:
  -Xmx512M
  -Xms512M
  foo
  bar

では、ネイティブイメージにビルドしてみます。

$ native-image PrintArgs print-args

javaコマンドの時と同じ引数を指定して実行してみると、なんと「-Xmx」などがなくなりました。

$ ./print-args -Xmx512M -Xms512M foo bar
Print program args:
  foo
  bar

試しに、他の場所に入れてもやっぱりなくなります。

$ ./print-args hoge foo bar -Xmx512M -Xms512M test
Print program args:
  hoge
  foo
  bar
  test

この点を、ちょっと追ってみましょう。

GraalVMのソースコードから、コマンドライン引数解析処理を見る

最初に参照した資料では、ヒープサイズの指定については「HeapPolicy.java」を見るとよいという記載がありました。

確かに、最大ヒープサイズあたりの記述があります。

https://github.com/oracle/graal/blob/vm-19.0.0/substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/HeapPolicy.java#L227-L232

ここで、このヒープの最大サイズ(-Xmx)などを扱っているXOptionsというクラスを見てみましょう。

graal/XOptions.java at vm-19.0.0 · oracle/graal · GitHub

どうやら、以下の4つを認識するようです。
※正確には、前方一致みたいですが(epoch、value)

  • -Xmn
  • -Xmx
  • -Xms
  • -Xss

https://github.com/oracle/graal/blob/vm-19.0.0/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/XOptions.java#L128-L134

これらをパースする処理を呼び出すのはどこだろう?と見てみると、このあたりみたいですね。

https://github.com/oracle/graal/blob/vm-19.0.0/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/RuntimeOptionParser.java#L76-L79

さらにその呼び出し元。

https://github.com/oracle/graal/blob/vm-19.0.0/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/JavaMainWrapper.java#L134

RuntimeOptionParser#parseAndConsumeAllOptionsの中身を見ると、XOptionsの他にも3つほど引数のパース処理があります。

見てみると、「-XX:」と「-Dgraal.」で始まるオプション、

https://github.com/oracle/graal/blob/vm-19.0.0/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/RuntimeOptionParser.java#L76-L77

https://github.com/oracle/graal/blob/vm-19.0.0/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/RuntimeOptionParser.java#L58-L66

※「-XX:」はフラグ(+または-)での指定、「-Dgraal.」はname=valueの形式になるようです

https://github.com/oracle/graal/blob/vm-19.0.0/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/SubstrateOptionsParser.java#L121-L122

そして、「-D<key>=<value>」なオプション(いわゆるシステムプロパティ)を対象としているようです。

https://github.com/oracle/graal/blob/vm-19.0.0/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/RuntimeOptionParser.java#L79

https://github.com/oracle/graal/blob/vm-19.0.0/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/properties/RuntimePropertyParser.java#L35

なるほど、このあたりはGraalVM(SubstrateVM)で面倒を見ているんですねぇ。

確認してみる

では、ちょっと試してみましょう。いくつかオプションをつけてみて…

$ ./print-args -Xmx512M foo -Dkey=value bar -XX:+AggressiveOpts
error: Could not find option 'AggressiveOpts'. Use -XX:PrintFlags= to list all available options.

コケてしまいました。「-XX:」で指定できるオプションには、制限がありそうです。

https://github.com/oracle/graal/blob/vm-19.0.0/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/option/SubstrateOptionsParser.java#L184

エラーメッセージ通り、「-XX:PrintFlags=」をつけてみると、指定可能な「-XX:」なオプションが表示されます。

$ ./print-args -XX:PrintFlags=
  -XX:±EagerSnippets                           Eagerly construct extra snippet info. Default: - (disabled).
  -XX:±InstallSegfaultHandler                  Install segfault handler that prints register contents and full Java stacktrace. Default: enabled for an executable,
                                               disabled for a shared library. 
  -XX:MaximumHeapSizePercent=80                The maximum heap size as percent of physical memory.
  -XX:MaximumYoungGenerationSizePercent=10     The maximum size of the young generation as a percent of the maximum heap size.
  -XX:PercentTimeInIncrementalCollection=50    Percentage of time that should be spent in young generation collections.
  -XX:PrintFlags=...                           Show available options based on comma-separated option-types (allowed categories: User, Expert, Debug). Default: None
  -XX:±PrintGC                                 Print summary GC information after each collection. Default: - (disabled).
  -XX:±PrintGCSummary                          Print summary GC information after main completion. Default: - (disabled).
  -XX:±PrintGCTimeStamps                       Print a time stamp at each collection, if +PrintGC or +VerboseGC. Default: - (disabled).
  -XX:±PrintGCTimes                            Print the time for each of the phases of each collection, if +VerboseGC. Default: - (disabled).
  -XX:±PrintHeapShape                          Print the shape of the heap before and after each collection, if +VerboseGC. Default: - (disabled).
  -XX:±SafepointPromptnessFailureIsFatal       Are safepoint promptness failures fatal?. Default: + (enabled).
  -XX:SafepointPromptnessFailureNanos=0        Exit the VM if I can not come to a safepoint in this many nanoseconds. 0 implies forever.
  -XX:SafepointPromptnessWarningNanos=0        Print a warning if I can not come to a safepoint in this many nanoseconds. 0 implies forever.
  -XX:TearDownFailureNanos=0                   The number of nanoseconds before tearing down an isolate gives a failure message.  0 implies no message.
  -XX:TearDownWarningNanos=0                   The number of nanoseconds before and between which tearing down an isolate gives a warning message.  0 implies no
                                               warning.
  -XX:±TraceHeapChunks                         Trace heap chunks during collections, if +VerboseGC and +PrintHeapShape. Default: - (disabled).
  -XX:±TraceVMOperations                       Trace VMOperation execution. Default: - (disabled).
  -XX:±VerboseGC                               Print more information about the heap before and after each collection. Default: - (disabled).

では、ちょっと変えてみましょう。

$ ./print-args -Xmx512M foo -Dkey=value bar -XX:+PrintGC
Print program args:
  foo
  bar

今度はOKでした。

ところで、「-Dgraal.」の場合はなにを指定するんでしょう?

これは、「-Dgraal.PrintFlags=」を指定することで確認できます。

$ ./print-args -Dgraal.PrintFlags=
  -Dgraal.±EagerSnippets                       Eagerly construct extra snippet info. Default: - (disabled).
  -Dgraal.±InstallSegfaultHandler              Install segfault handler that prints register contents and full Java stacktrace. Default: enabled for an executable,
                                               disabled for a shared library. 
  -Dgraal.MaximumHeapSizePercent=80            The maximum heap size as percent of physical memory.
  -Dgraal.MaximumYoungGenerationSizePercent=10 The maximum size of the young generation as a percent of the maximum heap size.
  -Dgraal.PercentTimeInIncrementalCollection=50
                                               Percentage of time that should be spent in young generation collections.
  -Dgraal.PrintFlags=...                       Show available options based on comma-separated option-types (allowed categories: User, Expert, Debug). Default: None
  -Dgraal.±PrintGC                             Print summary GC information after each collection. Default: - (disabled).
  -Dgraal.±PrintGCSummary                      Print summary GC information after main completion. Default: - (disabled).
  -Dgraal.±PrintGCTimeStamps                   Print a time stamp at each collection, if +PrintGC or +VerboseGC. Default: - (disabled).
  -Dgraal.±PrintGCTimes                        Print the time for each of the phases of each collection, if +VerboseGC. Default: - (disabled).
  -Dgraal.±PrintHeapShape                      Print the shape of the heap before and after each collection, if +VerboseGC. Default: - (disabled).
  -Dgraal.±SafepointPromptnessFailureIsFatal   Are safepoint promptness failures fatal?. Default: + (enabled).
  -Dgraal.SafepointPromptnessFailureNanos=0    Exit the VM if I can not come to a safepoint in this many nanoseconds. 0 implies forever.
  -Dgraal.SafepointPromptnessWarningNanos=0    Print a warning if I can not come to a safepoint in this many nanoseconds. 0 implies forever.
  -Dgraal.TearDownFailureNanos=0               The number of nanoseconds before tearing down an isolate gives a failure message.  0 implies no message.
  -Dgraal.TearDownWarningNanos=0               The number of nanoseconds before and between which tearing down an isolate gives a warning message.  0 implies no
                                               warning.
  -Dgraal.±TraceHeapChunks                     Trace heap chunks during collections, if +VerboseGC and +PrintHeapShape. Default: - (disabled).
  -Dgraal.±TraceVMOperations                   Trace VMOperation execution. Default: - (disabled).
  -Dgraal.±VerboseGC                           Print more information about the heap before and after each collection. Default: - (disabled).

どうやって気づいたかというと、適当なシステムプロパティを設定したら怒られたので、それでわかりました…。

$ ./print-args -Dgraal.foo=bar
error: Could not find option 'foo'. Use -Dgraal.PrintFlags= to list all available options.

ちゃんと見ると、いろいろわかるものですねぇ。