jcmdで何ができるかをまとめる

はじめに

jcmdは起動しているJavaプロセスのGCの統計情報をとったり、JFR起動したり諸々のことを行なうのによく使われるツールかと思います。
今まで必要になったコマンドを調べて使うぐらいしかしたことなかったのですが、実際どのくらいのことができるのか、ツールの全体感を把握できてなかったのでまとめてみようかと思います。  また、その中でJVMについても薄く学べればよいかと思います。
基本的にはオラクルのドキュメントやツールのマニュアルを元に書いていますが、あくまで自分の理解をまとめているものと捉えていただけるとありがたいです。
そして、もし間違え等あればご指摘いただけると嬉しいです。

環境

今回、動作確認に用いるJDKやその動作環境は以下のとおりです。

$ java --version
openjdk 16 2021-03-16
OpenJDK Runtime Environment (build 16+36-2231)
OpenJDK 64-Bit Server VM (build 16+36-2231, mixed mode, sharing)

$ uname -srvmpio
Linux 5.4.0-80-generic #90-Ubuntu SMP Fri Jul 9 22:49:44 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

$ lsb_release -a
LSB Version:    core-11.1.0ubuntu2-noarch:security-11.1.0ubuntu2-noarch
Distributor ID: Ubuntu
Description:    Ubuntu 20.04.2 LTS
Release:    20.04
Codename:   focal

jcmd

そもそもjcmdってなんなのか?

manコマンドで出力されるマニュアルにはシンプルに以下のように書かれます。

Sends diagnostic command requests to a running Java Virtual Machine (JVM).

JVMに対して診断用のコマンドを送るためのツールだそうです。このコマンドをつかうとJVMに関する様々な情報を取得できたり、JVMに対する指示を送ったりすることができます。具体的にに何ができるかはあとでみていきます。
基本的にjcmdはJVMが起動しているマシンと同一マシンかつJVMを起動したユーザと同じもしくはユーザグループに所属している必要があるみたいです。
ただ、DiagnosticCommandMBeanインターフェースを使えば外部プロセスからも診断コマンドを送ることが可能になるみたいですが、まずは基本的な使い方をまとめたいのでこのブログでは深くはおいません。
JDKにはjcmd以外にもjstackjmapのようなコマンドも用意されていますが、基本的にはjcmd1つでなんでもできるみたいです。

基本的な使い方

jcmdコマンドは以下の構成を取ります。

jcmd pid|main-class PerfCounter.print
jcmd pid|main-class -f filename
jcmd pid|main-class command[ arguments]

使用方法は大きく3つのパターンがありますが、第一引数にJavaのpidもしくはメインクラスの名前が来る点は変わりません。 メインクラス指定を行なうと同じ名前のメインクラスを持つすべてのプロセスの情報を取得するようです。
また、pidに0を指定するとすべてのJavaプロセスに診断コマンドを送信します。 また、引数なし、もしくは-lオプション使って実行すると実行中のJavaプロセスの一覧を出力します。

$ jcmd -l
11234 com.intellij.idea.Main
11791 jdk.jcmd/sun.tools.jcmd.JCmd -l

上記はIntelliJのpidとjcmd自身のpidが出力されています。

PerfCounter.printは指定したプロセス(もしくはクラス)のパフォーマンスカウンターを出力します。

$ jcmd 11234 PerfCounter.print
11234:
java.ci.totalTime=65102836647
java.cls.loadedClasses=55377
java.cls.sharedLoadedClasses=0
java.cls.sharedUnloadedClasses=0
java.cls.unloadedClasses=178
(省略)

jcmd pid|main-class command[ arguments]のパターンでは第二引数に診断コマンドを受け取ります。
詳細は後ほどまとめますが、GC.heap_infoをコマンドとして渡すと以下のようにGCの一般的な情報が出力されます。

$ jcmd 11234 GC.heap_info
11234:
 par new generation   total 261120K, used 118880K [0x0000000080000000, 0x0000000091b50000, 0x00000000a9990000)
  eden space 232128K,  51% used [0x0000000080000000, 0x0000000087418068, 0x000000008e2b0000)
  from space 28992K,   0% used [0x000000008e2b0000, 0x000000008e2b0000, 0x000000008ff00000)
  to   space 28992K,   0% used [0x000000008ff00000, 0x000000008ff00000, 0x0000000091b50000)
 concurrent mark-sweep generation total 579960K, used 293996K [0x00000000a9990000, 0x00000000ccfee000, 0x0000000100000000)
 Metaspace       used 373150K, capacity 388822K, committed 394296K, reserved 1388544K
  class space    used 48366K, capacity 54385K, committed 56128K, reserved 1048576K

jcmd pid|main-class -f filenameでファイル名を指定して渡すとふくすうのコマンドをJavaプロセスに送信することができます。 例えば以下のようなファイルを用意します。

$ cat command.txt 
# コメント
GC.heap_info
VM.flags

このファイルを以下のようにコマンドに渡してやると、ヒープの情報とフラグの情報を取得することができます。

$ jcmd 11234 -f command.txt 
11234:
Command executed successfully
 par new generation   total 261120K, used 192941K [0x0000000080000000, 0x0000000091b50000, 0x00000000a9990000)
  eden space 232128K,  83% used [0x0000000080000000, 0x000000008bc6b410, 0x000000008e2b0000)
  from space 28992K,   0% used [0x000000008e2b0000, 0x000000008e2b0000, 0x000000008ff00000)
  to   space 28992K,   0% used [0x000000008ff00000, 0x000000008ff00000, 0x0000000091b50000)
 concurrent mark-sweep generation total 579960K, used 293996K [0x00000000a9990000, 0x00000000ccfee000, 0x0000000100000000)
 Metaspace       used 373154K, capacity 388822K, committed 394296K, reserved 1388544K
  class space    used 48366K, capacity 54385K, committed 56128K, reserved 1048576K
-XX:CICompilerCount=2 -XX:ErrorFile=/home/yuya-hirooka/java_error_in_idea_%p.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/home/yuya-hirooka/java_error_in_idea_.hprof -XX:InitialHeapSize=134217728 -XX:MaxHeapSize=2147483648 -XX:MaxNewSize=697892864 -XX:MaxTenuringThreshold=6 -XX:MinHeapDeltaBytes=196608 -XX:NewSize=44695552 -XX:NonNMethodCodeHeapSize=5825164 -XX:NonProfiledCodeHeapSize=122916538 -XX:OldSize=89522176 -XX:-OmitStackTraceInFastThrow -XX:ProfiledCodeHeapSize=122916538 -XX:ReservedCodeCacheSize=251658240 -XX:+SegmentedCodeCache -XX:SoftRefLRUPolicyMSPerMB=50 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:+UseFastUnorderedTimeStamps 

コマンドの一覧

jcmdで使えるコマンドには以下のようなものがあります。
(※IntteliJのプロセスに対するリストなので、完璧なリストではないかも知れません)

コマンド名 説明 インパク
Compiler.CodeHeap_Analytics コード・ヒープ(JITコンパイルされたネイティブコードとスペースマネジメントに必要な関数のストレージ)の分析結果を出力する 低:ヒープサイズとコンテントに影響される
Compiler.codecache コード・ヒープのキャッシュのレイアウトとバインドの情報を出力
Compiler.codelist コード・ヒープのキャッシュに保存されているコンパイルされた行きているメソッド
Compiler.directives_[add remove clear | print])|コンパイラー・ディレクティブ(JITコンパイラーへの指示)を追加(add)、最後に追加された指示の削除(remove)、すべての指示の削除(clear)、出力(print)。Json形式のファイルを引数に与える|低
Compiler.queue コンパイルのキューに積まれたメソッドの出力
GC.class_histogram ヒープの使用率の統計情報を出力 高:ヒープサイズとコンテントに影響される
GC.class_stats Javaクラスのメタデータに関する統計情報を出力 高:ヒープサイズとコンテントに影響される
GC.finalizer_info ファイナライザーのキューに関する情報を取得
GC.heap_dump HPROFフォーマットのJavaのヒープダンプを取得する 高:ヒープサイズとコンテントに影響される。-allフラグが指定されない場合はフルGCがリクエストされる
GC.heap_info ヒープサイズ、利用率などのヒープに関する一般的な情報を出力する。
GC.run java.lang.System.gc()を実行
GC.run_finalization java.lang.System.runFinalization()を実行 中:コンテントによって影響される
JFR.check 記録中のJFRの記録をチェックする
JFR.configure JFRの設定を行なう
JFR.dump JFRの記録をファイルに出力する。<key>=<value>の形式でオプションを指定する。
JFR.start JFRの記録を開始する 低〜高:レコードのないようによって影響される
JFR.stop JFRの記録を停止する
JVMTI.agent_load JVMTI(JVM Tool Interface。開発ツールや監視ツールで使用されるインターフェース)のネイティブエージェントをロードする
JVMTI.data_dump JVMTIに対するデータダンプのリクエス
ManagementAgent.start リモートマネジメントエージェントをスタートする。 低:影響なし
ManagementAgent.start_local ローカルのマネジメントエージェントをスタートする 低:影響なし
ManagementAgent.status マネジメントエージェントのステータスを出力 低:影響なし
ManagementAgent.stop リモートのマネジメントエージェントを停止する 低:影響なし
Thread.print すべてのスレッドのスタックトレースを出力する 中:スレッドの数によって影響される
VM.class_hierarchy すべてのロードされているクラスのヒエラルキーをツリーで出力する 中:ロードされているクラスの数によって影響される
VM.classloader_stats すべてのクラスローダーの統計情報を出力する
VM.classloaders すべてのクラスローダーをツリーで出力する 中:クラスローダーの数によって影響される
VM.command_line VMを起動する際に実行されたコマンドラインを出力する
VM.dynlibs ロードされたダイナミクなライブラリーを出力する
VM.events VMのイベントログを出力する
VM.flags 現在利用されているVMのフラグを出力する
VM.info VMの環境とステータスを出力する
VM.log ログ、ログの設定を出力する 低:影響なし
VM.metaspace メタスペースに関する統計情報を出力する 中:ロードされているクラスの数によって影響される
VM.native_memory ネイティブメモリの使用率を出力する
VM.print_touched_methods JVMのライフタイムで一度も触れれていないメソッドを出力する。-XX:+LogTouchedMethodsを有効化する必要がある
VM.set_flag VMのオプションをセットする
VM.stringtable Stringテーブルをダンプする 中:コンテントによって影響される
VM.symboltable シンボルテーブルをダンプする 中:コンテントによって影響される
VM.system_properties システムプロパティを出力する
VM.version VMのバージョンを出力する
VM.uptime VMの起動時間を出力する

参考資料