CLOVER🍀

That was when it all began.

javaコマンドで使える、JDK_JAVA_OPTIONS環境変数ってなんだ?

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

JibのFAQを見ていて、こんな1文があることに気づきまして。

For Java 9+, often you may want to use JDK_JAVA_OPTIONS instead of JAVA_TOOL_OPTIONS.

環境変数JAVA_TOOL_OPTIONSは知っていて、過去にエントリーも書いたことがあるのですが。

環境変数JAVA_TOOL_OPTIONSで、Java VM引数を指定する(-XX:VMOptionsFileも加えて) - CLOVER🍀

JDK_JAVA_OPTIONSは知りませんでしたね。Java 9以降で使えるようです。

確かにJava 8ではドキュメントに書かれていませんが、

java (8)

Java 9で現れます。

java (9)

JDK_JAVA_OPTIONS環境変数

JDK_JAVA_OPTIONS環境変数が追加されたのは、こちらのようですね。

[JDK-8185446] Release Note: Add a new launcher environment variable JDK_JAVA_OPTIONS - Java Bug System

JAVA_TOOL_OPTIONS環境変数に関するドキュメントには、JDK_JAVA_OPTIONS環境変数に対する記述は特にありません。

トラブルシューティング・ガイド / 付録 / C 環境変数とシステム・プロパティ / JAVA_TOOL_OPTIONS環境変数

javaコマンド

JDK_JAVA_OPTIONS環境変数に関する説明は、こちらと

ノート: JDK_JAVA_OPTIONSランチャ環境変数を使用すると、その内容をjavaランチャの実際のコマンド行に付加できます。 「JDK_JAVA_OPTIONSランチャ環境変数の使用方法」を参照してください。

javaコマンド / 説明

こちらに記載があります。

javaコマンド / JDK_JAVA_OPTIONSランチャ環境変数の使用方法

最初にノートで書かれていたように、javaコマンド向けの環境変数のようです。

説明を見てみましょう。見てみると、コマンドラインで指定した引数の先頭に追加される、ということみたいです。

JDK_JAVA_OPTIONSは、その内容をコマンド行から解析されるオプションの先頭に追加します。 JDK_JAVA_OPTIONS環境変数の内容は、空白文字(isspace()で指定)で区切られた引数のリストです。

空白を含む場合は、引用符で囲みましょう、と。

一重引用符(')または二重引用符(")を使用して、空白文字が含まれる引数を囲むことができます。

また、JDK_JAVA_OPTIONS環境変数を使って指定できるオプションにはルールがあるようです。

JDK_JAVA_OPTIONSの動作の誤用の可能性を緩和するために、メイン・クラスを指定するオプション(-jarなど)またはメイン・クラスを実行せずにjavaランチャを終了させるオプション(-hなど)は環境変数で指定できません。 このようなオプションのいずれかが環境変数に指定されている場合、ランチャはエラー・メッセージを発行して停止します。

ヘルプなどは指定できないようですね。

指定例。

$ export JDK_JAVA_OPTIONS='-g @file1 -Dprop=value @file2 -Dws.prop="white spaces"'
$ java -Xint @file3

これは、以下と等価らしいです。

$ java -g @file1 -Dprop=value @file2 -Dws.prop="white spaces" -Xint @file3

ドキュメントを見るのはこれくらいにして、ちょっと試してみましょう。

環境

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

$ java --version
openjdk 17.0.3 2022-04-19
OpenJDK Runtime Environment (build 17.0.3+7-Ubuntu-0ubuntu0.20.04.1)
OpenJDK 64-Bit Server VM (build 17.0.3+7-Ubuntu-0ubuntu0.20.04.1, mixed mode, sharing)

サンプルプログラム

動作確認には、こちらのプログラムを使うことにします。

App.java

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

JDK_JAVA_OPTIONSとJAVA_TOOL_OPTIONSをちょっと比べる

JDK_JAVA_OPTIONS環境変数自体を追う前に、まずJAVA_TOOL_OPTIONS環境変数と比べてみましょう。

JAVA_TOOL_OPTIONS環境変数を、javacで利用。

$ JAVA_TOOL_OPTIONS=-verbose javac App.java
Picked up JAVA_TOOL_OPTIONS: -verbose
[0.002s][info][class,load] opened: /usr/share/java/java-atk-wrapper.jar
[0.023s][info][class,load] java.lang.Object source: shared objects file
[0.024s][info][class,load] java.io.Serializable source: shared objects file
[0.024s][info][class,load] java.lang.Comparable source: shared objects file
[0.024s][info][class,load] java.lang.CharSequence source: shared objects file
[0.024s][info][class,load] java.lang.constant.Constable source: shared objects file
[0.024s][info][class,load] java.lang.constant.ConstantDesc source: shared objects file
[0.024s][info][class,load] java.lang.String source: shared objects fil

〜省略〜

使われましたね。

javaコマンド。

$ JAVA_TOOL_OPTIONS='-Xmx1G -Dmy.prop=Hello' java App.java
Picked up JAVA_TOOL_OPTIONS: -Xmx1G -Dmy.prop=Hello
my.prop = Hello

こちらも有効ですね。

では、JDK_JAVA_OPTIONS環境変数で試してみましょう。

javacコマンド。

$ JDK_JAVA_OPTIONS=-verbose javac App.java

こちらには、反応しません。

javaコマンド。

$ JDK_JAVA_OPTIONS='-Xmx1G -Dmy.prop=Hello' java App.java
NOTE: Picked up JDK_JAVA_OPTIONS: -Xmx1G -Dmy.prop=Hello
my.prop = Hello

ドキュメントに書かれているとおり、JDK_JAVA_OPTIONS環境変数は確かにjavaコマンドに効果があるようです。

それでは、JDK_JAVA_OPTIONS環境変数をもうちょっと見てみましょう。

JDK_JAVA_OPTIONS環境変数をもう少し見てみる

次は、JDK_JAVA_OPTIONS環境変数とコマンドでの引数指定と同時に行ってみましょう。

$ JDK_JAVA_OPTIONS='-Xmx1G -Dmy.prop=Hello' java -Dmy.prop=World App.java
NOTE: Picked up JDK_JAVA_OPTIONS: -Xmx1G -Dmy.prop=Hello
my.prop = World

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

また、こんな話もありましたね。

JDK_JAVA_OPTIONSの動作の誤用の可能性を緩和するために、メイン・クラスを指定するオプション(-jarなど)またはメイン・クラスを実行せずにjavaランチャを終了させるオプション(-hなど)は環境変数で指定できません。

確認してみましょう。

-hを入れてみます。

$ JDK_JAVA_OPTIONS='-Xmx1G -Dmy.prop=Hello -h' java App.java
NOTE: Picked up JDK_JAVA_OPTIONS: -Xmx1G -Dmy.prop=Hello -h
Error: Option -h is not allowed in environment variable JDK_JAVA_OPTIONS

エラーになりましたね。確かに指定できないオプションがありそうです。

なにが指定できないオプションなんでしょうね?

エラーメッセージを見てみて

https://github.com/openjdk/jdk17u/blob/jdk-17.0.3+7/src/java.base/share/native/libjli/emessages.h#L51

環境変数を参照しているのは、こちらのようです。

https://github.com/openjdk/jdk17u/blob/jdk-17.0.3+7/src/java.base/windows/native/libjli/cmdtoargs.c#L212-L221

エラーメッセージを使っているのは、こちら。

        if (NULL == argsInFile) {
            if (isTerminalOpt(arg)) {
                if (inEnvVar) {
                    JLI_ReportMessage(ARG_ERROR9, arg, var_name);
                } else {
                    JLI_ReportMessage(ARG_ERROR15, arg);
                }
                exit(1);
            }
            JLI_List_add(args, arg);

https://github.com/openjdk/jdk17u/blob/jdk-17.0.3+7/src/java.base/share/native/libjli/args.c#L535-L544

isTerminalOptという関数を見ればよさそうですね。このあたりが対象のオプションみたいですね。

int isTerminalOpt(char *arg) {
    return JLI_StrCmp(arg, "-jar") == 0 ||
           JLI_StrCmp(arg, "-m") == 0 ||
           JLI_StrCmp(arg, "--module") == 0 ||
           JLI_StrCCmp(arg, "--module=") == 0 ||
           JLI_StrCmp(arg, "--dry-run") == 0 ||
           JLI_StrCmp(arg, "-h") == 0 ||
           JLI_StrCmp(arg, "-?") == 0 ||
           JLI_StrCmp(arg, "-help") == 0 ||
           JLI_StrCmp(arg, "--help") == 0 ||
           JLI_StrCmp(arg, "-X") == 0 ||
           JLI_StrCmp(arg, "--help-extra") == 0 ||
           JLI_StrCmp(arg, "-version") == 0 ||
           JLI_StrCmp(arg, "--version") == 0 ||
           JLI_StrCmp(arg, "-fullversion") == 0 ||
           JLI_StrCmp(arg, "--full-version") == 0;
}

https://github.com/openjdk/jdk17u/blob/jdk-17.0.3+7/src/java.base/share/native/libjli/args.c#L448-L464

--versionも対象のようなので、試してみましょう。

s$ JDK_JAVA_OPTIONS='-Xmx1G -Dmy.prop=Hello --version' java App.java
NOTE: Picked up JDK_JAVA_OPTIONS: -Xmx1G -Dmy.prop=Hello --version
Error: Option --version is not allowed in environment variable JDK_JAVA_OPTIONS

確かにそのようですね。

ちょっとソースコードを変えてみましょう。

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"));
    }
}

JDK_JAVA_OPTIONS環境変数と、コマンドの引数でそれぞれ別のものを指定してみます。

$ JDK_JAVA_OPTIONS='-Xmx1G -Dmy.prop1=Hello' java -Dmy.prop2=World App.java
NOTE: Picked up JDK_JAVA_OPTIONS: -Xmx1G -Dmy.prop1=Hello
my.prop1 = Hello
my.prop2 = World

併用できるようです。

とりあえず、こんなところでしょうか?

まとめ

javaコマンドで使える、JDK_JAVA_OPTIONS環境変数を試してみました。

全然存在に気づいていなかったですね。環境変数で指定するならJAVA_TOOL_OPTIONSかなと思っていたのですが、javaコマンドを
ターゲットにするならJDK_JAVA_OPTIONS環境変数を選んだ方が良さそうですね。

覚えておきましょう。