この章では、システムクラッシュをトラブルシューティングする具体的ないくつかの手順に関する情報と指針を提供します。
クラッシュ (致命的エラー) が発生すると、プロセスが異常終了します。クラッシュにはさまざまな理由が考えられます。たとえば、HotSpot VM 内、システムライブラリ内、Java SE のライブラリまたは API 内、アプリケーションネイティブコード内、さらにはオペレーティングシステム内のバグによってクラッシュが発生する可能性があります。オペレーティングシステムのリソース不足などの外部要因によってクラッシュが発生することもあります。
HotSpot VM または Java SE ライブラリコードのバグによってクラッシュが発生することはほとんどありません。この章では、クラッシュを調べる方法についてアドバイスを行います。場合によっては、バグの原因を診断して修正するまでクラッシュを回避することも可能です。
一般にどのようなクラッシュでも、最初の手順は致命的エラーログを見つけることです。これは、クラッシュの発生時に HotSpot VM によって生成されるテキストファイルです。このファイルを見つける方法とファイルの詳細な説明については、付録 C「致命的エラーログ」を参照してください。
このセクションでは、エラーログを使用してクラッシュの原因を示唆する方法を示す例をいくつか示します。
エラーログのヘッダーには、問題のあるフレームが示されます。「C.3 ヘッダー形式」を参照してください。
最上位フレームのタイプがネイティブフレームであり、オペレーティングシステムのものではない場合、これは Java 仮想マシンではなくそのネイティブライブラリに問題がある可能性が高いことを示します。このクラッシュを解決する最初の手順は、クラッシュが発生したネイティブライブラリのソースを調べることです。ネイティブライブラリのソースに応じて、3 つのオプションがあります。
ネイティブライブラリがアプリケーションによって提供されている場合は、ネイティブライブラリのソースコードを調べます。オプション -Xcheck:jni を使用して多くのネイティブバグを見つけることができます。「B.2.1 -Xcheck:jni オプション」を参照してください。
ネイティブライブラリが別のベンダーから提供され、アプリケーションで使用されている場合は、このサードパーティーアプリケーションに対するバグレポートを提出し、致命的エラーログの情報を提供します。
Java 実行環境 (JRE) ディストリビューションの jre/lib または jre/bin ディレクトリを調べて、ネイティブライブラリが JRE の一部であるかどうかを確認します。そうである場合は、バグレポートを提出し、このライブラリ名を目立つように示して、バグレポートが適切な開発者に転送できるようにします。
エラーログに示された最上位フレームが別のタイプのフレームである場合は、バグレポートを提出し、致命的エラーログとともに、問題の再現方法に関する情報を含めます。
この章の残りのセクションも参照してください。
クラッシュがネイティブライブラリ内であったことが致命的エラーログに示されている場合は、ネイティブコードまたは JNI ライブラリコード内のバグである可能性があります。クラッシュがほかのなにかで発生した可能性ももちろんありますが、最初はライブラリとコアファイルまたはクラッシュダンプを分析するのが適切です。たとえば、致命的エラーログのヘッダーから抜粋した次の部分について考えます。
# An unexpected error has been detected by HotSpot Virtual Machine: # # SIGSEGV (0xb) at pc=0x417789d7, pid=21139, tid=1024 # # Java VM: Java HotSpot(TM) Server VM (6-beta2-b63 mixed mode) # Problematic frame: # C [libApplication.so+0x9d7]
この場合は、ライブラリ libApplication.so 内で実行されているスレッドで SIGSEGV が発生しました。
場合によっては、ネイティブライブラリ内のバグが Java VM コード内のクラッシュとして表れます。JavaThread が _thread_in_vm 状態の間 (つまり、Java VM コード内で実行されている間) に失敗する、次のクラッシュについて考えます。
# An unexpected error has been detected by HotSpot Virtual Machine: # # EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x08083d77, pid=3700, tid=2896 # # Java VM: Java HotSpot(TM) Client VM (1.5-internal mixed mode) # Problematic frame: # V [jvm.dll+0x83d77] --------------- T H R E A D --------------- Current thread (0x00036960): JavaThread "main" [_thread_in_vm, id=2896] : Stack: [0x00040000,0x00080000), sp=0x0007f9f8, free space=254k Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code) V [jvm.dll+0x83d77] C [App.dll+0x1047] <========= C/native frame j Test.foo()V+0 j Test.main([Ljava/lang/String;)V+0 v ~StubRoutines::call_stub V [jvm.dll+0x80f13] V [jvm.dll+0xd3842] V [jvm.dll+0x80de4] V [jvm.dll+0x87cd2] C [java.exe+0x14c0] C [java.exe+0x64cd] C [kernel32.dll+0x214c7] :
この場合、スタックトレースは App.dll 内のネイティブルーチンが (おそらく JNI によって) VM を呼び出したことを示しています。
(上記の例のように) ネイティブアプリケーションライブラリ内でクラッシュが発生した場合は、ネイティブデバッガを (利用可能であれば) コアファイルまたはクラッシュダンプに接続できる可能性があります。ネイティブデバッガは、オペレーティングシステムに応じて dbx、gdb、または windbg です。
もう 1 つの方法は、コマンド行に -Xcheck:jni オプションを指定して実行することです (「B.2.1 -Xcheck:jni オプション」を参照)。このオプションを使用して JNI コードの問題をすべて検出できる保証はありませんが、かなりの数の問題を識別できる可能性があります。
クラッシュが発生したネイティブライブラリが Java 実行環境の一部 (たとえば、awt.dll や net.dll など) である場合は、ライブラリまたは API のバグが発生した可能性があります。詳細な分析の結果、これがライブラリまたは API のバグであると判断した場合は、できるだけ多くのデータを収集して、バグコールまたはサポートコールを提出します。第 7 章「バグレポートの提出」を参照してください。
Java 言語コード内でスタックオーバーフローが発生すると、通常は違反したスレッドが java.lang.StackOverflowError をスローします。一方、C および C++ はスタックの終わりを越えて書き込みを行い、スタックオーバーフローを引き起こします。これは、プロセスが終了する致命的エラーです。
HotSpot 実装では、Java メソッドがスタックフレームを C/C++ ネイティブコード (つまり、ユーザーネイティブコードおよび仮想マシン自体) と共有します。Java メソッドは、スタック空間を越えることなくネイティブコードを呼び出せるように、スタックの終わりに向かって一定距離のスタック空間が使用可能であることをチェックするコードを生成します。このスタックの終わりまでの距離を "シャドウページ" と呼びます。シャドウページのサイズは、プラットフォームに応じて 3-20 ページです。この距離は調整可能であるため、デフォルトより長い距離を必要とするネイティブコードを含むアプリケーションは、シャドウページのサイズを増やすことができます。シャドウページを増やすためのオプションは -XX:StackShadowPages=n です (n はプラットフォームのデフォルトのシャドウページより大きくします)。
アプリケーションでセグメンテーション障害が発生したときにコアファイルまたは致命的エラーログファイル (付録 C「致命的エラーログ」を参照) が生成されなかったか、Windows 上で STACK_OVERFLOW_ERROR が発生したか、または "An irrecoverable stack overflow has occurred" (回復不能なスタックオーバーフローが発生しました) というメッセージが表示された場合、これは StackShadowPages の値を超過したため、より多くの空間が必要であることを示します。
StackShadowPages の値を増やす場合は、-Xss パラメータを使用してデフォルトのスレッドスタックサイズも増やさなければいけないことがあります。デフォルトのスレッドスタックサイズを増やすと、作成できるスレッドの数が減る可能性があるため、スレッドスタックサイズの値は慎重に選択してください。スレッドスタックサイズは、プラットフォームによって 256k から 1024k までさまざまです。
ネイティブコード内でスレッドがスタックオーバーフローを引き起こした Windows システムの致命的エラーログのフラグメントを次に示します。
# An unexpected error has been detected by HotSpot Virtual Machine: # # EXCEPTION_STACK_OVERFLOW (0xc00000fd) at pc=0x10001011, pid=296, tid=2940 # # Java VM: Java HotSpot(TM) Client VM (1.6-internal mixed mode, sharing) # Problematic frame: # C [App.dll+0x1011] # --------------- T H R E A D --------------- Current thread (0x000367c0): JavaThread "main" [_thread_in_native, id=2940] : Stack: [0x00040000,0x00080000), sp=0x00041000, free space=4k Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code) C [App.dll+0x1011] C [App.dll+0x1020] C [App.dll+0x1020] : C [App.dll+0x1020] C [App.dll+0x1020] ...<more frames>... Java frames: (J=compiled Java code, j=interpreted, Vv=VM code) j Test.foo()V+0 j Test.main([Ljava/lang/String;)V+0 v ~StubRoutines::call_stub
上記の出力に含まれる次の情報に注目してください。
例外は EXCEPTION_STACK_OVERFLOW です。
スレッドの状態は _thread_in_native で、スレッドがネイティブコードまたは JNI コードを実行していることを意味します。
スタック情報では、空き領域は 4k (Windows システムの単一ページ) のみです。さらに、スタックポインタ (sp) はスタックの終わり (0x00040000) に近い 0x00041000 です。
ネイティブフレームの出力は、再帰的なネイティブ関数がこの場合の問題であることを示しています。
出力の ...
致命的エラーログの出力に、Current thread が CompilerThread0、CompilerThread1、または AdapterCompiler という名前の JavaThread であることが示された場合は、コンパイラのバグが発生した可能性があります。この場合は、コンパイラを切り替える (たとえば、HotSpot Server VM の代わりに HotSpot Client VM を使用する、またはその逆) か、クラッシュを引き起こしたメソッドをコンパイルから除外することによって、問題を一時的に回避しなければいけないことがあります。これについては、「4.2.1 HotSpot コンパイラスレッドまたはコンパイルされたコードでのクラッシュ」で説明します。
コンパイルされたコードでクラッシュが発生した場合は、不正なコードを生成するコンパイラバグが発生した可能性があります。問題のあるフレームがコード J (コンパイルされた Java フレームを意味する) でマークされている場合は、コンパイルされたコードでクラッシュが発生したと認識できます。このようなクラッシュの例を以下に示します。
# An unexpected error has been detected by HotSpot Virtual Machine: # # SIGSEGV (0xb) at pc=0x0000002a99eb0c10, pid=6106, tid=278546 # # Java VM: Java HotSpot(TM) 64-Bit Server VM (1.6.0-beta-b51 mixed mode) # Problematic frame: # J org.foobar.Scanner.body()V # : Stack: [0x0000002aea560000,0x0000002aea660000), sp=0x0000002aea65ddf0, free space=1015k Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code) J org.foobar.Scanner.body()V [error occurred during error reporting, step 120, id 0xb]
完全なスレッドスタックは入手できません。出力行 "error occurred during error reporting" (エラーレポート中にエラーが発生しました) は、スタックトレースを取得しようとして問題 (この例ではおそらくスタックの破壊) が発生したことを意味します。
コンパイラを切り替える (たとえば、HotSpot Server VM の代わりに HotSpot Client VM を使用する、またはその逆) か、クラッシュを引き起こしたメソッドをコンパイルから除外することによって、問題を一時的に回避できる可能性があります。この具体例ではコンパイラを切り替えられない可能性があります (これは 64 ビットのサーバー VM から取ったものであり、32 ビットのクライアント VM への切り替えはできない可能性がある)。
致命的エラーログの出力に、Current thread が VMThread であることが示されている場合は、THREAD セクションで VM_Operation を含む行を探してください。VMThread は、HotSpot VM の特別なスレッドです。VM 内でガベージコレクション (GC) などの特殊なタスクを実行します。VM_Operation によって、操作がガベージコレクションであることが示された場合は、ヒープの破壊などの問題が発生した可能性があります。
クラッシュが GC の問題である可能性もありますが、ほかの問題 (コンパイラのバグやランタイムのバグなど) のためにヒープ内のオブジェクト参照が不整合または不正な状態で残った可能性も同じ程度にあります。この場合は、環境に関する情報をできるだけ多く収集し、可能な回避策を試してください。問題が GC に関連する場合は、GC の構成を変更することによって問題を一時的に回避できる場合があります。これについては、「4.2.2 ガベージコレクション中のクラッシュ」で説明します。
重要なアプリケーションでクラッシュが発生し、HotSpot VM 内のバグによってクラッシュが発生したように見える場合は、一時的な回避策をすばやく見つけることが望ましい場合があります。このセクションの目的は、可能な回避策をいくつか示すことです。JDK の最新リリースとともに配備されたアプリケーションでクラッシュが発生した場合は、必ず Oracle にクラッシュを報告するようにしてください。
注 - このセクションの回避策によってクラッシュが正常に除去されても、その回避策は問題の修正ではなく、一時的な解決方法でしかありません。問題が見つかった元の構成で、サポートコールまたはバグレポートを提出してください。
コンパイラスレッドでクラッシュが発生したことが致命的エラーログに示された場合は、コンパイラのバグが発生した可能性があります (ただし、常にそうであるとは限りません)。同様に、コンパイルされたコードでクラッシュが発生した場合は、コンパイラが不正なコードを生成した可能性があります。
HotSpot Client VM (-client オプション) の場合は、コンパイラスレッドが CompilerThread0 としてエラーログに表示されます。HotSpot Server VM の場合は、複数のコンパイラスレッドがあり、それらが CompilerThread0、CompilerThread1、および AdapterThread としてエラーログファイルに表示されます。
J2SE 5.0 の開発中に発生し、修正されたコンパイラバグのエラーログのフラグメントを以下に示します。このログファイルは、HotSpot Server VM が使用され、CompilerThread1 でクラッシュが発生したことを示しています。このログファイルはさらに、Current CompileTask が java.lang.Thread.setPriority メソッドのコンパイルだったことを示しています。
# An unexpected error has been detected by HotSpot Virtual Machine: # : # Java VM: Java HotSpot(TM) Server VM (1.5-internal-debug mixed mode) : --------------- T H R E A D --------------- Current thread (0x001e9350): JavaThread "CompilerThread1" daemon [_thread_in_vm, id=20] Stack: [0xb2500000,0xb2580000), sp=0xb257e500, free space=505k Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code) V [libjvm.so+0xc3b13c] : Current CompileTask: opto: 11 java.lang.Thread.setPriority(I)V (53 bytes) --------------- P R O C E S S --------------- Java Threads: ( => current thread ) 0x00229930 JavaThread "Low Memory Detector" daemon [_thread_blocked, id=21] =>0x001e9350 JavaThread "CompilerThread1" daemon [_thread_in_vm, id=20] :
この場合は、2 つの可能な回避策があります。
強引な方法: HotSpot Client VM を指定するため、アプリケーションが -client オプションで実行されるように構成を変更します。
setPriority メソッドのコンパイル中にのみバグが発生するとみなして、このメソッドをコンパイルから除外します。
最初の (-client オプションを使用する) 方法は、一部の環境では容易に構成できる可能性があります。それ以外で、構成が複雑な場合や、VM を構成するコマンド行に容易にアクセスできない場合は、より困難になる可能性があります。一般に、HotSpot Server VM から HotSpot Client VM に切り替えることで、アプリケーションのピークパフォーマンスも低下します。環境によっては、実際の問題の診断と修正が完了するまで、これを容認できる場合があります。
2 つ目の (メソッドをコンパイルから除外する) 方法では、アプリケーションの作業ディレクトリ内に .hotspot_compiler ファイルを作成する必要があります。このファイルの例を以下に示します。
exclude java/lang/Thread setPriority
一般に、このファイルの形式は exclude CLASS METHOD です (CLASS は (パッケージ名で完全指定された) クラス、METHOD はメソッドの名前です)。コンストラクタメソッドは
注 - .hotspot_compiler ファイルは、サポートされていないインタフェースです。ここでは、トラブルシューティングと一時的な回避策の特定のみを目的として記載しています。
アプリケーションが再起動すると、コンパイラは .hotspot_compiler ファイルに除外対象として指定されたメソッドをコンパイルしようとしません。場合によっては、クラッシュの根本原因を診断してバグを修正するまで、これが一時的な救済策になる可能性があります。
HotSpot VM が上の例に示した .hotspot_compiler ファイルを正しく検出して処理したことを確認するには、実行時に次のログ情報を探してください。ファイル名の区切り文字はスラッシュではなくドットです。
### Excluding compile: java.lang.Thread::setPriority
ガベージコレクション (GC) 中にクラッシュが発生した場合は、致命的エラーログに、VM_Operation が進行中であることが報告されます。ここでは説明のため、ほぼ同時に処理される GC (-XX:+UseConcMarkSweep) は使用されないことを前提としてください。ログの THREAD セクションには VM_Operation が表示され、次のいずれかの状況を示します。
割り当て用の世代コレクション
全世代のコレクション
パラレル GC の割り当ての失敗
パラレル GC の永続的な割り当ての失敗
パラレル GC のシステム GC
ほとんどの場合、ログで報告される現在のスレッドは VMThread です。これは、HotSpot VM で特殊なタスクを実行するために使用される特別なスレッドです。次の致命的エラーログのフラグメントは、シリアルガベージコレクタでのクラッシュの例を示しています。
--------------- T H R E A D --------------- Current thread (0x002cb720): VMThread [id=3252] siginfo: ExceptionCode=0xc0000005, reading address 0x00000000 Registers: EAX=0x0000000a, EBX=0x00000001, ECX=0x00289530, EDX=0x00000000 ESP=0x02aefc2c, EBP=0x02aefc44, ESI=0x00289530, EDI=0x00289530 EIP=0x0806d17a, EFLAGS=0x00010246 Top of Stack: (sp=0x02aefc2c) 0x02aefc2c: 00289530 081641e8 00000001 0806e4b8 0x02aefc3c: 00000001 00000000 02aefc9c 0806e4c5 0x02aefc4c: 081641e8 081641c8 00000001 00289530 0x02aefc5c: 00000000 00000000 00000001 00000001 0x02aefc6c: 00000000 00000000 00000000 08072a9e 0x02aefc7c: 00000000 00000000 00000000 00035378 0x02aefc8c: 00035378 00280d88 00280d88 147fee00 0x02aefc9c: 02aefce8 0806e0f5 00000001 00289530 Instructions: (pc=0x0806d17a) 0x0806d16a: 15 08 83 3d c0 be 15 08 05 53 56 57 8b f1 75 0f 0x0806d17a: 0f be 05 00 00 00 00 83 c0 05 a3 c0 be 15 08 8b Stack: [0x02ab0000,0x02af0000), sp=0x02aefc2c, free space=255k Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code) V [jvm.dll+0x6d17a] V [jvm.dll+0x6e4c5] V [jvm.dll+0x6e0f5] V [jvm.dll+0x71771] V [jvm.dll+0xfd1d3] V [jvm.dll+0x6cd99] V [jvm.dll+0x504bf] V [jvm.dll+0x6cf4b] V [jvm.dll+0x1175d5] V [jvm.dll+0x1170a0] V [jvm.dll+0x11728f] V [jvm.dll+0x116fd5] C [MSVCRT.dll+0x27fb8] C [kernel32.dll+0x1d33b] VM_Operation (0x0373f71c): generation collection for allocation, mode: safepoint, requested by thread 0x02db7108
注 - ガベージコレクション中のクラッシュがガベージコレクション実装内のバグを示すとはかぎりません。コンパイラやランタイムのバグ、またはその他の問題を示す場合もあります。
ガベージコレクション中にクラッシュが繰り返し発生する場合は、次の回避策を試すことができます。
GC の構成を切り替えます。たとえば、シリアルコレクタを使用している場合はスループットコレクタを試します (またはその逆)。
HotSpot Server VM を使用している場合は、HotSpot Client VM を試します。
どのガベージコレクタを使用しているかわらない場合は、Solaris OS および Linux 上で jmap ユーティリティー (「2.7 jmap ユーティリティー」を参照) を使用して、コアファイルからヒープ情報を取得できます (コアファイルが使用可能な場合)。一般に、GC 構成がコマンド行で指定されていない場合、Windows ではシリアルコレクタが使用されます。Solaris OS および Linux では、マシンの構成によって異なります。マシンに 2G バイト以上のメモリーと 2 個以上のプロセッサが搭載されている場合は、スループットコレクタ (パラレル GC) が使用されます。小規模なマシンでは、シリアルコレクタがデフォルトです。シリアルコレクタを選択するためのオプションは -XX:+UseSerialGC で、スループットコレクタを選択するためのオプションは -XX:+UseParallelGC です。回避策としてスループットコレクタからシリアルコレクタに切り替えた場合、マルチプロセッサシステムではパフォーマンスがある程度低下する可能性があります。根本的な問題の診断と解決が完了するまで、これを容認できる場合があります。
クラスデータ共有は、J2SE 5.0 の新機能でした。Sun の提供するインストーラを使用して 32 ビットプラットフォームに JRE をインストールすると、インストーラがシステム JAR ファイルから一連のクラスを private 内部表現にロードして、その表現を共有アーカイブと呼ばれるファイルにダンプします。VM が起動されると、共有アーカイブはメモリーにマッピングされます。これにより、クラスのロードが減り、クラスに関連するメタデータの多くを複数の VM インスタンスで共有できます。J2SE 5.0 では、HotSpot Client VM を使用する場合にのみクラスデータ共有が有効になります。また、共有はシリアルガベージコレクタでのみサポートされます。
致命的エラーログでは、ログのヘッダーにバージョン文字列が出力されます。共有が有効になっている場合は、次の例に示すように sharing というテキストでそれが示されます。
# An unexpected error has been detected by HotSpot Virtual Machine: # # EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x08083d77, pid=3572, tid=784 # # Java VM: Java HotSpot(TM) Client VM (1.5-internal mixed mode, sharing) # Problematic frame: # V [jvm.dll+0x83d77]
共有を無効にするには、コマンド行に -Xshare:off オプションを指定します。共有を無効にするとクラッシュを再現できず、共有を有効にするとクラッシュを再現できる場合は、この機能のバグが発生した可能性があります。その場合は、できるだけ多くの情報を収集し、バグレポートを提出してください。
JDK 7 ソフトウェアは、32 ビットと 64 ビット両方のプラットフォーム用の Microsoft Visual Studio 2010 Professional を使用して Windows 上で構築されています。Java アプリケーションでクラッシュが発生し、コンパイラの異なるリリースでコンパイルされたネイティブライブラリまたは JNI ライブラリがある場合は、ランタイム間の互換性の問題を考慮する必要があります。具体的には、Microsoft のガイドラインに従って複数のランタイムを処理している場合にのみ、環境がサポートされます。たとえば、あるランタイムを使用してメモリーを割り当てた場合は、同じランタイムを使用してそれを解放する必要があります。リソースの割り当て時と異なるライブラリを使用してリソースを解放すると、予測できない動作やクラッシュが発生する可能性があります。