Java Platform, Standard Editionトラブルシューティング・ガイド
目次      

6.2 ハング・プロセスの診断

アプリケーションがハング・アップし、プロセスがアイドル状態になっているように見える場合は、最初の手順はスレッド・ダンプを取得してみることです。アプリケーション・コンソールを使用できる場合は、[Ctrl]+[\]キー(Oracle SolarisまたはLinuxの場合)または[Ctrl]+[Break]キー(Windowsの場合)を押して、HotSpot VMにスレッド・ダンプを出力させることができます。Oracle SolarisおよびLinuxオペレーティング・システムでは、SIGQUITをプロセスに送信(コマンドkill -QUIT pid)してもスレッド・ダンプを取得できます。ハング・アップしたプロセスがスレッド・ダンプを収集できる場合、その出力はターゲット・プロセスの標準出力に出力されます。

スレッド・ダンプの出力後、HotSpot VMはデッドロック検出アルゴリズムを実行します。

次の項では、ハング・プロセスの様々な状況を説明します。

6.2.1 デッドロック検出

デッドロックが検出された場合は、デッドロックに関与したスレッドのスタック・トレースとともにそれが出力されます。例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-0mainによってロックされている0xf0c30560に入るのを待機しています。

スタック・トレースの詳細から、デッドロックを見つけるのに役立つ情報が得られます。

6.2.2 デッドロックが検出されていない

スレッド・ダンプが出力されたが、デッドロックが見つからなかった場合、この問題は通知されないモニターでスレッドが待機しているというバグである可能性があります。これは、タイミングの問題か、一般的なロジックのバグである可能性があります。

この問題についてさらに調べるには、スレッド・ダンプ内の各スレッドと、Object.wait()でブロックされた各スレッドを調べます。スタック・トレース内の呼出し側フレームは、wait()メソッドを呼び出しているクラスとメソッドを示しています。行番号情報付きでコードがコンパイルされている場合(デフォルト)は、これによって調査すべきコードに関する手掛かりが提供されます。ほとんどの場合、この問題をより詳しく診断するには、アプリケーション・ロジックまたはライブラリに関してある程度の知識が必要です。一般に、アプリケーションでの同期動作、特にモニターがいつどこで通知されるかに関する詳細と条件について理解している必要があります。

6.2.3 スレッド・ダンプがない

VMが[Ctrl]+[\]または[Ctrl]+[Break]に応答しない場合は、他のなんらかの理由でVMがデッドロックまたはハングアップした可能性があります。その場合は、jstackユーティリティを使用してスレッド・ダンプを取得します。ハングアップしたプロセスのスタック・ダンプを強制するには、jstack -F pidコマンドを使用します。これは、アプリケーションにアクセスできない場合や、出力が未知の場所に送信されている場合にも当てはまります。

jstackの出力で、BLOCKED状態になっている各スレッドを調べます。場合によっては、最上位フレームにスレッドがブロックされている理由(たとえば、Object.waitThread.sleep)が示されていることがあります。スタックの残りの部分から、スレッドが実行している処理についてのヒントが得られます。これは、行番号情報付きでソースがコンパイルされている場合(デフォルト)に特に当てはまり、その場合はソース・コードを相互参照できます。

スレッドがBLOCKED状態になっていて、理由がはっきりしない場合は、-mオプションを使用して混合したスタックを取得します。混合したスタックの出力を使用して、スレッドがブロックされた理由を特定できるはずです。同期化メソッドまたはブロックに入ろうとしてスレッドがブロックされた場合は、スタックの最上位付近にObjectMonitor::enterのようなフレームが表示されます。例6-2は、混合スタックのサンプル出力を示しています。

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でスタックしているように見えるが、functiondoitで終了する場合、これは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はネイティブ・フレームのみを報告していました。

目次      

Copyright © 1993, 2020, Oracle and/or its affiliates. All rights reserved.