🐱 うさねこ教室 Java・OpenAPI・OpenTelemetry の教室

症状から診断手段を選ぶ

🐱 この章の目次

同じ「遅い」でも集める証拠は異なる

診断手段は、調べたい問いから選びます。 ブレークポイントは値と制御の流れを追う手段であり、本番環境のCPU使用率を説明する手段ではありません。 CPUプロファイルも、ロック待ちや外部I/O待ちだけで遅い処理を直接は説明できません。

症状または問い最初に確認するもの次に使う診断手段
特定の入力で値が壊れる例外、ログ、再現テストデバッガー
応答が止まり、スレッドが進まないスレッド状態、ロック所有者jcmd <pid> Thread.print
CPU 使用率が高いプロセスとコンテナの CPU 指標CPU プロファイル
CPU 使用率は低いが遅い外部依存のトレース、スレッド状態Wall-clock プロファイル、JFR の I/O イベント
ヒープが増え続ける使用量と GC 後のヒープJFR、クラスヒストグラム、必要ならヒープダンプ
GC 停止が増えたGC 回数、停止時間、割り当て量JFR の GC と割り当てイベント

この表は一つの道具だけで原因が確定することを意味しません。 たとえばCPU使用率の上昇をメトリクスで検知し、JFRで発生時刻を絞り、async-profilerで幅の広いスタックを調べるというように証拠をつなぎます。

デバッガーが向く範囲

Java Platform Debugger Architecture(JPDA) は、デバッガーと対象JVMを接続する仕組みです。 JDKには、この仕組みを使う簡単なコマンドラインデバッガーとして jdb が含まれます。

手元のプログラムは、次のように jdb から起動できます。

javac Example.java
jdb Example

起動済みJVMへ接続する場合は、対象をJDWP待受け付きで起動します。 address=8000 はループバックアドレスだけで待ち受けます。

java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000 Example
jdb -attach 8000

JDWPで接続したデバッガーは、実行の停止、変数の参照、メソッドの呼び出しなど、対象プロセスを強く制御できます。 本番環境で常時公開せず、必要な場合もループバックや認証済みトンネルなどで接続元を限定します。 address=*:8000 は全インターフェースで待ち受ける指定なので、ローカル演習では使いません。

停止中のスレッドを調べる

処理が進まないときは、まずスレッドダンプで待機理由とロック所有者を確認します。 jcmd は同じホスト上で、対象JVMと同じ実効ユーザーおよびグループから実行するJDKの診断コマンドです。

# Java プロセスを確認する
jcmd -l

# スレッドとロックの状態を表示する
jcmd <pid> Thread.print -l

コンテナ内のJVMは、ホスト側の jcmd -l に表示されないことがあります。 その場合はコンテナ内で jcmd を実行するか、コンテナから見えるPIDを確認します。

スレッドダンプを時間を空けて複数回取得すると、同じスタックで止まり続けているスレッドと、たまたまその瞬間だけ待機していたスレッドを区別しやすくなります。

取得コストを記録する

診断コマンドにも対象JVMへの影響があります。 JDK 21の jcmd リファレンスでは、Thread.print は中程度、GC.class_histogramGC.heap_dump は高い影響と分類されています。 ヒープダンプはヒープサイズに応じて時間と保存領域を使い、既定ではFull GCも要求します。

本番環境では、実行したコマンド、時刻、対象PID、設定、生成ファイル、前後のサービス指標を作業記録へ残します。 記録があれば、診断そのものがレイテンシへ与えた影響も後から区別できます。

参考資料