この章では、Oracle JRockit JVMのスレッド・ダンプの取得方法と使用方法について説明します。スレッドおよびスレッドの同期化に関する基本情報については、『Oracle JRockit JDKの紹介』を参照してください。
スレッド・ダンプは、プロセスの一部となっているすべてのスレッドの状態のスナップショットです。各スレッドの状態は、スレッドのスタックの内容を示したスタック・トレースに表示されます。スレッドには、実行中のJavaアプリケーションに属するスレッドや、JVM内部スレッドがあります。
スレッド・ダンプは、アプリケーションのスレッド・アクティビティに関する情報を示します。問題を診断し、アプリケーションおよびJVMのパフォーマンスを最適化するのに役立ちます。たとえば、スレッド・ダンプにはデッドロックの発生が自動的に表示されます。デッドロックが発生すると、アプリケーションの一部または全部が完全な停止状態になります。
この章では、次の節について説明します。
プロセスからスレッド・ダンプを作成するには、次のいずれかを実行します。
プロセスの実行中に[Ctrl]+[Break]を押します(または、LinuxおよびSolarisでは、プロセスにSIGQUIT
を送信します)。
コマンドラインに、次の引数を入力します。
bin\jrcmd.exe pid print_threads
スレッド・ダンプがコマンドラインに表示されます。
この項ではスレッド・ダンプの一般的な内容について説明します。スレッド・ダンプの例が最初から最後まで表示されます。例3-1、例3-2、例3-3、例3-4および例3-5はスレッド・ダンプの各コンポーネントを表しています。次にメイン・スレッドに関する情報が表示されてから、JVMのすべての内部スレッド、その後に他のすべてのJavaアプリケーション・スレッド(ある場合)が続きます。最後にロック・チェーンに関する情報が表示されます。
サンプルのスレッド・ダンプは、3つのスレッドを作成し、そのスレッドがすぐにデッドロックに入るというプログラムから取得したものです。アプリケーション・スレッドThread-0、Thread-1、およびThread-2は、Javaコード内の3つの異なるクラスに対応しています。
例3-2に、メイン・アプリケーション・スレッドのスタック・トレースを示します。スレッド情報の行があり、その後に、ロックに関する情報とスレッド・ダンプの時点でのスレッドのスタック・トレースが続きます。
例3-2 スレッド・ダンプのメイン・スレッド
"Main Thread" id=1 idx=0x2 tid=48652 prio=5 alive, in native, waiting -- Waiting for notification on: util/repro/Thread1@0x01226528[fat lock] at jrockit/vm/Threads.waitForSignal(J)Z(Native Method) at java/lang/Object.wait(J)V(Native Method) at java/lang/Thread.join(Thread.java:1095) ^-- Lock released while waiting: util/repro/Thread1@0x01226528[fat lock] at java/lang/Thread.join(Thread.java:1148) at util/repro/DeadLockExample.main(DeadLockExample.java:23) at jrockit/vm/RNI.c2java(IIII)V(Native Method) -- end of trace
名前と他の識別情報の後に、メイン・アプリケーション・スレッドの様々なステータス・メッセージが出力されます。例3-2のメイン・アプリケーション・スレッドは、実行中のスレッド(alive
)です。これは、JVM内部コードまたはユーザー定義のJNIコード(in native
)です。現在、オブジェクトが解放されるのを待機しています(waiting
)。スレッドが(Object.wait()
を呼び出して)ロックに関する通知を待機している場合は、これがスタック・トレースの先頭に表示されます(Waiting for notification on
)。
各スレッドについて、JRockit JVMは以下の情報を出力します。
スレッドがロックを取得しようとした(同期ロックに入ろうとした)ものの、別のスレッドがすでにロックを保持している場合は、スタック・トレースの先頭に表示されます(Blocked trying to get lock
)。
(Object.wait()
を呼び出して)スレッドがロック上で通知を待機している場合は、スタック・トレースの先頭に表示されます(Waiting for notification
)。
スレッドがロックを取得した場合、これがスタック・トレースに表示されます。関数呼出しを表すスタック・トレース内の行の後に、その関数でスレッドが取得したロックのリストがあります。これは^-- Holding lock
のように記述されます(^--
は、ロックがその上の行の関数で取得されたものであることを示しています)。
Javaのオブジェクトにおける(通知の)待機のセマンティクスは多少複雑です。同期ブロックに入るには、最初にオブジェクトのロックを取得し、次にそのオブジェクトのwait()
を呼び出す必要があります。waitメソッドでは、スレッドが通知を待機して実際にスリープ状態に入る前に、ロックが解放されます。スレッドが通知を受け取ると、wait()
はロックを返す前に再び取得します。スレッドがロックを取得し、そのロック上で通知を待機している場合、ロックの取得時に記述されるスタック・トレースの行には、(Holding lock
)ではなく(Lock released while waiting
)と表示されます。
すべてのロックはClassname@0xLockID[LockType]
の形式で記述されます。例:
java/lang/Object@0x105BDCC0[thin lock]
Classname@0xLockID
はロックが属しているオブジェクトを表します。Classnameはオブジェクトの実際の完全修飾クラス名です。LockID
は1つのスレッド・ダンプで有効な一時的なIDです。1つのスレッド・ダンプで、スレッドAがロックjava/lang/Object@0x105BDCC0
を保持していて、スレッドBがロックjava/lang/Object@0x105BDCC0
を待機している場合、それらは同じロックになります。それ以降のスレッド・ダンプでは、スレッドが同じロックを保持したとしても、LockID
は異なる可能性があります。LockType
はJVMの内部的なタイプのロック(ファット、シン、再帰的、または遅延)を表します。アクティブなロック(モニター)のステータスもスタック・トレースに表示されます。
コンパイラによる最適化が原因で、表示されるロック情報が正確ではない場合があります。これは、2つのことを意味します。
あるスレッドが同じ関数内で最初にロックAを取得し、次にロックBを取得した場合、これらのロックが出力される順序は不特定です。
あるスレッドがメソッドedit()
内でメソッドsave()
を呼び出し、save()
でロックAを取得した場合、そのロックはedit()
で取得されたものとして出力されることがあります。
通常はこのことは問題になりません。ロックの行の順序は正しい場所からそれほど大きく離れることはありません。また、ロックの行が失われることはなく、スレッドが取得したすべての行はスレッド・ダンプに表示されます。
例3-3に、JVM内部スレッドのトレースを示します。daemon
という状態インジケータからわかるように、このスレッドはデーモン・スレッドとしてマークされています。デーモン・スレッドは、(この例のように)JVM内部スレッドである場合と、java.lang.Thread.setDaemon()
でデーモン・スレッドとしてマークされたスレッドである場合があります。
例3-3 JVM内部スレッドのリスト内で最初と最後のスレッド
"(Signal Handler)" id=2 idx=0x4 tid=48668 prio=5 alive, in native, daemon [...] "(Sensor Event Thread)" id=10 idx=0x1c tid=48404 prio=5 alive, in native, daemon
例3-3では、JVM内部スレッドに関するロック情報とスタック・トレースは出力されていません。これはデフォルトの設定です。
JVM内部スレッドのスタック・トレースを表示する場合は、print_threads
コマンドを実行するときにパラメータnativestack=true
を使用します。コマンドラインで、次のように入力します。
bin\jrcmd.exe <pid> print_threads nativestack=true
通常、ユーザーが関心を寄せるのは実行中のJavaアプリケーションのスレッド(メイン・スレッドを含む)です。メイン・スレッド以外のすべてのJavaアプリケーション・スレッドは、スレッド・ダンプの最後の方に表示されます。例3-4は、3つの異なるアプリケーション・スレッドのスタック・トレースを示しています。
例3-4 追加のアプリケーション・スレッド
"Thread-0" id=11 idx=0x1e tid=48408 prio=5 alive, in native, blocked -- Blocked trying to get lock: java/lang/Object@0x01226300[fat lock] at jrockit/vm/Threads.waitForSignal(J)Z(Native Method) at jrockit/vm/Locks.fatLockBlockOrSpin(ILjrockit/vm/ObjectMonitor;II)V(Unknown Source) at jrockit/vm/Locks.lockFat(Ljava/lang/Object;ILjrockit/vm/ObjectMonitor;Z)Ljava/lang/Object;(Unknown Source) at jrockit/vm/Locks.monitorEnterSecondStage(Ljava/lang/Object;I)Ljava/lang/Object;(Unknown Source) at jrockit/vm/Locks.monitorEnter(Ljava/lang/Object;)Ljava/lang/Object;(Unknown Source) at util/repro/Thread1.run(DeadLockExample.java:34) ^-- Holding lock: java/lang/Object@0x012262F0[thin lock] ^-- Holding lock: java/lang/Object@0x012262F8[thin lock] at jrockit/vm/RNI.c2java(IIII)V(Native Method) -- end of trace "Thread-1" id=12 idx=0x20 tid=48412 prio=5 alive, in native, blocked -- Blocked trying to get lock: java/lang/Object@0x012262F8[thin lock] at jrockit/vm/Threads.sleep(I)V(Native Method) at jrockit/vm/Locks.waitForThinRelease(Ljava/lang/Object;I)I(Unknown Source) at jrockit/vm/Locks.monitorEnterSecondStage(Ljava/lang/Object;I)Ljava/lang/Object;(Unknown Source) at jrockit/vm/Locks.monitorEnter(Ljava/lang/Object;)Ljava/lang/Object;(Unknown Source) at util/repro/Thread2.run(DeadLockExample.java:48) at jrockit/vm/RNI.c2java(IIII)V(Native Method) -- end of trace "Thread-2" id=13 idx=0x22 tid=48416 prio=5 alive, in native, blocked -- Blocked trying to get lock: java/lang/Object@0x012262F8[thin lock] at jrockit/vm/Threads.sleep(I)V(Native Method) at jrockit/vm/Locks.waitForThinRelease(Ljava/lang/Object;I)I(Unknown Source) at jrockit/vm/Locks.monitorEnterSecondStage(Ljava/lang/Object;I)Ljava/lang/Object;(Unknown Source) at jrockit/vm/Locks.monitorEnter(Ljava/lang/Object;)Ljava/lang/Object;(Unknown Source) at util/repro/Thread3.run(DeadLockExample.java:65) ^-- Holding lock: java/lang/Object@0x01226300[fat lock] at jrockit/vm/RNI.c2java(IIII)V(Native Method) -- end of trace
3つのスレッドはすべてブロックされた状態です(blocked
で示されています)。つまり、これらのスレッドはいずれも同期化ブロックに入ろうとしています。Thread-0はロックObject@0x01226300[fat lock]
を取得しようとしていますが、このロックはThread-2が保持しています。Thread-2とThread-1は両方ともObject@0x012262F8[thin lock]
を取得しようとしていますが、このロックはThread-0が保持しています。つまり、Thread-1がブロックされている間、Thread-0とThread-2がデッドロックを発生させています。
JRockit JVMは、実行中のスレッドの中で、デッドロックしたロック・チェーン、ブロックされたロック・チェーン、開いたロック・チェーンを自動的に検出します。例3-5には、スレッドT1、T2、T3、T4およびT5によって形成されたすべてのロック・チェーンが示されています。この情報を使用して、Javaコードのチューニングやトラブルシューティングを行えます。
例3-5 デッドロックしたチェーンとブロックされたロック・チェーン
Circular (deadlocked) lock chains ================================= Chain 6: "Dead T1" id=16 idx=0x48 tid=3648 waiting for java/lang/Object@0x01225018 held by: "Dead T3" id=18 idx=0x50 tid=900 waiting for java/lang/Object@0x01225010 held by: "Dead T2" id=17 idx=0x4c tid=3272 waiting for java/lang/Object@0x01225008 held by: "Dead T1" id=16 idx=0x48 tid=3648 Blocked lock chains =================== Chain 7: "Blocked T2" id=20 idx=0x58 tid=3612 waiting for java/lang/Object@0x01225310 held by: "Blocked T1" id=19 idx=0x54 tid=2500 waiting for java/lang/Object@0x01224B60 held by: "Open T3" id=13 idx=0x3c tid=1124 in chain 1 Open lock chains ================ Chain 1: "Open T5" id=15 idx=0x44 tid=4048 waiting for java/lang/Object@0x01224B68 held by: "Open T4" id=14 idx=0x40 tid=3380 waiting for java/lang/Object@0x01224B60 held by: "Open T3" id=13 idx=0x3c tid=1124 waiting for java/lang/Object@0x01224B58 held by: "Open T2" id=12 idx=0x38 tid=3564 waiting for java/lang/Object@0x01224B50 held by: "Open T1" id=11 idx=0x34 tid=2876 (active)
この項では、スレッド・ダンプ内のスレッドの様々なステータスまたは状態を説明します。
スレッドがスレッド・ダンプに示す可能性のある活動状態を表3-0に示します。
表3-1 スレッドの活動状態
状態 | 説明 |
---|---|
|
これは通常の実行中のスレッドです。実質的に、スレッド・ダンプ内のすべてのスレッドは(alive)になります。 |
|
|
|
このスレッドは |
スレッドがスレッド・ダンプに示す可能性のある実行状態を表3-2に示します。
表3-2 スレッドの実行状態
状態 | 説明 |
---|---|
|
このスレッドは同期ブロックに入ろうとしましたが、別のスレッドがロックを取得しています。ロックが解除されるまで、このスレッドはブロックされます。 |
|
これは |
|
このスレッドは、オブジェクトの |
|
このスレッドは |
|
このスレッドは |
|
スレッドの実行は |
スレッドがスレッド・ダンプに示す可能性のある特別状態を表3-3に示します。これらの状態はすべて相互に排他的ではありません。
表3-3 スレッドの特別状態
状態 | 説明 |
---|---|
|
ユーザーはこのスレッドで |
|
これは、JVM内部スレッドか、 |
|
このスレッドは、ユーザーが指定したJNIコードまたはJVM内部コードのいずれかのネイティブ・コードを実行しています。 |
|
このスレッドはJVM内部コードを実行中で、自身を |
|
このスレッドはJVM内部コードを実行中で、JVM内部ロックを取得しようとしています。該当するロックは別のスレッドで保持されているため、スレッドはブロックされています。 |
|
このスレッドはJVM内部コードを実行中で、JVM内部ロックに関する別のスレッドからの通知を待機しています。 |
この項では、トラブルシューティングと診断にスレッド・ダンプを使用するための情報を示します。
デッドロックの検出だけでなく、トラブルシューティングにもスレッド・ダンプを使用するには、同じプロセスから複数のスレッド・ダンプを取得する必要があります。動作の長時間の分析を行う場合は、複数のスレッド・ダンプと、他の診断ツール(Oracle JRockit Mission Controlの一部であるJRockitフライト・レコーダなど)を組み合せる方が効果的です。
Oracle JRockit JVMは自動的にスレッド・ダンプ情報を分析して、循環(デッドロックした)ロック・チェーンやブロックされたロック・チェーンが存在するかどうかを検出します。ブロックされたロック・チェーンはデッドロックではなく、競合を示しています。
スレッド内のデッドロック以外のものを検出するには、複数の連続するスレッド・ダンプを作成する必要があります。これにより、複数のスレッドが同じロックを取得しようとする際の競合の発生が検出されます。競合は、デッドロックされていなくても、長いオープンなロック・チェーンを形成する可能性があり、パフォーマンスの低下につながります。
(連続したスレッド・ダンプで)アプリケーションの1つまたは複数のスレッドがロックの解放を待機して一時的にスタックしていることを見つけた場合は、Javaアプリケーションのコードを調べて、同期化(シリアライゼーション)が必要かどうか、スレッドを別の方法で編成できるかどうかを確認してください。