SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

Javaがもっとおもしろくなる! プログラムの実行を担うJVMの情報を見てみよう

知って使えるJVMのログ出力と情報収集や解析ツール

Javaがもっとおもしろくなる! プログラムの実行を担うJVMの情報を見てみよう 第2回

  • X ポスト
  • このエントリーをはてなブックマークに追加

JVM関連ツールを使ってJVMの実行情報を見よう

 次の題材はJVMの情報取得や設定変更をするツールです。JDK(Java Development Kit)を配置した(言い換えるとJAVA_HOMEに設定した)ディレクトリ配下のbinディレクトリにはJVMの情報取得や設定変更ができるツールがあります。binディレクトリを見てみましょう。Javaのバージョンは17(Eclipse TemurinのJDK 17.0.2+8)です。28個のコマンドがあります。

$ cd $JAVA_HOME/bin
$ ls
jar java javadoc jcmd jdb jdeps jhsdb
jinfo jmap jpackage jrunscript jstack jstatd rmiregistry
jarsigner javac javap jconsole jdeprscan jfr
jimage jlink jmod jps jshell jstat keytool serialver

 本記事ではこの中からJVMに関連するツールを紹介します。以下の2つです。

  1. jcmd
  2. jhsdb

jcmd

 jcmdコマンド(以下jcmd)はJVMの情報取得や設定に関して広い範囲をカバーします。jcmdがアクセスできる対象はローカルプロセスのみですが、そうであっても非常に便利なコマンドで使う頻度も多いです。トラブルシュートにもとても役立つコマンドで、ぜひ知っておきましょう。ただしjcmdはカバーする範囲が広い分、コマンドの実行方法が複雑です。

 Javaアプリケーションの実行環境であるJVMからさまざまな情報を取得するためには、Javaの以前のバージョンでは例えばjstackやjmapなど取得したい情報に合わせてコマンドを使い分ける必要がありました。jcmdはそういったコマンドの代替であり、現在ではjcmdを使うだけでさまざまな情報を取得できるようになっています。さらにjcmdは情報を取得できるだけでなく、実行に関する設定の追加や変更もできます。

 本記事の前半で紹介したUnified Loggingのログ出力設定は、今まで実行時のオプションで指定していました。もし実行した後にログ出力設定の一部を変えたいとするとどうすればよいでしょうか。実行時のオプションを変更してアプリケーションを再実行することが最初に思い浮かぶかと思います。ただし、この方法ではアプリケーションは一度停止します。停止しても問題ないアプリケーションもあるでしょうし、そうでないものもあるでしょう。

 ULの設定変更はjcmdから実行することもできます。jcmdは実行中のアプリケーションとやり取りをします。そのためアプリケーションを実行したままULのログ設定を変更できるため、アプリケーションを停止させる必要がありません。早速jcmdでのULログ設定を試してみましょう。以下のプログラムを用意します。

class LongSleep {
    public static void main(String... args) throws Exception {
        Thread.sleep(300000);
    }
}

 単にスリープするだけのものです。LongSleep.javaというファイル名で保存し、実行します。スリープするプログラムなので実行するとしばらく実行中になります。その間に別のコマンドプロンプトなどから以下のコマンドを実行します。

$ jcmd
87476 jdk.compiler/com.sun.tools.javac.launcher.Main LongSleep.java
(個々の状況により上の数字や出力行数が変わります)

$ jcmd 87476 VM.log output="sample.log"
87476:
Command executed successfully
(87476という数字の部分は最初のコマンドの出力にあるLongSleep.javaに対応した数字を指定します)

 ここまで実行できたら最初のLongSleepプログラムを停止させてもよいです。このプログラムを実行したディレクトリを見るとsample.logというファイルができています。このファイルの中を見るとULのログが出力できていることがわかります。今試したことを整理します。ULログの設定をせずにアプリケーションを実行したあと、アプリケーションを実行したままjcmdを使ってULログの出力を新たに設定しました。その結果ファイルにULログが出力できたことを確認しました。

 では実行したコマンドの内容を詳しく見ていきましょう。最初にjcmdとだけ入力して実行しました。この場合実行中のJavaアプリケーションのプロセスとそのプロセスIDを出力します。先ほどの例にあった87476というのはプロセスIDの数値だったのです。なおJDKにはプロセスIDを調べるためのコマンドとして以前からjpsコマンドがありました。jcmdとjpsのどちらを使ってもプロセスIDを調べられます。ただし出力内容や指定できるオプションに違いがあります。

 jcmdでJavaプロセスの一覧を出す以外の機能を使う場合、jcmdと入力した次に対象とするプロセスIDを指定します。jcmd <プロセスIDまたはメインクラス> <コマンド>という形式です。先ほどの例ではjcmd 87476 VM.logと入力しました。そのためコマンドはVM.logです。さらにコマンドは.の前の部分をドメイン、後ろの部分を操作と言います。VM.logではVMがドメイン、logが操作です。ドメインで関連する操作がまとめられています。VMドメインの操作にはJVMの実行情報取得や設定変更があります。VMドメインの操作は以下のものがあります。

  • VM.classloader_stats
  • VM.class_hierarchy
  • VM.command_line
  • VM.dynlibs
  • VM.info
  • VM.log
  • VM.flags
  • VM.native_memory
  • VM.print_touched_methods
  • VM.set_flag
  • VM.stringtable
  • VM.symboltable
  • VM.systemdictionary
  • VM.system_properties
  • VM.uptime
  • VM.version

 各操作の詳細については省略します。ドメインはVMドメインを含め以下のものがあります(※厳密にはドメインではなく別個の命令としてPerfCounter.printもあります)。

ドメイン 内容
VM JVMの実行情報取得、設定変更
Compiler JITコンパイラ関連
GC ガベージコレクション関連
Thread 現状スレッドダンプ取得のみ
JVMTI JVM Tool Interface関連、例えばJVMTIエージェントのロード
JFR JDK Flight Recorder関連
ManagementAgent JMXエージェント関連

 ドメインの内容を見ると利用価値の高いものばかりです。各ドメインにある操作の一覧は公式リファレンスを参照するとよいでしょう。もしくはjcmd <プロセスIDまたはメインクラス> helpを実行するとそのプロセスに対して実行できるコマンドの一覧を出力します。

$ jcmd 59756 help                                                          
59756:                                                                                                                                     
The following commands are available:                                                                                                      
Compiler.CodeHeap_Analytics                                                                                                                
Compiler.codecache
(以下省略)

 なお各操作で指定できるオプションなど使い方を調べたいときはjcmd <プロセスIDまたはメインクラス> help <コマンド>とコマンドの前にhelpを入力して実行するとそのコマンドの使い方を出力します。

$ jcmd 16655 help Thread.print
16655:
Thread.print
Print all threads with stacktraces.

Impact: Medium: Depends on the number of threads.

Permission: java.lang.management.ManagementPermission(monitor)

Syntax : Thread.print [options]

Options: (options must be specified using the  or = syntax)
        -l : [optional] print java.util.concurrent locks (BOOLEAN, false)
        -e : [optional] print extended thread information (BOOLEAN, false)

 jcmdを使う機会はアプリケーション運用時に障害が発生した場合などです。障害が発生したとすれば再度同じ現象が起こったときに上記のThread.printでスレッドダンプを取ったり、JFRドメインの操作を使ってすぐにフライトレコードを取り始めたりできます。jcmdはJavaエンジニアにとっての使い勝手の良い調査キットと言えます。jcmdが作られたことで同様のことができるコマンドjstackやjinfo、jmapは使わなくなりました。

jhsdb

 OpenJDKのJVMであるHotSpot VMはC++で書かれています。そのためJVMがクラッシュした場合C++とJava両方のコードを見なければならないことが多いです。C++側はGDBやLLDBなどのネイティブデバッガを使って調べますが、その情報を元にJava側を調べるときに使うツールがHSDBです。HSDBとはHotSpot Debuggerを意味します。HSDBにはGUI版とCLI版があり、jhsdbコマンドで指定してどちらも起動できます。jhsdbコマンドのヘルプを見ます。

$ jhsdb --help
    clhsdb              command line debugger
    hsdb                ui debugger
    debugd --help       to get more information
    jstack --help       to get more information
    jmap   --help       to get more information
    jinfo  --help       to get more information
    jsnap  --help       to get more information

 jhsdb の形式で実行します。本記事ではhsdb、debugdの2つのmode(以下モード)を解説します。jstackとjmap、jinfoはそれぞれbinディレクトリにある同名のコマンドと同じ機能です。またjsnapはパフォーマンスカウンタ情報を取得するためのものです。

 モード 内容
hsdb GUI版のHSDBを起動する
clhsdb CLI版のHSDBを起動する
debugd デバッグサーバを起動する
jstack スタックトレースを出力する
jmap ヒープ情報を出力する
jinfo 実行時のオプションやシステムプロパティの値を取得、設定する
jsnap パフォーマンスカウンタ情報を出力する

 hsdbモードを使ってみましょう。GUI版でもCUI版でもHSDBを活用する方法は次の3つです。

  1. 実行中のJavaアプリケーションのプロセスIDを指定してHSDBを接続する
  2. クラッシュの際のコアファイルと使用したjavaコマンドを指定してHSDBに読み込ませる
  3. 起動しているデバッグサーバを指定してHSDBを接続する

 実行時のオプションとして上記の内容を指定するか、HSDB起動後に上記の内容を指定します。本記事では1と3のケースを試します。jcmdの説明で使用したLongSleepのプログラムを実行しましょう。その後jcmdでプロセスIDを調べ、以下のコマンドを実行します。

$ jhsdb hsdb --pid 44228

 GUI版のHSDBが起動し、以下のような画面を表示します。なおmacOSではSIP(System Integrity Protection)を無効にしなければなりません。SIPが有効の状態だとHSDBからアプリケーションに接続しようとするとエラーが発生します。SIPを無効にする方法はmacOSのリカバリモードに入り、ターミナルでcsrutil disableと実行し、再起動します。なお、リカバリモードへの入り方はいわゆるM1 MacとIntel版Macで異なりますので注意してください。

HSDB起動画面
HSDB起動画面

 HSDBのよくある使い方としては、HSDBでアドレスを確認したあとGDBやLLDBなどのネイティブデバッガでそのアドレスを使うというものです。この記事でそこまで解説すると記事の趣旨やスコープから大きく外れてしまうので、今回は簡略化してHSDBでクラスのアドレスを調べ、さらにインスペクタでそのアドレスを入力して情報を取得します。HSDBのメニューからTools > Class Browserを選択します。Class Browser画面が表示するリストの一番上に今回作成し実行したLongSleepクラスがあります。そのリンクを押し、画面下部のclass LongSleep @0x0000000800d2c000といった部分の@より後ろのアドレス部分に着目します。テキストとしてコピーしておくとよいでしょう。なおこのアドレス部分は実行環境によって異なります。

 次にメニューからTools > Inspectorを選択します。Inspector画面のAddress / C++ Expressionのテキストボックスに先ほど着目したアドレスを入力しEnterキーを押します。すると以下のような画面になります。

Inspector画面
Inspector画面

 Inspector画面が表示する内容はLongSleepクラスのJavaではなくC++での表現です。HSDBがJavaとC++の世界を橋渡しするというイメージを持っていただければよいでしょう。JVMがクラッシュしてコアダンプを出力したときなどはGDBやLLDBなどのネイティブデバッガと合わせてHSDBを使うことで障害解析が進むことも多いです。

 ここまでHSDBをローカルのJavaプロセスに接続していました。HSDBはローカルだけでなくリモートからも利用できます。そのためには対象となるマシンでHSDBをリモートデバッグサーバとして起動します。下の図のように別のマシンでクライアントとしてHSDBを起動し、リモートデバッグサーバに接続してその情報を閲覧できます。

リモートデバッグサーバへの接続
リモートデバッグサーバへの接続

 解析対象となるマシンにGUI環境がないけれどGUI版のHSDBを使いたいなど、そのマシン上で作業できない理由があることもあるでしょう。解析対象のマシンとクライアントとなるマシンの間にOSなどプラットフォームの違いがあっても大丈夫です。試してみましょう。

 リモートデバッグサーバとしてWindowsマシンを、HSDBのクライアントとしてmacOS(Apple M1チップ)を使用しました。Windows上でLongSleepプログラムを実行しjcmdでプロセスIDを調べたあと、デバッグサーバを起動します。ホスト名はjyukutyo-windowsと指定しました。ホスト名は皆さんのネットワークの設定状況に合わせて変更してください。以下のようなメッセージが出力されればデバッグサーバを起動できています。

> jhsdb debugd --pid 80465 --hostname jyukutyo-windows
Attaching to process ID 80465 and starting RMI services, please wait...
Debugger attached and RMI services started.

 次にクライアントであるmacOSの方でHSDBを起動しデバッグサーバに接続します。

$ jhsdb hsdb --connect jyukutyo-windows

 ローカルで接続したときと同様、以下のような画面を表示します。以降の操作方法はローカルのときと同じです。注意点としてアプリケーション、リモートデバッグサーバ、クライアントで使用するJavaのバージョンを完全に一致させておくことが望ましいです。たとえマイナーバージョンしか違っていなくてもJVMの実装内に違いがあるため、正確に情報が見られないこともあります。異なるバージョンを使用すると警告メッセージが出ます。なおオプション-Dsun.jvm.hotspot.runtime.VM.disableVersionCheckを使用すればこのチェックを無効にできますが、チェックしないというだけで正確に情報が見られないことに変わりはありません。

リモート接続画面
リモート接続画面

 またjava.rmi.ConnectExceptionなどの例外が出て接続できないときは、WindowsはC:\Windows\System32\drivers\etc\hosts、LinuxやmacOSは/etc/hostsの内容を確認し適切に設定してください。デバッグサーバのホスト名とIPアドレスを両方のマシンに設定します。ただしデバッグサーバをmacOS(Apple M1チップ、Intelチップどちらでも)にするとクライアントから接続する際に以下の例外が発生します。

Exception in thread "AWT-EventQueue-0" java.lang.ClassCastException: class sun.jvm.hotspot.debugger.remote.RemoteDebuggerClient cannot be cast to class sun.jvm.hotspot.debugger.bsd.BsdDebuggerLocal (sun.jvm.hotspot.debugger.remote.RemoteDebuggerClient and sun.jvm.hotspot.debugger.bsd.BsdDebuggerLocal are in module jdk.hotspot.agent of loader 'app')          
        at jdk.hotspot.agent/sun.jvm.hotspot.runtime.bsd_aarch64.BsdAARCH64JavaThreadPDAccess.getThreadProxy(BsdAARCH64JavaThreadPDAccess.java:136)
(以下中略)

 アプリケーションの多くはmacOS以外で稼働していると思います。このようにHSDBはリモートから接続できることも知っておくと障害発生時に使う機会があるでしょう。

まとめ

 本記事ではJVMのログを出力するUnified Logging(UL)とJDKに含まれるツールの中からjcmdとjhsdbを紹介しました。ULはJava 9からですので今後多くのアプリケーションがJava 8から新しいバージョンに移行する際に今回の内容が役に立つでしょう。jcmdとjhsdbは障害発生に備えて身につけておきたいツールです。

 さて、この連載はJVMの内部を文章で解説するものではなく、その情報を取得するさまざまなツールの利用を通じてJVMについての知識を深めることを目的としています。連載を継続するためには、ここまで読んでくださった皆さんの(ポジティブな)お声がぜひとも必要です! この画面にあるツイートやシェア、ブックマークボタンからフィードバックをいただけましたらうれしいです。次回はJITコンパイラとその関連ツールを使ってJITコンパイルの情報を取得するという内容です。次回もお楽しみに!

参考資料

  1. Introduction to JVM Unified Logging (JEP-158 / JEP-271)
  2. Unified Logging Of JVM Messages With The \-Xlog Option

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
Javaがもっとおもしろくなる! プログラムの実行を担うJVMの情報を見てみよう連載記事一覧
この記事の著者

阪田 浩一(株式会社NTTデータ)(サカタ コウイチ)

 Javaのオープンソース実装OpenJDKの研究開発とそのトラブルシューティングに従事。OpenJDK Author(最小の開発者ロール)、Javaチャンピオン。Java仮想マシン(JVM)により一層詳しくなりたいという思いで歩を進めている。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/16074 2022/08/15 11:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング