プライマリ・コンテンツに移動
Java Platform, Standard Editionトラブルシューティング・ガイド
リリース9
E90916-02
目次へ移動
目次

前
次

6 プロセスのハングおよびループのトラブルシューティング

この章では、ハング・アップまたはループしているプロセスをトラブルシューティングする具体的な手順に関する情報と指針を説明します。

ハング・アップまたはループしているプロセスが関与する問題が発生することがあります。ハング・アップは様々な理由で発生する可能性がありますが、多くの場合、アプリケーション・コード、APIコード、またはライブラリ・コード内のデッドロックが原因です。Java HotSpot VMのバグが原因でハングアップが発生することもあります。

ハング・アップのように見えて、実はループであることが判明する場合もあります。たとえば、VMプロセス内のバグによって1つ以上のスレッドが無限ループに入ると、使用可能なCPUサイクルがすべて消費される可能性があります。

ハングアップを診断する最初の手順は、VMプロセスがアイドル状態なのか、それとも使用可能なCPUサイクルをすべて消費しているかを調べることです。これはオペレーティング・システム(OS)のネイティブ・ユーティリティを使用して行うことができます。プロセスがビジー状態で、使用可能なCPUサイクルをすべて消費しているように見える場合は、デッドロックではなくループしているスレッドが問題である可能性が高いです。たとえばOracle Solarisオペレーティング・システムでは、prstat -L -p pidコマンドを使用してターゲット・プロセスのすべての軽量プロセス(LWP)の統計を表示して、CPUサイクルを大量に消費しているスレッドを特定できます。

この章の構成は、次のとおりです。

ループ処理の診断

VMプロセスがループしているように見える場合、最初の手順はスレッド・ダンプを取得しようとすることです。スレッド・ダンプを取得できれば、多くの場合、どのスレッドがループしているかがはっきりします。ループしているスレッドを識別できたら、スレッド・ダンプ内のトレース・スタックから、そのスレッドがループしている場所(および場合によっては理由)に関する手掛かりを得ることができます。

アプリケーション・コンソール(標準入力/出力)を使用できる場合は、[Ctrl]+[\]キーの組合せ(Oracle SolarisまたはLinuxの場合)または[Ctrl]+[Break]キーの組合せ(Windowsの場合)を押して、HotSpot VMにスレッドの状態を含むスレッド・ダンプを出力させることができます。Oracle SolarisおよびLinuxオペレーティング・システムでは、(kill -QUIT pidコマンドで)SIGQUITをプロセスに送信して、スレッド・ダンプを取得することもできます。この場合、スレッド・ダンプはターゲット・プロセスの標準出力に出力されます。プロセスの起動方法によっては、出力をファイルに送信することもできます。

-XX:+PrintClassHistogramコマンド行オプションを指定してJavaプロセスを起動すると、Ctrl+Breakハンドラによってヒープ・ヒストグラムが生成されます。

スレッド・ダンプを取得できる場合は、RUNNABLE状態のスレッドのスレッド・スタックから始めるのが適切です。スレッド・ダンプの形式、およびスレッド・ダンプ内で見られるスレッド状態の表の詳細は、「スレッド・ダンプ」を参照してください。場合によっては、どのスレッドが継続的にビジーに見えるかを特定するため、スレッド・ダンプを連続して取得する必要がある場合があります。

アプリケーション・コンソールを使用できない(プロセスがバックグラウンドで実行されていたり、VMの出力が未知の場所に送信されていたりするなど)場合は、jstackユーティリティを使用してスタック・スレッドを取得できます。このユーティリティの出力の詳細は、「jstackユーティリティ」を参照してください。Javaスレッドがループしている証拠がスレッド・ダンプに示されない場合も、jstackユーティリティを使用するようにしてください。

jstackユーティリティの出力を確認するときは、まずRUNNABLE状態のスレッドに注目してください。これは、ビジー状態でループしていると思われるスレッドが取る可能性がもっとも高い状態です。場合によっては、どのスレッドがループしているかを見極めるために、jstackを数回にわたって実行する必要があります。スレッドが常にRUNNABLE状態であるように見える場合は、-mオプションを使用してネイティブ・フレームを出力し、スレッドが実行している処理について詳細なヒントを得ることができます。スレッドがRUNNABLE状態のままで継続的にループしているように見える場合、この状況はより詳しい調査を必要とする潜在的HotSpot VMバグを示している可能性があります。

VMが[Ctrl]+[\]に応答しない場合、これはアプリケーション・コードやライブラリ・コードの問題ではなく、VMのバグを示している可能性があります。この場合は、-mオプションを指定したjstackを使用して、すべてのスレッドのスレッド・スタックを取得します。この出力には、VM内部のスレッドのスレッド・スタックが含まれます。このスタック・トレースで、待機しているように見えないスレッドを識別します。たとえばOracle Solarisオペレーティング・システムでは、__lwp_cond_wait__lwp_park___pollsysなどの関数、または他のブロック関数に入っていないスレッドを識別します。VMのバグによってループが発生しているように見える場合は、できるだけ多くのデータを収集して、バグ・レポートを提出してください。データ収集の詳細は、「バグ・レポートの提出」を参照してください。

ハング・プロセスの診断

スレッド・ダンプを使用して、ハングアップしたプロセスを診断します。

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

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

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

デッドロックが検出された

デッドロックが検出された場合は、デッドロックに関与したスレッドのスタック・トレースとともにそれが出力されます。

次の例は、この状況のスタック・トレースを示しています。

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に入るのを待機しています。

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

デッドロックが検出されなかった

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

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

スレッド・ダンプがない

VMでデッドロックが発生したかハングした場合は、jstackコマンドを使用します。

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

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

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

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

Oracle Solaris 8スレッド・ライブラリ

Oracle Solaris 8オペレーティング・システム上のデフォルトのスレッド・ライブラリは多くの場合、T1ライブラリと呼ばれます。このスレッド・ライブラリはm:nスレッド・モデルを実装しました(m個のユーザー・スレッドがn個のカーネル・レベル・スレッド(LWP)にマップされます)。Oracle Solaris 8オペレーティング・システムには、代替の新しいスレッド・ライブラリも付属しており、/usr/lib/lwpにあります。代替スレッド・ライブラリは多くの場合、T2ライブラリと呼ばれ、Oracle Solaris 9および10オペレーティング・システムではデフォルトのスレッド・ライブラリになりました。古いリリース(特に1.4.0より前)のJ2SEでは、デフォルトのスレッド・ライブラリに関する問題(スレッド・ライブラリ内のバグ、LWPの同期化問題、LWPの枯渇など)がいくつかありました。LWPの枯渇は、RUNNABLE状態のユーザー・スレッドはあるが、使用可能なカーネル・レベル・スレッドがないというシナリオです。

前述の問題は過去のものですが、Oracle Solaris 8オペレーティング・システム上にJDKソフトウェアを配備すると、今でもデフォルトでT1ライブラリが使用されることに注意してください。JDKリリースでは「バインドされたスレッド」が使用され、ユーザー・スレッドがそれぞれ1つのカーネル・スレッドにバインドされるため、LWP枯渇タイプの問題は発生しません。しかし、スレッド・ライブラリの問題だと思われる問題(ハングアップなど)が発生した場合は、LD_LIBRARY_PATHに/usr/lib/lwpを追加することで、T2ライブラリを使用するようにHotSpot VMに指示できます。T2ライブラリが使用されているかどうかをチェックするには、pldd pidコマンドを発行して、指定したプロセスによってロードされるライブラリをリストしてください。