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
ユーティリティまたはjhsdb jstack
ユーティリティを使用してスタック・スレッドを取得できます。これらのユーティリティの出力の詳細は、「jstackユーティリティ」またはjhsdbのjstack
モードに関する項を参照してください。Javaスレッドがループしている証拠がスレッド・ダンプに示されない場合も、jstack
ユーティリティまたはjhsdb jstack
ユーティリティを使用するようにしてください。
jstack
ユーティリティの出力を確認するときは、まずRUNNABLE
状態のスレッドに注目してください。これは、ビジー状態でループしていると思われるスレッドが取る可能性がもっとも高い状態です。場合によっては、どのスレッドがループしているかを見極めるために、jstack
を数回にわたって実行する必要があります。スレッドが常にRUNNABLE
状態であるように見える場合は、--mixed
オプションを指定したjhsdb jstack
を使用してネイティブ・フレームを出力し、スレッドが実行している処理について詳細なヒントを得ることができます。スレッドがRUNNABLE
状態のままで継続的にループしているように見える場合、この状況はより詳しい調査を必要とする潜在的HotSpot VMバグを示している可能性があります。
VMが[Ctrl]+[\]に応答しない場合、これはアプリケーション・コードやライブラリ・コードの問題ではなく、VMのバグを示している可能性があります。この場合は、--mixed
オプションを指定したjhsdb 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-0
はmain
によってロックされている0xf0c30560
に入るのを待機しています。
スタック・トレースの詳細から、デッドロックを見つけるのに役立つ情報が得られます。
デッドロックが検出されなかった
スレッド・ダンプが出力されたが、デッドロックが見つからなかった場合、この問題は通知されないモニターをスレッドが待機しているというバグである可能性があります。これは、タイミングの問題か、一般的なロジックのバグである可能性があります。
この問題についてさらに調べるには、スレッド・ダンプ内の各スレッドと、Object.wait()
でブロックされた各スレッドを調べます。スタック・トレース内の呼出し側フレームは、wait()
メソッドを呼び出しているクラスとメソッドを示しています。行番号情報付きでコードがコンパイルされている場合(デフォルト)は、これによって調査する必要があるコードに関する手掛かりが得られます。ほとんどの場合、この問題をより詳しく診断するには、アプリケーション・ロジックまたはライブラリに関してある程度の知識が必要です。一般に、アプリケーションでの同期動作、およびモニターがいつどこで通知されるかに関する詳細と条件について理解している必要があります。
スレッド・ダンプがない
VMでデッドロックが発生したかハングした場合は、jstack
またはjhsdb jstack
コマンドを使用します。
VMが[Ctrl]+[\]または[Ctrl]+[Break]に応答しない場合は、他のなんらかの理由でVMがデッドロックまたはハングアップした可能性があります。その場合は、jstackユーティリティまたはjhsdb jstackを使用してスレッド・ダンプを取得します。これは、アプリケーションにアクセスできない場合や、出力が未知の場所に送信されている場合にも当てはまります。
スレッド・ダンプで、BLOCKED
状態になっている各スレッドを調べます。場合によっては、最上位フレームにスレッドがブロックされている理由(たとえば、Object.wait
やThread.sleep
)が示されていることがあります。スタックの残りの部分から、スレッドが実行している処理についてのヒントが得られます。これは、行番号情報付きでソースがコンパイルされている場合(デフォルト)に特に当てはまり、その場合はソース・コードを相互参照できます。
スレッドがBLOCKED
状態になっていて、理由がはっきりしない場合は、jhsdb jstack --mixed
オプションを使用して混合したスタックを取得します。混合したスタックの出力を使用して、スレッドがブロックされた理由を特定できるはずです。同期化メソッドまたはブロックに入ろうとしてスレッドがブロックされた場合は、スタックの最上位付近に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
でスタックしているように見えるが、function
がdoit
で終了する場合、これはVMに問題があることを示している可能性もあります。
一般に、コマンド行からアプリケーションを実行できるが、VMが[Ctrl]+[\]または[Ctrl]+[Break]に応答しない状態になる場合は、VMのバグ、スレッド・ライブラリの問題、または別のライブラリのバグを発見した可能性が高くなります。これが発生した場合は、クラッシュ・ダンプを取得してください。できるだけ多くの情報を収集する手順について「コア・ダンプの収集」を参照して、バグ・レポートを提出するか、サポート部門に連絡してください。
ハングアップしたプロセスとの関連で説明する必要があるもう1つのツールは、Oracle Solarisオペレーティング・システムのpstack
ユーティリティです。Oracle Solaris 8および9オペレーティング・システムでは、このユーティリティはターゲット・プロセス内のLWPのスレッド・スタックを出力します。Oracle Solaris 10オペレーティング・システムおよびJDK 5.0リリース以降では、pstack
の出力はjhsdb jstack --mixed
の出力に似ています(ただし、同じではありません)。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
コマンドを発行して、指定したプロセスによってロードされるライブラリをリストしてください。