第2章: 設計の概要
この章はJNIの主な設計の問題に焦点をあてています。 このセクションの設計の問題のほとんどはネイティブ・メソッドと関連があります。 呼出しAPIの設計については、「第5章: 呼出しAPI」に掲載されています。
この章では次のトピックについて説明します。
- JNIインタフェースの関数とポインタ
- ネイティブ・メソッドのコンパイル、ロード、およびリンク
- Javaオブジェクトの参照
- Javaオブジェクトへのアクセス
- プログラミング・エラーの報告
- Javaの例外
JNIインタフェースの関数とポインタ
ネイティブ・コードは、JNI関数を呼び出してJava VM機能にアクセスします。 JNI関数はインタフェース・ポインタを介して使用できます。 インタフェース・ポインタは、ポインタを指すポインタです。 このポインタはポインタの配列を指し、このそれぞれのポインタがインタフェース関数を指します。 どのインタフェース関数も配列内の事前に定義されたオフセットにあります。 次の「インタフェース・ポインタ」図は、インタフェース・ポインタの構成を図示したものです。
JNIインタフェースは、C++仮想関数表またはCOMインタフェースのように構成されています。 固定された組込み関数エントリでなくインタフェース表を使用する利点は、JNI名前空間がネイティブ・コードと分離できるようになることです。 VMは複数バージョンのJNI関数表を容易に提供できます。 たとえば、VMは次のように2つのJNI関数表をサポートすることもできます。
- 一方の表は、不正な引数の検査を厳密に実行するので、デバッグに適している
- もう一方の表は、JNI仕様で要求される最小限の検査を実行し、それによって効率が上がる。
JNIインタフェース・ポインタは現在のスレッドの中だけで有効です。 したがって、ネイティブ・メソッドがスレッド間でインタフェース・ポインタを渡さないようにしてください。 JNIを実装しているVMは、JNIインタフェース・ポインタによって指示された領域にスレッドのローカル・データを割り当てて格納することもできます。
ネイティブ・メソッドはJNIインタフェース・ポインタを引数として受け取ります。 したがって、VMが同じJavaスレッドからネイティブ・メソッドに複数の呼出しを行う場合は、ネイティブ・メソッドに同じインタフェース・ポインタを渡すことが保証されています。 しかし、ネイティブ・メソッドは、異なるJavaスレッドからでも呼び出すことができるので、異なるJNIインタフェース・ポインタを受け取ることもあります。
ネイティブ・メソッドのコンパイル、ロード、およびリンク
Java VMはマルチスレッド化されているため、ネイティブ・ライブラリも、マルチスレッドに対応したネイティブ・コンパイラでコンパイルおよびリンクするべきです。 たとえば、Sun StudioコンパイラでコンパイルされるC++コードには-mt
フラグを使用するべきです。 GNU gccコンパイラでコンパイルされるコードには、フラグ-D_REENTRANT
または-D_POSIX_C_SOURCE
を使用するべきです。 詳細は、ネイティブ・コンパイラのドキュメントを参照してください。
ネイティブ・メソッドは、System.loadLibrary
メソッドを使用してロードされます。 次の例では、クラス初期化メソッドが、ネイティブ・メソッドf
が定義されているプラットフォーム固有のネイティブ・ライブラリをロードしています。
package p.q.r;
class A {
native double f(int i, String s);
static {
System.loadLibrary("p_q_r_A");
}
}
System.loadLibrary
の引数は、プログラマによって任意に選択されたライブラリ名です。 このシステムは、標準であってもプラットフォーム固有の方式に従ってライブラリ名をネイティブ・ライブラリ名に変換します。 たとえば、Linuxシステムではp_q_r_A
という名前がlibp_q_r_A.so
に変換され、Windowsシステムでは同じp_q_r_A
名がp_q_r_A.dll
に変換されます。
プログラマは、同じローダーでクラスがロードされるかぎり、必要とするクラスがいくらあっても、その必要なすべてのネイティブ・メソッドを、単一ライブラリを使用して格納できます。 VMはクラス・ローダーごとのロードされたネイティブ・ライブラリのリストを内部的に維持します。 ベンダーは、名前ができるだけ競合しないネイティブ・ライブラリ名を選択する必要があります。
動的にリンクされたライブラリと静的にリンクされたライブラリのサポート、およびそれぞれのライフサイクル管理の"load"および"unload"関数フックは、「ライブラリとバージョン管理にある「呼び出しAPI」セクション」で詳しく説明されています。
ネイティブ・メソッド名の解決
JNIは、Javaで宣言されたnative
メソッドの名前からネイティブ・ライブラリに存在するネイティブ・メソッドの名前への1対1マッピングを定義します。 VMはこのマッピングを使用して、native
メソッドのJava呼出しをネイティブ・ライブラリの対応する実装に動的にリンクします。
マッピングでは、native
メソッド宣言から導出された次のコンポーネントを連結して、ネイティブ・メソッド名が生成されます:
- 接頭辞
Java_
native
メソッドを宣言するクラスの二項名(内部形式): 名前のエスケープの結果。- アンダースコア("_")
- エスケープされたメソッド名
native
メソッド宣言がオーバーロードされている場合: メソッド宣言のエスケープされたパラメータ記述子(JVMS 4.3.3)が続く2つのアンダースコア("__")。
エスケープすると、英数字のASCII文字(A-Za-z0-9
)はすべて変更されず、次の表の各UTF-16コード・ユニットが対応するエスケープ・シーケンスに置き換えられます。 エスケープする名前にサロゲート・ペアが含まれている場合、上位サロゲート・コード単位と下位サロゲート・コード単位は別々にエスケープされます。 エスケープの結果は、ASCII文字A-Za-z0-9
とアンダースコアのみで構成される文字列です。
UTF-16コード単位 | エスケープ・シーケンス |
---|---|
スラッシュ(/ , U+002F) |
_ |
アンダースコア(_ , U+005F) |
_1 |
セミコロン(; , U+003B) |
_2 |
左大カッコ([ , U+005B) |
_3 |
英数字のASCII (A-Za-z0-9 )、スラッシュ、アンダースコア、セミコロンまたは左大カッコを表さないUTF-16コード・ユニットの\u WXYZ |
_0wxyz ここで、w , x , y およびz は16進数数字W , X , Y およびZ の小文字形式です。 (たとえば、U+ABCD は_0abcd になります。) |
エスケープは2つの理由で必要です。 まず、Unicode文字を含む可能性のあるJavaソース・コード内のクラス名とメソッド名が、Cソース・コード内の有効な関数名に変換されるようにします。 次に、";"および"["文字を使用してパラメータ型をエンコードするnative
メソッドのパラメータ記述子をC関数名でエンコードできることを確認します。
Javaプログラムがnative
メソッドを起動すると、VMはまずネイティブ・メソッド名の短縮バージョン、つまりエスケープされた引数シグネチャのない名前を検索して、ネイティブ・ライブラリを検索します。 短縮名のネイティブ・メソッドが見つからない場合、VMはネイティブ・メソッド名の長いバージョン、つまりエスケープされた引数シグネチャを含む名前を検索します。
最初に短縮名を検索すると、ネイティブ・ライブラリでの実装の宣言が容易になります。 たとえば、Javaに次のnative
メソッドがあるとします:
package p.q.r;
class A {
native double f(int i, String s);
}
対応するC関数には、Java_p_q_r_A_f__ILjava_lang_String_2
ではなくJava_p_q_r_A_f
という名前を付けることができます。
ネイティブ・ライブラリでの長い名前を持つ実装の宣言は、クラス内の複数のnative
メソッドが同じ名前を持つ場合にのみ必要です。 たとえば、Javaに次のnative
メソッドがあるとします:
package p.q.r;
class A {
native double f(int i, String s);
native double f(int i, Object s);
}
native
メソッドはオーバーロードされているため、対応するC関数にはJava_p_q_r_A_f__ILjava_lang_String_2
およびJava_p_q_r_A_f__ILjava_lang_Object_2
という名前を付ける必要があります。
Javaのnative
メソッドがnative
以外のメソッドによってのみオーバーロードされている場合、ネイティブ・ライブラリの長い名前は必要ありません。 次の例では、他のメソッドg
がnative
ではなく、ネイティブ・ライブラリに存在しないため、native
メソッドg
をロング・ネームを使用してリンクする必要はありません。
package p.q.r;
class B {
int g(int i);
native int g(double d);
}
エスケープ・シーケンスは、_0
や_1
などで安全に開始できます。これは、Javaソース・コードのクラス名やメソッド名が数字で始まらないためです。 ただし、Javaソース・コードから生成されなかったクラス・ファイルには該当しません。 ネイティブ・メソッド名への1対1マッピングを保持するために、VMは次のように結果の名前をチェックします。 native
メソッド宣言(クラス名、メソッド名、または引数の型)からカーソル文字列をエスケープするプロセスによって、"0
"、"1
"、"2
"、または"3
"文字がカーソル文字列からエスケープされ、アンダースコアのすぐ後、またはエスケープされた文字列 (完全に組み立てられた名前のアンダースコアの後に続きます)の最初のeitherが変更されない場合、エスケープ・プロセスは"failed"と言われます。。 このような場合、ネイティブ・ライブラリ検索は実行されず、native
メソッド呼出しをリンクしようとするとUnsatisfiedLinkError
がスローされます。 このような場合をカバーするように現在の単純なマッピング・スキームを拡張できますが、複雑さのコストはすべての利点を上回ります。
ネイティブ・メソッドとインタフェースAPIの両方とも、所定のプラットフォーム上での標準ライブラリ呼出し規則に従っています。 たとえば、UNIXシステムはC呼出し規則を使用するのに対して、Win32システムは__stdcallを使用します。
ネイティブ・メソッドは、「RegisterNatives
ファンクション」を使用して明示的にリンクすることもできます。 RegisterNatives
は、特定のネイティブJavaメソッドに対して実行されるネイティブ・コードを変更することで、JVM (暗号化アルゴリズム、正確性、セキュリティ、型の安全性を含む)のドキュメント化された動作を変更できることに注意してください。 したがって、RegisterNatives
関数を使用するネイティブ・ライブラリを持つアプリケーションは慎重に使用してください。
ネイティブ・メソッドの引数
JNIインタフェース・ポインタは、ネイティブ・メソッドの最初の引数です。 JNIインタフェース・ポインタはJNIEnv型です。 2番目の引数は、ネイティブ・メソッドがstaticであるかstaticでないかによって異なります。 staticでないネイティブ・メソッドの2番目の引数は、オブジェクトの参照です。 staticなネイティブ・メソッドの2番目の引数は、Javaクラスの参照です。
残りの引数は、通常のJavaメソッド引数に対応しています。 ネイティブ・メソッド呼出しは、呼出し側ルーチンに結果を値で渡して戻します。 第3章: JNIの型とデータ構造で、Java型とC型とのマッピングについて説明しています。
次のコード例に、C関数を使用してネイティブ・メソッドf
を実装する方法を示します。 ネイティブ・メソッドf
は、次のように宣言されます。
package p.q.r;
class A {
native double f(int i, String s);
// ...
}
長い名前のJava_p_q_r_A_f_ILjava_lang_String_2
を持つC関数は、ネイティブ・メソッドf
を実装します:
jdouble Java_p_q_r_A_f__ILjava_lang_String_2 (
JNIEnv *env, /* interface pointer */
jobject obj, /* "this" pointer */
jint i, /* argument #1 */
jstring s) /* argument #2 */
{
/* Obtain a C-copy of the Java string */
const char *str = (*env)->GetStringUTFChars(env, s, 0);
/* process the string */
...
/* Now we are done with str */
(*env)->ReleaseStringUTFChars(env, s, str);
return ...
}
Javaオブジェクトは、常にインタフェース・ポインタenvを使用して操作します。 C++を使用すると、次のコード例に示すように、多少すっきりしたコードを書くことができます。
extern "C" /* specify the C calling convention */
jdouble Java_p_q_r_A_f__ILjava_lang_String_2 (
JNIEnv *env, /* interface pointer */
jobject obj, /* "this" pointer */
jint i, /* argument #1 */
jstring s) /* argument #2 */
{
const char *str = env->GetStringUTFChars(s, 0);
// ...
env->ReleaseStringUTFChars(s, str);
// return ...
}
C++では、余分な間接参照およびインタフェース・ポインタ引数がソース・コードから消えています。 しかし、基盤となるメカニズムはCによる場合とまったく同じです。 C++では、JNI関数が、インライン・メンバー関数として定義されますが、これらは展開されて、Cの対応部分になります。
Javaオブジェクトの参照
整数、文字などのプリミティブ型は、Javaとネイティブ・メソッド間でコピーされます。 他方、任意のJavaオブジェクトは参照渡しです。 VMはネイティブ・コードに渡されたすべてのオブジェクトがガベージ・コレクタによって解放されないよう、これらのオブジェクトを追跡しなければなりません。 ネイティブ・コードは、逆に、オブジェクトがもう必要ないことをVMに通知する手段を持たなければなりません。 さらに、ガベージ・コレクタは、ネイティブ・コードによって参照されるオブジェクトを移動することもできなければなりません。
グローバル参照およびローカル参照
JNIは、ネイティブ・コードによって使用されるオブジェクト参照をローカル参照とグローバル参照の2つのカテゴリに分けます。 ローカル参照は、ネイティブ・メソッド呼出しの間だけ有効で、ネイティブ・メソッドが復帰すると自動的に解放されます。 グローバル参照は、明示的に解放されるまで有効になっています。
オブジェクトは、ローカル参照としてネイティブ・メソッドに渡されます。 JNI関数によって返されるJavaオブジェクトはすべてローカル参照です。 JNIでは、プログラマがローカル参照からグローバル参照を作成できます。 Javaオブジェクトを扱うJNI関数は、グローバルとローカルの両方の参照を受け入れます。 ネイティブ・メソッドは、その結果、グローバルまたはローカルのどちらかの参照をVMに返すことになります。
ほとんどの場合、プログラマは、ネイティブ・メソッドが戻ったあと、VMに基づいてすべてのローカル参照を解放すべきです。 しかし、プログラマが明示的にローカル参照を解放する必要がある場合もあります。 たとえば、次のような状況があります。
- ネイティブ・メソッドが大きなJavaオブジェクトにアクセスし、このJavaオブジェクトに対してローカル参照を作成する。 次にネイティブ・メソッドは、呼出し側に返す前に追加の計算を実行する。 大きなJavaオブジェクトのローカル参照は、このオブジェクトが残りの計算に使用されなくなった場合でも、ガベージ・コレクトが妨げられる。
- ネイティブ・メソッドは多数のローカル参照を作成するが、これらのすべてが同時に使用されるわけではない。 VMはローカル参照を追跡するため一定量のスペースを必要とし、そのため多くのローカル参照を作成すると、システムのメモリーがなくなることがある。 たとえば、ネイティブ・メソッドは、大きな配列のオブジェクトを通してループし、その要素をローカル参照として検索し、反復のたびに1つの要素で演算する。 各反復の終了後、プログラマはその配列要素のローカル参照をもう必要としない。
JNIでは、プログラマがネイティブ・メソッド内の任意の点でローカル参照を手動で削除できます。 プログラマが手動でローカル参照を解放できることを保証するため、JNI関数では、これら関数が結果として返す参照を除いて、余分なローカル参照を作成できないようになっています。
ローカル参照は、これらが作成されたスレッドの中だけで有効です。 ネイティブ・コードは、スレッド間でローカル参照を受渡ししてはいけません。
ローカル参照の実装
ローカル参照子を実装するため、Java VMはJavaからネイティブ・メソッドに制御が移行するたびにレジストリを作成します。 レジストリは、移動できないローカル参照をJavaオブジェクトにマッピングし、オブジェクトがガベージ・コレクトされないよう守ります。 ネイティブ・メソッドに渡されるすべてのJavaオブジェクト(JNI関数呼出しの結果として返されるものも含む)は、自動的にレジストリに追加されます。 このレジストリは、ネイティブ・メソッドが返ったあとに削除され、そのすべての項目をガベージ・コレクトできるようにします。
レジストリを実装するには、表、連結リスト、またはハッシュ表を使用するなど、さまざまな方法があります。 レジストリの中の項目の重複を避けるため参照のカウントが使用されることがありますが、JNIの実装では重複項目を検出し重複をなくす必要はありません。
ローカル参照は、厳密にネイティブ・スタックをスキャンしても、忠実に実装することはできません。 ネイティブ・コードは、ローカル参照をグローバルまたはヒープ・データ構造に格納することもあります。
Javaオブジェクトへのアクセス
JNIは、グローバル参照およびローカル参照への豊富なアクセス機能のセットを提供します。 これは、VMが内部的にどのようにJavaオブジェクトを表現していても、同じネイティブ・メソッド実装が作動することを意味します。 これが決定的な理由となって、JNIは多様なVM実装でサポートされています。
不透明な参照を介してアクセス用関数を使用するオーバーヘッドは、Cデータ構造体へ直接アクセスする場合より高くなります。 ほとんどの場合にJavaプログラマはネイティブ・メソッドを使用して、このインタフェースのオーバーヘッドが目立たなくなるような重要な(自明的でない)タスクを実行していると考えられます。
プリミティブ配列へのアクセス
このオーバーヘッドは、整数列や文字列のような多くのプリミティブ・データ型を含んでいる大きなJavaオブジェクトでは受け入れられません。 ベクトルおよび行列の計算を実行するために使用されるネイティブ・メソッドを考えてください。 Java配列を反復演算し、各要素をすべて関数呼出しによって取り出すことは、非効率です。
1つの解決法では、"pinning"という概念が導入されているため、ネイティブ・メソッドはVMに配列の内容を固定するよう要求することができます。 そのあとネイティブ・メソッドは、その要素を指すダイレクト・ポインタを受け取ります。 しかし、このアプローチは次の2つのことを意味します。
- カベージ・コレクタはピニングをサポートしなければならない。
- VMはプリミティブ配列をメモリーに切れ目なく連続して配置しなければならない。 これはほとんどのプリミティブ配列にとってもっとも自然な実装だが、ブール配列はパックでもアンパックでも実装できる。 したがって、ブール配列の正確な配置に基づくネイティブ・コードは移植できない。
上記の両方の問題を克服する折衷案を採用しています。
第一に、Java配列のセグメントとネイティブ・メモリー・バッファの間でプリミティブ配列要素をコピーするための関数のセットを提供します。 ネイティブ・メソッドが大きな配列の中の少数要素だけにアクセスする必要しかない場合は、これらの関数を使用してください。
第二に、プログラマは別の関数のセットを使用して、ピニングされたバージョンの配列要素を検索できます。 これらの関数がストレージの割当ておよびコピーを実行するにはJava VMが必要なことを覚えておいてください。 これらの関数が実際に配列をコピーできるかどうかは、次のようにVMの実装によって決まります。
- ガベージ・コレクタがピニングをサポートする場合、配列の配置はネイティブ・メソッドが予期するものと同じなので、コピーは必要ない。
- それ以外の場合は、配列が移動できないメモリー・ブロック(Cヒープの中など)にコピーされ、必要なフォーマット変換が実行される。 コピーへのポインタが返される。
このインタフェースは、ネイティブ・コードが配列要素にアクセスする必要がなくなったことをVMに通知するための関数を備えています。 これらの関数を呼び出すと、システムは配列のピンを外すか、または元の配列を移動できないコピーと適合させ、そのコピーを解放します。
これによって、柔軟性が高くなります。 ガベージ・コレクタ・アルゴリズムにより、指定配列ごとのコピーまたはピニングについて個別に判断できます。 たとえば、ガベージ・コレクタが小さなオブジェクトをコピーし、大きなオブジェクトをピニングすることもできます。
JNIの実装では、複数のスレッドで実行されているネイティブ・メソッドが、同時に同じ配列に確実にアクセスできるようにしなければなりません。 たとえば、JNIはピニングされた配列ごとに内部カウンタを備えて、あるスレッドが、別のスレッドもピニングしている配列のピンを外すことがないようにしています。 JNIはネイティブ・メソッドによる排他アクセスのためにプリミティブ配列をロックする必要はありません。 異なるスレッドから同時にJava配列を更新すると、不測の結果を招きます。
フィールドおよびメソッドへのアクセス
JNIでは、ネイティブ・コードでフィールドにアクセスし、Javaオブジェクトのメソッドを呼び出すことができます。 JNIは、シンボリック名および型のシグニチャによってメソッドおよびフィールドを識別します。 2ステップのプロセスにより、名前およびシグニチャからフィールドまたはメソッドを探し出す手間を分けています。 たとえば、cls
クラスでメソッドfを呼び出す場合、ネイティブ・コードはまず次のようにメソッドIDを取得します。
jmethodID mid = env->GetMethodID(cls, "f", "(ILjava/lang/String;)D");
続いてネイティブ・コードは、次のようにメソッド探索の手間をかけずにメソッドIDを繰返し使用できます。
jdouble result = env->CallDoubleMethod(obj, mid, 10, str);
フィールドまたはメソッドIDでは、VMがそのIDが導き出されたクラスをアンロードしないように防ぐことはできません。 クラスがアンロードされると、フィールドまたはメソッドIDは無効になります。 そのため、ネイティブ・コードで次の点を確認する必要があります。
- ベースとなるクラスのライブ参照を保持するかどうか
- フィールドまたはメソッドIDを再計算するかどうか
延長された期間中にメソッドまたはフィールドIDを使用するかどうか。
JNIは、フィールドまたはメソッドIDがどのように内部的に実装されているかには何の制約も課しません。
コール元を区別するメソッドのコール
少数のJavaメソッドに、呼出し元依存性という特殊なプロパティがあります。 呼出し元依存メソッドは、その直接の呼出し元の識別情報によって異なる動作をする可能性があります。 たとえば、AccessibleObject::canAccessは、アクセシビリティを判定するコール元を知っている必要があります。
ネイティブ・コードがこのようなメソッドをコールするときは、コール・スタックにJavaコール元が存在しない可能性があります。 プログラマは、ネイティブ・コードからコールされるJavaメソッドがコール・センシティブかどうか、およびJavaコール元がない場合にそれらのメソッドがどのように応答するかを把握する必要があります。 プログラマは必要に応じて、ネイティブ・コードが呼び出すことのできるJavaコードを提供し、そのコードが元のJavaメソッドを呼び出します。
プログラミング・エラーの報告
JNIは、nullポインタまたは不正な引数型の受け渡しのようなプログラミング・エラーについてチェックを行いません。 不正な引数型には、Javaクラス・オブジェクトの代わりに通常のJavaオブジェクトを使用するようなことが含まれます。 JNIは、次のような理由からこれらのプログラミング・エラーについてのチェックを行いません。
- JNI関数に起こり得るすべてのエラー条件についてチェックするよう強制すると、通常の(正常な)ネイティブ・メソッドのパフォーマンスが低下する。
- 多くの場合、このようなチェックを実行できるほど十分な実行時の情報がない。
ほとんどのCライブラリ関数は、プログラム・エラーに対して保護されていません。 たとえば、printf()
関数は、無効アドレスを受け取ると、通常は実行時エラーを起し、エラー・コードを返しません。 すべての起こり得るエラー条件についてチェックするようにCライブラリ関数に強制すると、ユーザー・コードで1回チェックしてまたライブラリでも行うというように、チェックが重複する可能性があります。
プログラマは不正なポインタや間違った型の引数をJNI関数に渡してはいけません。 これを行うと、システムの破壊状態またはVMのクラッシュを含む、不測の結果に至ることがあります。
Javaの例外
JNIでは、ネイティブ・メソッドは任意のJavaの例外を発生させることが可能です。 ネイティブ・コードでも、未処理のJavaの例外を処理できます。 未処理のままになっているJavaの例外はVMに送り返されます。
例外およびエラー・コード
JNI関数によっては、Javaの例外メカニズムを使用してエラー条件を報告するものもあります。 ほとんどの場合、JNI関数は、エラー・コードを返し、かつ Javaの例外をスローすることによって、エラー状態を報告します。 通常、このエラー・コードは、通常の戻り値の範囲外にある特殊な戻り値(NULLなど)です。 したがって、プログラマは次のことを行うことができます。
- 最後のJNI呼出しの戻り値をすばやくチェックして、エラーが起きているかどうか判断する
- 関数
ExceptionOccurred()
を呼び出して、エラー状態のさらに詳細な記述が含まれている例外オブジェクトを取得する。
プログラマが最初にエラー・コードをチェックできない状態で、例外をチェックすることが必要になる場合として、次の2つのケースがあります。
- Javaメソッドを呼び出すJNI関数がJavaメソッドの結果を返す。 プログラマは
ExceptionOccurred()
を呼び出して、Javaメソッドの実行中に起こり得る例外が起きていないかチェックする必要があります。 - JNI配列アクセス関数の一部には、エラー・コードを返さないが、
ArrayIndexOutOfBoundsException
またはArrayStoreException
をスローするものがあります。
その他すべての場合は、非エラーの戻り値で、例外がスローされていないことを保証しています。
非同期の例外
1つのスレッドは、Java 2 SDKリリース1.2以降で非推奨されているThread.stop()
メソッドを呼び出すことによって、別のスレッドで非同期例外を発生させることがあります。 Thread.stop()
を使用することを強く推奨します。Thread.stop()
は一般的に不確定なアプリケーション状態につながります。
さらに、JVMは、JNI APIコールの直接の結果ではなく、さまざまなJVM内部エラーのために、現在のスレッドで例外を生成することがあります: VirtualMachineError
のようなStackOverflowError
またはOutOfMemoryError
。 これらは、非同期例外とも呼ばれます。
非同期例外は、現在のスレッドのネイティブ・コードの実行に直ちには影響しません:
- ネイティブ・コードが、同期した例外を発生させることができるJNI関数の1つを呼び出す
- ネイティブ・コードが
ExceptionOccurred()
を使用して、同期および非同期の例外があるかを明示的にチェックする。
同期例外を発生させる可能性のあるJNI関数だけが、非同期例外をチェックすることに注意してください。
ネイティブ・メソッドは、ExceptionOccurred()
チェックを必要な場所に挿入する必要があります(他の例外チェックなしで長い実行コードなど)。 これにより、現在のスレッドが妥当な時間内に非同期例外に確実に応答するようになります。 ただし、その非同期性のため、コールの前に例外チェックを行うことは、チェックとコールの間で非同期例外が発生しないことを保証するものではありません。
例外処理
ネイティブ・コードで例外を処理するには、次の2とおりの方法があります。
- ネイティブ・メソッドは、ただちに復帰して、ネイティブ・メソッド呼出しを開始したJavaコード中で例外をスローさせることができる。
- ネイティブ・コードは、
ExceptionClear()
を呼び出して例外をクリアしてから、自身の例外処理コードを実行できる。
例外の発生後、ネイティブ・コードは、ほかのJNI呼出しを行う前にまず例外をクリアする必要があります。 未処理の例外があるとき、安全に呼び出せるJNI関数は次のとおりです。
ExceptionOccurred()
ExceptionDescribe()
ExceptionClear()
ExceptionCheck()
ReleaseStringChars()
ReleaseStringUTFChars()
ReleaseStringCritical()
Release<Type>ArrayElements()
ReleasePrimitiveArrayCritical()
DeleteLocalRef()
DeleteGlobalRef()
DeleteWeakGlobalRef()
MonitorExit()
PushLocalFrame()
PopLocalFrame()
DetachCurrentThread()