7 シグナルおよび例外の処理

この章では、Java HotSpot仮想マシンによるシグナルおよび例外の処理方法について説明します。また、独自のシグナル・ハンドラをインストールする必要があるアプリケーションを容易に作成できるように、Oracle Solaris、LinuxおよびmacOSオペレーティング・システムで使用可能なシグナル・チェーン機能についても説明します。

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

Oracle Solaris、LinuxおよびmacOSでのシグナルの処理

Java HotSpot VMは、様々な機能を実装して致命的エラー状態に対処するために、シグナル・ハンドラをインストールします。

たとえば、まれにjava.lang.NullPointerExceptionがスローされた場合に明示的なNULLチェックを回避する最適化では、SIGSEGVシグナルをキャッチして処理し、NullPointerExceptionをスローします。

一般に、シグナルやトラップが発生する場合には2つのカテゴリがあります。

  • 暗黙的なNULL処理のような、シグナルが予想され処理される場合。もう1つの例として、セーフポイントが必要になったときにメモリー内のページを保護する、セーフポイント・ポーリング・メカニズムがあります。そのページにアクセスするスレッドによってSIGSEGVが生成され、その結果、スレッドをセーフポイントに移行させるスタブが実行されます。

  • 予期しないシグナル。これには、VMコード、Java Native Interface (JNI)コード、またはネイティブ・コードで実行されたときのSIGSEGVが含まれます。これらの場合、このシグナルは予想されていないため、エラー・ログを作成してプロセスを終了するために致命的エラー処理が呼び出されます。

表7-2に、Oracle Solaris、LinuxおよびmacOSオペレーティング・システムで現在使用されているシグナルの一覧を示します。

Windowsでの例外処理

Windowsでは、例外はプログラムの実行中に発生するイベントです。

例外には、ハードウェア例外とソフトウェア例外の2種類があります。ハードウェア例外は、Oracle SolarisおよびLinuxオペレーティング・システム上のSIGSEGVSIGKILLなどのシグナルに相当します。ソフトウェア例外は、アプリケーションまたはオペレーティング・システムがRaiseException() APIを使用して明示的に発行します。

Windowsでは、ハードウェア例外とソフトウェア例外の両方を処理するメカニズムを構造化例外処理(SEH)と呼びます。これは、C++やJavaの例外処理メカニズムに似たスタック・フレームベースの例外処理です。C++では、次の例に示すように__tryおよび__exceptキーワードを使用して、例外が発生する可能性があるコードのセクションを保護します。

__try {
     // guarded body of code
 } __except (filter-expression) {
     // exception-handler block
 }

__exceptブロックは、GetExceptionCode() APIから返される整数例外コード、またはGetExceptionInformation() APIから返される例外情報、あるいはその両方を使用するフィルタ式によってフィルタ処理されます。

このフィルタ式は、次のいずれかの値として評価されるはずです。

  • EXCEPTION_CONTINUE_EXECUTION = -1

    このフィルタ式によって状況が修復され、例外が発生した場所から実行が継続されます。一部の例外スキームと異なり、SEHは再開モデルもサポートします。これは、シグナル・ハンドラの完了後にプログラムが中断した場所から実行が継続するという点で、Unixのシグナル処理とよく似ています。違いは、この場合のハンドラがフィルタ式だけであり、__exceptブロックではないことです。ただし、フィルタ式に関数呼出しを含めることもできます。

  • EXCEPTION_CONTINUE_SEARCH = 0

    現在のハンドラはこの例外を処理できません。次のハンドラのハンドラ検索が続行されます。これは、C++およびJavaで例外型を照合しないcatchブロックに似ています。

  • EXCEPTION_EXECUTE_HANDLER = 1

    現在のハンドラはこの例外を照合し、処理できます。__exceptブロックが実行されます。

終了ハンドラを構築するには、次の例に示すように、__tryおよび__finallyキーワードを使用します。

__try { 
    // guarded body of code  
} __finally { 
    // __finally block  
}

(例外の後、または例外なしで) __tryブロックから制御が離れたときは、__finallyブロックが実行されます。__finallyブロックの内部でAbnormalTermination() APIを呼び出すと、例外のあとで制御が継続したかどうかをテストできます。

Windowsプログラムは、最上位の未処理例外フィルタ関数をインストールして、__try/__exceptブロックで処理されない例外をキャッチすることもできます。この関数は、SetUnhandledExceptionFilter() APIを使用してプロセス単位でインストールされます。ある例外のハンドラが存在しない場合は、UnhandledExceptionFilter()が呼び出され、これが最上位の未処理例外フィルタ関数を(あれば)呼び出して、その例外をキャッチします。この関数によって、未処理例外をユーザーに通知するメッセージ・ボックスも表示されます。

Windowsの例外は、現在の実行ストリームに起因するUnixの同期シグナルに相当します。Windowsでは、コンソール・イベント(たとえば、ユーザーがコンソールで[Ctrl]+[C]を押した場合)などの非同期イベントは、SetConsoleCtlHandler() APIを使用して登録されたコンソール制御ハンドラによって処理されます。

Windows上でアプリケーションがsignal() APIを使用すると、C実行時ライブラリ(CRT)はWindowsの例外とコンソール・イベントの両方を適切なシグナルまたはC実行時エラーにマップします。たとえば、CRTは[Ctrl]+[C]をSIGINTにマップし、他のすべてのコンソール・イベントをSIGBREAKにマップします。同様に、SIGSEGVハンドラを登録すると、CRTは対応する例外をシグナルに変換します。CRTの起動コードは、main()関数の周囲に__try/__exceptブロックを実装しています。CRTの例外フィルタ関数(名前は_XcptFilter)は、Win32例外をシグナルにマップし、シグナルを適切なハンドラにディスパッチします。シグナルのハンドラをSIG_DFL (デフォルトの処理)に設定すると、_XcptFilterUnhandledExceptionFilterを呼び出します。

ベクトル化例外処理メカニズムを使用することもできます。ベクトル化ハンドラは、フレームベースのハンドラではありません。プログラムは、AddVectoredExceptionHandler APIを使用して0個以上のベクトル化例外ハンドラを登録できます。ベクトル化ハンドラは、構造化例外ハンドラ(あれば)の前に呼び出され、例外の発生場所に関係なく呼び出されます。

ベクトル化例外ハンドラは、次のいずれかの値を返します。

  • EXCEPTION_CONTINUE_EXECUTION: 次のベクトル化およびSEHハンドラをスキップします。

  • EXCEPTION_CONTINUE_SEARCH: 次のベクトル化またはSEHハンドラを続行します。

Windowsの例外処理の詳細は、MicrosoftのWebサイトを参照してください。

シグナル・チェーン

シグナル・チェーンでは、独自のシグナル・ハンドラをインストールする必要があるアプリケーションを書けます。この機能はSolaris、LinuxおよびmacOSで使用できます。

シグナル・チェーン機能は、次の機能を提供します。

  • OracleのHotSpot仮想マシンの作成時に事前インストールされるシグナル・ハンドラに対するサポート。

    HotSpot VMが作成されるときに、HotSpot VMで使用されるシグナルのシグナル・ハンドラが保存されます。実行中にこれらのシグナルが生成され、その対象がHotSpot VMでない場合は、事前インストールされたハンドラが呼び出されます。つまり、これらのシグナルのために、事前インストールされたシグナル・ハンドラはHotSpot VMハンドラの背後でチェーンされます。

  • Java Native Interfaceコード内または別のネイティブ・スレッドからのどちらかで、HotSpot VMを作成した後にインストールされたシグナル・ハンドラのサポート。

    アプリケーションは、libc/libthread/libpthreadライブラリの前にlibjsig.so共有ライブラリをリンクしてロードできます。HotSpot VMによってすでにインストールされているシグナル・ハンドラとハンドラが競合する場合に、このライブラリでは、signal()sigset()sigaction()などの呼出しがインターセプトされて、HotSpot VMで使用されるシグナル・ハンドラが置き換えれません。かわりに、これらのコールでは、新しいシグナル・ハンドラが保存されます。新しいシグナル・ハンドラは、これらのシグナルのために、HotSpot VM シグナル・ハンドラの背後でチェーンされます。実行中にこれらのシグナルが生成され、その対象がHotSpot VMでない場合は、事前インストールされたハンドラが呼び出されます。

    VMの作成後にインストールされるシグナル・ハンドラに対するサポートが必要ない場合、libjsig.so共有ライブラリは不要です。

    シグナル・チェーンを有効にするには、次の手順のいずれかを実行してlibjsig.so共有ライブラリを使用します。

    • HotSpot VMを作成または埋め込むアプリケーションにlibjsig.so共有ライブラリをリンクします。

      cc -L libjvm.so-directory -ljsig -ljvm java_application.c
      
    • LD_PRELOAD環境変数を使用します。

      • Kornシェル(ksh):

        export LD_PRELOAD=libjvm.so-directory/libjsig.so; java_application
      • Cシェル(csh):

        setenv LD_PRELOAD libjvm.so-directory/libjsig.so; java_application

    この間に呼び出されるsignal()sigset()、およびsigaction()コールは、HotSpot VMによってインストールされ、オペレーティング・システムで認識されているシグナル・ハンドラではなく、保存されたシグナル・ハンドラを返します。

ノート:

SIGQUITSIGTERMSIGINT、およびSIGHUPシグナルはチェーンできません。アプリケーションでこれらのシグナルを処理する必要がある場合は、—Xrsオプションの使用を検討してください。

macOSのシグナル・チェーンの有効化

macOSでシグナル・チェーンを有効にするには、次の環境変数を設定します。

  • DYLD_INSERT_LIBRARIES: SolarisおよびLinuxで使用可能なLD_PRELOAD環境変数のかわりに、指定されたライブラリがプリロードされます。

  • DYLD_FORCE_FLAT_NAMESPACE: macOSには(シンボルの完全修飾名にそのライブラリが含まれる)2レベル・ネームスペースがあるため、libjsigライブラリの関数を有効にしてOSの実装を置き換えます。この機能を有効にするには、この環境変数を任意の値に設定します。

次のコマンドは、libjsigライブラリをプリロードして、シグナル・チェーンを有効にします。

$ DYLD_FORCE_FLAT_NAMESPACE=0 DYLD_INSERT_LIBRARIES="JAVA_HOME/lib/libjsig.dylib" java MySpiffyJavaApp

ノート:

macOSのライブラリ・ファイル名はlibjsig.dylibで、SolarisまたはLinuxの場合のlibjsig.soではありません。

Java HotSpot VMによる例外処理

HotSpot VMでは、64ビット・システム用のAddVectoredExceptionHandlerAPIを使用して、初期化中に最上位の例外ハンドラをインストールします。

また、作成される各スレッドのスレッド(内部)起動関数呼出しの周囲に、C++の__try /__exceptブロックを使用してWin32 SEHをインストールします。

最後に、JNI関数の周囲に例外ハンドラをインストールします。

アプリケーションでJNIコード内の構造化例外を処理する必要がある場合は、C++の__try /__except文を使用できます。ただし、JNIコード内でベクトル化例外ハンドラを使用する必要がある場合、そのハンドラはVMの例外ハンドラに進むためにEXCEPTION_CONTINUE_SEARCHを返す必要があります。

一般に、例外が発生する2つのカテゴリがあります。

  • 例外が予想され、処理されるとき。例として、前述の暗黙的なNULL処理があります(以前はNULLへのアクセスにはEXCEPTION_ACCESS_VIOLATIONを使用して処理されていました)。

  • 予想外の例外。例として、VMコード、JNIコード、またはネイティブ・コード内で実行されているときのEXCEPTION_ACCESS_VIOLATIONがあります。これらの場合、このシグナルは予想されていないため、エラー・ログを作成してプロセスを終了するために致命的エラー処理が呼び出されます。

コンソール・ハンドラ

このトピックでは、Java HotSpot VMに登録されているコンソール・イベントのリストについて説明します。

Java HotSpot VMは、表7-1に示すコンソール・イベントを登録します。

表7-1 コンソール・イベント

コンソール・イベント シグナル 使用方法

CTRL_C_EVENT

SIGINT

このイベントとシグナルは、プロセスを終了するために使用されます。(省略可能)

CTRL_CLOSE_EVENTCTRL_LOGOFF_EVENTCTRL_SHUTDOWN_EVENT

SIGTERM

このイベントとシグナルは、VMが異常終了したときにシャットダウン・フック・メカニズムによって使用されます。(省略可能)

CTRL_BREAK_EVENT

SIGBREAK

このイベントとシグナルは、Javaスタック・トレースを標準エラー・ストリームに出力するために使用されます。(省略可能)

アプリケーションが独自のコンソール・ハンドラを登録する必要がある場合は、-Xrsオプションを使用できます。このオプションを使用すると、(前述のイベントのマッピングによる) SIGTERMに対してシャットダウン・フックが実行されず、(前述の[Ctrl]+[Break]イベントのマッピングによる) SIGBREAKに対してスレッド・ダンプのサポートを利用できなくなります。

Oracle Solaris、LinuxおよびmacOSで使用されるシグナル

このトピックでは、Solaris OS、LinuxおよびmacOSで使用されるシグナルのリストについて説明します

表7-2 Oracle Solaris、LinuxおよびmacOSで使用されるシグナル

シグナル 説明

SIGSEGVSIGBUSSIGFPESIGPIPESIGILL

これらのシグナルは暗黙的なNULLチェックの実装などに使用されます。

SIGQUIT

このシグナルは、Javaスタック・トレースを標準エラー・ストリームに出力するために使用されます。(省略可能)

SIGTERMSIGINTSIGHUP

これらのシグナルは、VMが異常終了したときにシャットダウン・フック・メカニズム(java.lang.Runtime.addShutdownHook)をサポートするために使用されます。(省略可能)

SIGJVM1SIGJVM2

これらのシグナルはJava仮想マシンで使用するために予約されています。(Solarisのみ)

SIGUSR2

このシグナルはLinuxおよびmacOSで内部的に使用されます。Solaris上のVMでは使用されません。

SIGABRT

HotSpot VMはこのシグナルを処理しません。かわりに、致命的エラー処理の後でabort関数を呼び出します。アプリケーションでこのシグナルを使用する場合は、期待されるセマンティックスを保持するため、プロセスを終了するようにしてください。

シグナルの使用を減らすために-Xrsオプションを指定すると、「オプション」としてタグ付けされたシグナルは使用されません。このオプションを使用すると、使用されるシグナルの数が減りますが、VMはSIGSEGVなどの基本的なシグナルのために独自のシグナル・ハンドラをインストールします。このオプションを指定すると、プロセスがSIGQUITSIGTERMSIGINT、またはSIGHUPを受信した場合にシャットダウン・フック・メカニズムが実行されなくなります。VMが正常終了した場合(つまり、最後の非デーモン・スレッドが完了するか、System.exitメソッドが呼び出されると)、シャットダウン・フックが予想どおり実行されます。

SIGUSR2は、suspendとresumeをLinuxおよびmacOSで実装するために使用されます。ただし、SIGUSR2のかわりに代替シグナルを使用するように指定することも可能です。これを行うには、_JAVA_SR_SIGNUM環境変数を指定します。この環境変数を設定する場合は、SIGSEGVおよびSIGBUSの最大より大きい値に設定する必要があります。