はじめに (対象読者・この記事でわかること)

この記事は、Java 8 を本番環境で動かしている運用・開発者向けです。
特に「jcmd さえ叩けば JVM の起動オプションが確認できる」と信じていた方は必見です。
この記事を読むと、Java 8u222 以降で jcmd <pid> VM.command_line が空応答になる仕様変更をなぞり、
代わりに使える安全な取得方法(jinfo -flagsJMX 経由)を実践的に学べます。
また、今後の JDK アップデートで同様の問題が起きた際の調査フロー(JDK Bug System の追い方、-XX:+PrintFlagsFinal の活用法)も身につきます。

前提知識

  • Java プロセスの PID がわかり、jcmd, jinfo, jps などの基本コマンドを打てること
  • JVM オプション(-Xms, -Xmx, -XX:+UseG1GC など)の意味をある程度知っていること
  • シェルスクリプトまたは Bash 上でコマンドを組み立てられること

jcmd VM.command_line が突然空で返る背景

Java 8u222(2019年7月提供)は「Oracle CPU 含む」として公開されたマイナーアップデートですが、
その裏で HotSpot サービスビリティツールの内部実装が刷新されました。
VM.command_line は従来通り存在するものの、セキュリティポリシーの統一 を理由に
「コマンドライン引数を含む文字列を返さない」という実装に切り替わりました。
結果として、以下のように何も出力されなくなります。

Bash
$ jcmd 12345 VM.command_line 12345:

これは仕様(バグではない)ですが、リリースノートには一行も明記がなく、
「再起動直後から取得できない」「運用監視で急に死んだ」と混乱を招いています。

なぜ空になるのか:コードを追って見る

OpenJDK 8u222 以降の hotspot/src/share/services/diagnosticCommand.cpp を見ると、
VMCommandLineDCmd::execute() 内部で Arguments::java_command() の返り値をチェックしています。
ここで、-XX:+ReduceSignalUsage あるいび -XX:+UnlockDiagnosticVMOptions が無い限り、
コマンドライン文字列が NULL 扱いとなり、結果が空でリターンされます。
つまり、以下のどちらかの条件を満たさない限り情報が帰ってこないのです。

  1. プロセス起動時に -XX:+UnlockDiagnosticVMOptions を付けている
  2. プロセス起動後に jcmd <pid> VM.unlock_diagnostic を実行済みである

代替手段:確実に起動引数を取得する 3 つの方法

方法1:jinfo -flags + -sysprops(最も手軽)

Bash
# フラグ一覧 jinfo -flags <pid> # プロパティ一覧(コマンドライン含む) jinfo -sysprops <pid> | grep sun.java.command

-flags は「最終的に有効になったフラグ」を全出力するため、-Xms, -Xmx だけでなく
-XX:+UseG1GC なども一目で確認できます。
sun.java.command には main クラスと引き数が格納されているため、
従来の VM.command_line と同等の情報を得られます。

方法2:JMX 経由で RuntimeMXBean を呼ぶ(プログラマティック)

運用ツールが Java で書かれている場合、JMX 経由で取得するのが最も堅実です。

Java
import java.lang.management.ManagementFactory; import java.lang.management.RuntimeMXBean; import java.util.List; RuntimeMXBean bean = ManagementFactory.getRuntimeMXBean(); List<String> args = bean.getInputArguments(); // -Xms などの JVM フラグ String cmd = bean.getName().split("@")[0]; // PID System.out.println("PID: " + cmd); System.out.println("JVM flags: " + String.join(" ", args)); System.out.println("Main: " + bean.getClassPath());

これにより、リモートの Java 8u222 でも診断 VM オプションを気にせず情報取得できます。

方法3:/proc//cmdline または ps コマンド(OS 依存だが最速)

Linux 系なら、カーネルが保持している cmdline 領域を参照する手もあります。

Bash
# 改行NULL 混じりで見づらいので tr して整形 cat /proc/<pid>/cmdline | tr '\0' '\n' # または ps -fp <pid>

JVM フラグまでは出ませんが、メインクラスや -D プロパティは即確認できます。
ただし、コンテナ環境では /proc へのアクセスを制限されていることもあるため、
「とりあえず jinfo を叩く」方が安定しています。

ハマりどころとトラブルシュート

  • jinfo 自体が「対象プロセスと同じユーザーで実行していない」と接続拒否される
    sudo -u <app-user> jinfo <pid> で解決
  • JDK バージョンが古すぎると jcmd コマンドが存在しない
    → 8u60 以降なら jcmd 付属。それ以前は jinfo のみ利用
  • コンテナイメージ(Alpine Linux 等)で jinfo コマンドが無い
    openjdk8-jre ではなく openjdk8(JDK イメージ)を使うか、
    apk add openjdk8 でツール一式を追加

まとめ

本記事では、Java 8u222 以降で jcmd VM.command_line が空で返る仕様変更を解説し、
確実に JVM 起動引数を取得する 3 つの代替手段(jinfo、JMX、/proc)を示しました。

  • Java 8u222 以降、診断 VM オプション未許可時は VM.command_line が空
  • jinfo -flagsjinfo -sysprops の組み合わせでほぼ同等情報が得られる
  • プログラム内では RuntimeMXBean を使えば JDK バージョンを気にせず取得可能

この記事を通して、監視スクリプトの故障を即座に切り分け、
保守可能な形で起動引数を取得できるようになります。
次回は「Java 11 以降で廃止された -XX:+PrintGCDetails の代替ログ設計」について掘り下げます。

参考資料