CLOVER🍀

That was when it all began.

環境変数JAVA_TOOL_OPTIONSで、Java VM引数を指定する(-XX:VMOptionsFile)

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

以前、OpenJDKのイメージで遊んでいた時に、JAVA_TOOL_OPTIONSという環境変数が使われているのを見て。

$ docker container run -it --rm adoptopenjdk/openjdk11:latest java -version
Picked up JAVA_TOOL_OPTIONS: -XX:+UseContainerSupport
openjdk version "11.0.2" 2019-01-15
OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.2+9)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.2+9, mixed mode)

これ、なんだろうと思っていたのですが、ドキュメントに記載があったようです。

JAVA_TOOL_OPTIONS環境変数

Java VMにオプションを渡せる環境変数のようですね。知りませんでした。

せっかくなので、ちゃんと見てみようかな、と。

まあ、結論を書いておくと。
いろいろ試してみたんですが、指定方法はいろいろあれど、あれこれ組み合わせて使うものじゃないですね、というのが感想です。

JAVA_OPTS

Java VMにオプションを渡す環境変数といえば、JAVA_OPTSが有名だと思います。

ですが、これはJava VMがサポートしているわけではなく、これが使えるソフトウェアが多い、という話です。

アプリケーションサーバーの起動スクリプトなどに記述されています。

Tomcat。

https://github.com/apache/tomcat/blob/9.0.39/bin/catalina.sh

WildFly。

https://github.com/wildfly/wildfly/blob/21.0.0.Final/ee-feature-pack/common/src/main/resources/content/bin/appclient.conf

https://github.com/wildfly/wildfly/blob/21.0.0.Final/ee-feature-pack/common/src/main/resources/content/bin/appclient.sh

結局のところ、javaコマンドの引数として指定される形に展開されます。Java VMへの引数を環境変数で指定したいケースで、
かつ環境変数JAVA_OPTSが使えるなら、これを使えばいいのではないかな、という気がします。

この後を読むとわかりますが、JAVA_TOOL_OPTIONSとぶつかってもJAVA_OPTSの内容が優先されることになるので。

JAVA_TOOL_OPTIONS

JAVA_TOOL_OPTIONSは、Javaのドキュメントに記載のある環境変数です。

環境変数とシステム・プロパティ

JAVA_TOOL_OPTIONS環境変数

この環境変数では、ツールの初期化(具体的には-agentlibまたは-javaagentオプションを使用したネイティブまたはJavaプログラミング言語エージェントの起動)を指定できます。

この変数は、診断の目的でコマンド行をほかのオプションを使って拡張するためにも使用できます。たとえば、-XX:OnErrorオプションを使用すると、致命的エラーが発生した場合に実行されるスクリプトまたはコマンドを指定できます。

この環境変数はJNI_CreateJavaVM関数の呼出し時に検査されるため、通常はランチャによって処理されるオプションを使用したコマンド行の拡張(-clientまたは-serverオプションを使ったVM選択など)には使用できません。

なるほど、ちょっと確認してみます。

環境

今回の環境は、こちらです。

$ java --version
openjdk 11.0.8 2020-07-14
OpenJDK Runtime Environment (build 11.0.8+10-post-Ubuntu-0ubuntu120.04)
OpenJDK 64-Bit Server VM (build 11.0.8+10-post-Ubuntu-0ubuntu120.04, mixed mode, sharing)

サンプルプログラム

動作確認用の、サンプルプログラムはこちら。
App.java

public class App {
    public static void main(String... args) {
        System.out.printf("my.prop1 = %s %n", System.getProperty("my.prop1"));
        System.out.printf("my.prop2 = %s %n", System.getProperty("my.prop2"));
    }
}

システムプロパティに値を指定して、動作確認。

$ java -Dmy.prop1=value1-from-cli -Dmy.prop2=value2-from-cli App.java
my.prop1 = value1-from-cli 
my.prop2 = value2-from-cli

このシステムプロパティの指定方法に、環境変数JAVA_TOOL_OPTIONSを使ってみましょう。

環境変数JAVA_TOOL_OPTIONSを使う

まずは、環境変数JAVA_TOOL_OPTIONSを指定します。

$ export JAVA_TOOL_OPTIONS='-Dmy.prop1=value1-from-env-tool-options -Dmy.prop2=value2-from-env-tool-options'

確認。

$ java App.java
Picked up JAVA_TOOL_OPTIONS: -Dmy.prop1=value1-from-env-tool-options -Dmy.prop2=value2-from-env-tool-options
my.prop1 = value1-from-env-tool-options 
my.prop2 = value2-from-env-tool-options 

JAVA_TOOL_OPTIONSが使用されたことが表示されました。

このあたりっぽいですね。

https://github.com/openjdk/jdk/blob/jdk-11%2B8/src/hotspot/share/runtime/arguments.cpp#L3625-L3626

コマンドでの引数指定と、同時に行うとどうなるんでしょう?

$ java -Dmy.prop1=value1-from-cli -Dmy.prop2=value2-from-cli App.java
Picked up JAVA_TOOL_OPTIONS: -Dmy.prop1=value1-from-env-tool-options -Dmy.prop2=value2-from-env-tool-options
my.prop1 = value1-from-cli 
my.prop2 = value2-from-cli 

コマンドでの引数で指定した方が優先されるようです。

少し、ひねくれた(?)指定をしてみましょう。環境変数で、片方のシステムプロパティを指定。

$ export JAVA_TOOL_OPTIONS=-Dmy.prop1=value1-from-env-tool-options

コマンドでの指定では、もう片方を指定。

$ java -Dmy.prop2=value2-from-cli App.java
Picked up JAVA_TOOL_OPTIONS: -Dmy.prop1=value1-from-env-tool-options
my.prop1 = value1-from-env-tool-options 
my.prop2 = value2-from-cli 

環境変数の内容を、コマンドでの引数指定で上書き、という挙動みたいですね。なので、コマンドでの指定と競合しなかったものは
残ります、と。

VM.javaを見てみる

ところで、環境変数JAVA_TOOL_OPTIONSのことが書かれたソースコードコメントを見ると、もう少しなにか書いてあります。

    /**
     * Returns the VM arguments for this runtime environment.
     *
     * @implNote
     * The HotSpot JVM processes the input arguments from multiple sources
     * in the following order:
     * 1. JAVA_TOOL_OPTIONS environment variable
     * 2. Options from JNI Invocation API
     * 3. _JAVA_OPTIONS environment variable
     *
     * If VM options file is specified via -XX:VMOptionsFile, the vm options
     * file is read and expanded in place of -XX:VMOptionFile option.
     */
    public static native String[] getRuntimeArguments();

https://github.com/openjdk/jdk/blob/jdk-11%2B8/src/java.base/share/classes/jdk/internal/misc/VM.java#L400-L409

https://github.com/openjdk/jdk/blob/jdk-11%2B8/src/hotspot/share/runtime/arguments.cpp#L3603-L3609

_JAVA_OPTIONSという環境変数と、-XX:VMOptionFileというオプションがありそうです。

こちらも見てみますか。

環境変数_JAVA_OPTIONS

環境変数_JAVA_OPTIONSは、ドキュメントに記載がないオプションですね。

とりあえず、指定してみましょう。

$ export _JAVA_OPTIONS='-Dmy.prop1=value1-from-env-options -Dmy.prop2=value2-from-env-options'

確認。

$ java App.java
Picked up _JAVA_OPTIONS: -Dmy.prop1=value1-from-env-options -Dmy.prop2=value2-from-env-options
my.prop1 = value1-from-env-options 
my.prop2 = value2-from-env-options

確かに、反映されました。

この状態で、環境変数JAVA_TOOL_OPTIONSも指定してみます。

$ export JAVA_TOOL_OPTIONS='-Dmy.prop1=value1-from-env-tool-options -Dmy.prop2=value2-from-env-tool-options'

確認。

$ java App.java
Picked up JAVA_TOOL_OPTIONS: -Dmy.prop1=value1-from-env-tool-options -Dmy.prop2=value2-from-env-tool-options
Picked up _JAVA_OPTIONS: -Dmy.prop1=value1-from-env-options -Dmy.prop2=value2-from-env-options
my.prop1 = value1-from-env-options 
my.prop2 = value2-from-env-options

_JAVA_OPTIONSが後勝ちになるようです。

では、コマンド引数から指定すると…

$ java -Dmy.prop1=value1-from-cli -Dmy.prop2=value2-from-cli App.java
Picked up JAVA_TOOL_OPTIONS: -Dmy.prop1=value1-from-env-tool-options -Dmy.prop2=value2-from-env-tool-options
Picked up _JAVA_OPTIONS: -Dmy.prop1=value1-from-env-options -Dmy.prop2=value2-from-env-options
my.prop1 = value1-from-env-options 
my.prop2 = value2-from-env-options 

上書きされ…ない。

ちょっと1度、リセット。

$ unset _JAVA_OPTIONS
$ unset JAVA_TOOL_OPTIONS

_JAVA_OPTIONSだけの指定にしてみます。

$ export _JAVA_OPTIONS='-Dmy.prop1=value1-from-env-options -Dmy.prop2=value2-from-env-options'

やっぱり変わらないようです。

$ java -Dmy.prop1=value1-from-cli -Dmy.prop2=value2-from-cli App.java
Picked up _JAVA_OPTIONS: -Dmy.prop1=value1-from-env-options -Dmy.prop2=value2-from-env-options
my.prop1 = value1-from-env-options 
my.prop2 = value2-from-env-options

Java VMへ直接引数を指定した場合って、

Options from JNI Invocation API

に該当するんでしょうかね…?

混ぜてみます。

$ export _JAVA_OPTIONS=-Dmy.prop1=value1-from-env-options
$ export JAVA_TOOL_OPTIONS=-Dmy.prop2=value2-from-env-tool-options


$ java App.java
Picked up JAVA_TOOL_OPTIONS: -Dmy.prop2=value2-from-env-tool-options
Picked up _JAVA_OPTIONS: -Dmy.prop1=value1-from-env-options
my.prop1 = value1-from-env-options 
my.prop2 = value2-from-env-tool-options 


$ java -Dmy.prop1=value1-from-cli -Dmy.prop2=value2-from-cli App.java
Picked up JAVA_TOOL_OPTIONS: -Dmy.prop2=value2-from-env-tool-options
Picked up _JAVA_OPTIONS: -Dmy.prop1=value1-from-env-options
my.prop1 = value1-from-env-options 
my.prop2 = value2-from-cli 

面白いですね。

まあ、ドキュメントに書かれているのはJAVA_TOOL_OPTIONSですし、使うならこちらでしょうか。

-XX:VMOptionsFile

最後、-XX:VMOptionsFileというオプションについて。Java 9で増えたんでしょうかね?

javaの拡張ランタイム・オプション

-XX:VMOptionsFile=filename

ユーザーがVMオプションをファイルに指定できるようにします。

対応してそうなチケットもありました。

[JDK-8061999] Enhance VM option parsing to allow options to be specified in a file - Java Bug System

[JDK-8141358] Document -XX:VMOptionsFile - Java Bug System

こちらも試してみましょう。

あんまり情報がないので、このあたりを参考に。

https://github.com/openjdk/jdk/blob/jdk-11%2B8/src/hotspot/share/runtime/arguments.cpp#L3573-L3601

https://github.com/openjdk/jdk/tree/jdk-11+8/test/hotspot/jtreg/runtime/CommandLine/VMOptionsFile

こんなファイルを作成。
options.conf

-Dmy.prop1=value1-from-option-file
-Dmy.prop2=value2-from-option-file

実行。

$ java -XX:VMOptionsFile=options.conf App.java
my.prop1 = value1-from-option-file 
my.prop2 = value2-from-option-file 

認識しました。

コマンドラインから同時に指定した場合は、オプションの位置に影響を受けるようです。

$ java -XX:VMOptionsFile=options.conf -Dmy.prop1=value1-from-cli -Dmy.prop2=value2-from-cli App.java
my.prop1 = value1-from-cli 
my.prop2 = value2-from-cli


$ java -Dmy.prop1=value1-from-cli -Dmy.prop2=value2-from-cli -XX:VMOptionsFile=options.conf App.java
my.prop1 = value1-from-option-file 
my.prop2 = value2-from-option-file 

環境変数を使うパターンも試してみましょう。

環境変数JAVA_TOOL_OPTIONSを指定。

$ export JAVA_TOOL_OPTIONS='-Dmy.prop1=value1-from-env-tool-options -Dmy.prop2=value2-from-env-tool-options'

これは、-XX:VMOptionsFileの方が優先ですね。

$ java -XX:VMOptionsFile=options.conf App.java
Picked up JAVA_TOOL_OPTIONS: -Dmy.prop1=value1-from-env-tool-options -Dmy.prop2=value2-from-env-tool-options
my.prop1 = value1-from-option-file 
my.prop2 = value2-from-option-file 

環境変数_JAVA_OPTIONSを使う場合。

$ export _JAVA_OPTIONS='-Dmy.prop1=value1-from-env-options -Dmy.prop2=value2-from-env-options'

$ java -XX:VMOptionsFile=options.conf App.java
Picked up _JAVA_OPTIONS: -Dmy.prop1=value1-from-env-options -Dmy.prop2=value2-from-env-options
my.prop1 = value1-from-env-options 
my.prop2 = value2-from-env-options 

あらまあ。_JAVA_OPTIONSの方が優先みたいですね。

ということは、優先度的にはコマンドラインでの引数指定と同じようですね。

部分的な定義にして混ぜてみましょう。
options-partial.conf

-Dmy.prop2=value2-from-option-file

確認。

$ java -XX:VMOptionsFile=options-partial.conf -Dmy.prop1=value1-from-cli App.java
my.prop1 = value1-from-cli 
my.prop2 = value2-from-option-file 

併用は可、ですと。

環境変数も加えてみます。

$ export JAVA_TOOL_OPTIONS=-Dmy.prop1=value1-from-env-tool-options

$ java -XX:VMOptionsFile=options-partial.conf App.java
Picked up JAVA_TOOL_OPTIONS: -Dmy.prop1=value1-from-env-tool-options
my.prop1 = value1-from-env-tool-options 
my.prop2 = value2-from-option-file 

それぞれ使えそうですね。

_JAVA_OPTIONSも。

$ export _JAVA_OPTIONS=-Dmy.prop1=value1-from-env-options

$ java -XX:VMOptionsFile=options-partial.conf App.java
Picked up _JAVA_OPTIONS: -Dmy.prop1=value1-from-env-options
my.prop1 = value1-from-env-options 
my.prop2 = value2-from-option-file

まとめ

Java VMに、引数を指定する方法をいろいろ試してみました。

環境変数、ファイル指定、コマンドに直接指定等々。

いろいろ試してみて思いましたが、設定の方法はいくつもあれど、実際に指定する時にはどれか少なく絞りたいですね。

どれが優先されるとか覚えてられないですし、この先、挙動が変わるかわからないですし…。