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以外にもjstack
やjmap
のようなコマンドも用意されていますが、基本的にはjcmd
1つでなんでもできるみたいです。
基本的な使い方
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の起動時間を出力する | 低 |