アプリケーションがハング・アップし、プロセスがアイドル状態になっているように見える場合は、最初の手順はスレッド・ダンプを取得してみることです。アプリケーション・コンソールを使用できる場合は、[Ctrl]+[\]キー(Oracle SolarisまたはLinuxの場合)または[Ctrl]+[Break]キー(Windowsの場合)を押して、HotSpot VMにスレッド・ダンプを出力させることができます。Oracle SolarisおよびLinuxオペレーティング・システムでは、SIGQUIT
をプロセスに送信(コマンドkill -QUIT
pid)してもスレッド・ダンプを取得できます。ハング・アップしたプロセスがスレッド・ダンプを収集できる場合、その出力はターゲット・プロセスの標準出力に出力されます。
スレッド・ダンプの出力後、HotSpot VMはデッドロック検出アルゴリズムを実行します。
次の項では、ハング・プロセスの様々な状況を説明します。
デッドロックが検出された場合は、デッドロックに関与したスレッドのスタック・トレースとともにそれが出力されます。例6-1は、この状況のスタック・トレースを示しています。
例6-1 デッドロックのスタック・トレース
Found one Java-level deadlock: ============================= "AWT-EventQueue-0": waiting to lock monitor 0x000ffbf8 (object 0xf0c30560, a java.awt.Component$AWTTreeLock), which is held by "main" "main": waiting to lock monitor 0x000ffe38 (object 0xf0c41ec8, a java.util.Vector), which is held by "AWT-EventQueue-0" Java stack information for the threads listed above: =================================================== "AWT-EventQueue-0": at java.awt.Container.removeNotify(Container.java:2503) - waiting to lock <0xf0c30560> (a java.awt.Component$AWTTreeLock) at java.awt.Window$1DisposeAction.run(Window.java:604) at java.awt.Window.doDispose(Window.java:617) at java.awt.Dialog.doDispose(Dialog.java:625) at java.awt.Window.dispose(Window.java:574) at java.awt.Window.disposeImpl(Window.java:584) at java.awt.Window$1DisposeAction.run(Window.java:598) - locked <0xf0c41ec8> (a java.util.Vector) at java.awt.Window.doDispose(Window.java:617) at java.awt.Window.dispose(Window.java:574) at javax.swing.SwingUtilities$SharedOwnerFrame.dispose(SwingUtilities.java:1743) at javax.swing.SwingUtilities$SharedOwnerFrame.windowClosed(SwingUtilities.java:1722) at java.awt.Window.processWindowEvent(Window.java:1173) at javax.swing.JDialog.processWindowEvent(JDialog.java:407) at java.awt.Window.processEvent(Window.java:1128) at java.awt.Component.dispatchEventImpl(Component.java:3922) at java.awt.Container.dispatchEventImpl(Container.java:2009) at java.awt.Window.dispatchEventImpl(Window.java:1746) at java.awt.Component.dispatchEvent(Component.java:3770) at java.awt.EventQueue.dispatchEvent(EventQueue.java:463) at java.awt.EventDispatchThread.pumpOneEventForHierarchy(EventDispatchThread.java:214) at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:163) at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:157) at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:149) at java.awt.EventDispatchThread.run(EventDispatchThread.java:110) "main": at java.awt.Window.getOwnedWindows(Window.java:844) - waiting to lock <0xf0c41ec8> (a java.util.Vector) at javax.swing.SwingUtilities$SharedOwnerFrame.installListeners(SwingUtilities.java:1697) at javax.swing.SwingUtilities$SharedOwnerFrame.addNotify(SwingUtilities.java:1690) at java.awt.Dialog.addNotify(Dialog.java:370) - locked <0xf0c30560> (a java.awt.Component$AWTTreeLock) at java.awt.Dialog.conditionalShow(Dialog.java:441) - locked <0xf0c30560> (a java.awt.Component$AWTTreeLock) at java.awt.Dialog.show(Dialog.java:499) at java.awt.Component.show(Component.java:1287) at java.awt.Component.setVisible(Component.java:1242) at test01.main(test01.java:10) Found 1 deadlock.
デフォルトのデッドロック検出は、synchronizedキーワードを使用して取得されたロック、およびjava.util.concurrent
パッケージを使用して取得されたロックとともに動作します。Java VMのフラグ-XX:+PrintConcurrentLocks
を設定すると、スタック・トレースにロックの所有者のリストも表示されます。
デッドロックが検出された場合は、デッドロックを理解するために出力を詳細に調べる必要があります。前述の例では、スレッドmain
がオブジェクト0xf0c30560
をロックし、スレッドAWT-EventQueue-0
によってロックされている0xf0c41ec8
に入るのを待機しています。しかし、スレッドAWT-EventQueue-0
はmain
によってロックされている0xf0c30560
に入るのを待機しています。
スタック・トレースの詳細から、デッドロックを見つけるのに役立つ情報が得られます。
スレッド・ダンプが出力されたが、デッドロックが見つからなかった場合、この問題は通知されないモニターでスレッドが待機しているというバグである可能性があります。これは、タイミングの問題か、一般的なロジックのバグである可能性があります。
この問題についてさらに調べるには、スレッド・ダンプ内の各スレッドと、Object.wait()
でブロックされた各スレッドを調べます。スタック・トレース内の呼出し側フレームは、wait()
メソッドを呼び出しているクラスとメソッドを示しています。行番号情報付きでコードがコンパイルされている場合(デフォルト)は、これによって調査すべきコードに関する手掛かりが提供されます。ほとんどの場合、この問題をより詳しく診断するには、アプリケーション・ロジックまたはライブラリに関してある程度の知識が必要です。一般に、アプリケーションでの同期動作、特にモニターがいつどこで通知されるかに関する詳細と条件について理解している必要があります。
VMが[Ctrl]+[\]または[Ctrl]+[Break]に応答しない場合は、他のなんらかの理由でVMがデッドロックまたはハングアップした可能性があります。その場合は、jstackユーティリティを使用してスレッド・ダンプを取得します。ハングアップしたプロセスのスタック・ダンプを強制するには、jstack -F
pidコマンドを使用します。これは、アプリケーションにアクセスできない場合や、出力が未知の場所に送信されている場合にも当てはまります。
jstack
の出力で、BLOCKED
状態になっている各スレッドを調べます。場合によっては、最上位フレームにスレッドがブロックされている理由(たとえば、Object.wait
やThread.sleep
)が示されていることがあります。スタックの残りの部分から、スレッドが実行している処理についてのヒントが得られます。これは、行番号情報付きでソースがコンパイルされている場合(デフォルト)に特に当てはまり、その場合はソース・コードを相互参照できます。
スレッドがBLOCKED
状態になっていて、理由がはっきりしない場合は、-m
オプションを使用して混合したスタックを取得します。混合したスタックの出力を使用して、スレッドがブロックされた理由を特定できるはずです。同期化メソッドまたはブロックに入ろうとしてスレッドがブロックされた場合は、スタックの最上位付近にObjectMonitor::enter
のようなフレームが表示されます。例6-2は、混合スタックのサンプル出力を示しています。
例6-2 スレッド・ブロック状態での混合スタック・トレース
----------------- t@13 ----------------- 0xff31e8b8 ___lwp_cond_wait + 0x4 0xfea8c810 void ObjectMonitor::EnterI(Thread*) + 0x2b8 0xfeac86b8 void ObjectMonitor::enter2(Thread*) + 0x250 :
RUNNABLE
状態のスレッドもブロックされる可能性があります。混合したスタックの最上位フレームに、スレッドが実行している処理が示されているはずです。
チェックすべき特定スレッドの1つはVMThread
です。これは、ガベージ・コレクション(GC)のような操作を実行するために使用される特殊なスレッドです。その初期のフレームでVMThread::run()
を実行しているスレッドとして識別できます。Oracle Solarisでは通常t@4
です。Linuxでは、C++分解名_ZN8VMThread4loopEv
を使用して識別できるはずです。
一般にVMスレッドは、VM操作の実行を待機している、VM操作に備えてすべてのスレッドを同期している、またはVM操作を実行している、の3つの状態のいずれかを取ります。ハング・アップがアプリケーションやクラス・ライブラリのデッドロックではなく、HotSpot VMのバグであると疑われる場合は、VMスレッドに特別な注意を払ってください。
VMスレッドがSafepointSynchronize::begin
でスタックしているように見える場合、これはVMがセーフポイントに移行する問題を示している可能性があります。セーフポイントは、VM内で実行されているすべてのスレッドがブロックされ、GCなどの特殊な操作が完了するのを待機していることを示します。
VMスレッドがfunction
でスタックしているように見えるが、function
がdoit
で終了する場合、これはVMに問題があることを示している可能性もあります。
一般に、コマンド行からアプリケーションを実行できるが、VMが[Ctrl]+[\]または[Ctrl]+[Break]に応答しない状態になる場合は、VMのバグ、スレッド・ライブラリの問題、または別のライブラリのバグを発見した可能性が高くなります。これが発生した場合は、クラッシュ・ダンプを取得してください。詳細について「コア・ダンプの収集」を参照して、できるだけ多くの情報を収集し、バグ・レポートまたはサポート・コールを提出してください。
ハングアップしたプロセスとの関連で説明すべきもう1つのツールは、Oracle Solarisオペレーティング・システムのpstack
ユーティリティです。Oracle Solaris 8および9オペレーティング・システムでは、このユーティリティはターゲット・プロセス内のLWPのスレッド・スタックを出力します。Oracle Solaris 10オペレーティング・システムおよびJDK 5.0リリース以降では、pstack
の出力はjstack -m
の出力に似ています(ただし、同じではありません)。jstack
と同様に、Oracle Solaris 10オペレーティング・システムのpstack
の実装では、完全修飾クラス名、メソッド名およびバイトコード・インデックス(BCI)が出力されます。また、行番号情報付きでソースがコンパイルされた場合(デフォルト)は、行番号も出力します。これは、/proc
ファイル・システムの機能を実行するOracle Solarisオペレーティング・システム上の他のユーティリティに習熟している開発者や管理者にとって便利です。
Linuxには、pstack
と同等のツールとしてlsstack
があります。このユーティリティは、一部のディストリビューションに含まれており、そうでない場合はsourceforgeから取得します。これを執筆している時点では、lsstack
はネイティブ・フレームのみを報告していました。