JNI オブジェクト参照の概要

GC による JNI オブジェクト参照の検出方法に関する実装の詳細は、JNI 仕様では記述されていません。代わりに JNI では、信頼性が高く、かつ予測可能な必須動作が指定されます。

ローカル参照およびグローバル参照

ローカル参照は、作成スタック・フレームおよびスレッドをスコープとし、作成スタック・フレームが戻されると、自動的に削除されます。グローバル参照では、JVM に接続されている任意のスレッドで、ネイティブ・コードがローカル参照をネイティブ・コードで使用可能な形式にレベル上げできます。

グローバル参照およびメモリー・リーク

グローバル参照は自動的に削除されないため、プログラマーによるメモリー管理が必要です。グローバル参照では必ず対象へのルートが確立され、そのサブツリー全体がアクセス可能になります。そのため、メモリー・リークを回避するために、作成されたグローバル参照は必ず解放する必要があります。

グローバル参照におけるリークは、メモリー不足の例外を引き起こすことになります。 このようなエラーは、特に JNI 例外処理を行わない場合に、解決が困難になることがあります。例外の処理を参照してください。

JNI グローバル参照機能を提供し、同時に対象の自動ガーベッジ・コレクションの一部も提供するために、JNI では次の 2 つの関数を提供します。
  • NewWeakGlobalRef
  • DeleteWeakGlobalRef
以上の関数は、JNI に弱参照へのアクセスを提供します。

ローカル参照およびメモリー・リーク

スコープ内になくなったローカル参照の自動ガーベッジ・コレクションにより、ほとんどの場合にメモリー・リークが回避されます。この自動ガーベッジ・コレクションは、ネイティブ・スレッドが Java (ネイティブ・メソッド) に戻されるか、JVM (呼び出し API) から切り離された場合に行われます。自動ガーベッジ・コレクションが行われない場合、ローカル参照のメモリー・リークが発生することがあります。メモリー・リークは、ネイティブ・メソッドが JVM に戻されない場合、または呼び出し API を使用するプログラムが JVM から切り離されない場合に発生することがあります。

以下のコードの例を見てみます。この例では、ループ内でネイティブ・コードにより新規ローカル参照が作成されます。

while ( <condition> )
{
   jobject myObj = (*env)->NewObject( env, clz, mid, NULL );

   if ( NULL != myObj )
   {
      /* myObj は有効なローカル ref であるため、使用します */
      jclass myClazz = (*env)->GetObjectClass(env, myObj);

      /* myObj や myClazz は使用しますが、新規のローカル ref は使用しません */

      /* 以下の呼び出しがないと、リークが発生します */
      (*env)->DeleteLocalRef( env, myObj ); 
      (*env)->DeleteLocalRef( env, myClazz );
   }

} /* end while */

ループ内で myObj 変数および myClazz 変数が新しいローカル参照により上書きされますが、各ローカル参照はルート・セットに保持されます。これらの参照は、DeleteLocalRef 呼び出しにより明示的に削除する必要があります。DeleteLocalRef 呼び出しが行われない場合、スレッドが Java に戻されるか JVM から切り離されるまで、ローカル参照でリークが発生します。

JNI 弱グローバル参照

弱グローバル参照は、特殊な種類のグローバル参照です。任意のスレッドで使用でき、ネイティブ関数呼び出し間で使用できますが、GC のルートとしては振る舞いません。弱グローバル参照により参照されるオブジェクトに他に強参照が含まれない場合、GC により常時そのオブジェクトが削除されます。

弱グローバル参照は注意して使用する必要があります。弱グローバル参照により参照されるオブジェクトがガーベッジ・コレクションにより処理されると、その参照はヌル参照になります。ヌル参照は、JNI 関数のサブセットとのみ安全に使用できます。弱グローバル参照が収集されたかどうかを検査するには、IsSameObject JNI 関数を使用して、弱グローバル参照をヌル値と比較します。

弱グローバル参照がヌルではないことを検査により確認済みであっても、弱グローバル参照で JNI 関数を呼び出すことは、ほとんどの場合、安全ではありません。これは、弱グローバル参照が、検査による確認後に、または JNI 関数の実行中でも、ヌル参照になることがあるためです。その代わり、弱グローバル参照を使用する前に、必ず強参照にレベル上げする必要があります。弱グローバル参照をレベル上げするには、JNI 関数 NewLocalRef または NewGlobalRef を使用します。

弱グローバル参照はメモリーを使用するため、必要ではなくなった場合には DeleteWeakGlobalRef JNI 関数により解放する必要があります。弱グローバル参照を解放できない場合は、ゆっくりとしたメモリー・リークが生じ、メモリー不足の例外が引き起こされることになります。

JNI 弱グローバル参照の使用に関する情報および注意事項については、JNI 仕様を参照してください。

JNI 参照の管理

JNI 参照の管理には、プラットフォームに依存しない一連の規則があります。

この規則には、以下のようなものがあります。

  1. JNI 参照は、JVM に接続しているスレッドでのみ有効です。
  2. ネイティブ・コード内の有効な JNI ローカル参照は、以下として取得する必要があります。
    1. ネイティブ・コードに対するパラメーター
    2. JNI 関数呼び出しの戻り値
  3. 有効な JNI グローバル参照は、NewGlobalRef または NewWeakGlobalRef を呼び出して、他の有効な JNI 参照 (グローバルまたはローカル) から取得する必要があります。
  4. ヌル値参照は常に有効であり、任意の JNI 参照 (グローバルまたはローカル) の代わりに使用できます。
  5. JNI ローカル参照は作成元のスレッドでのみ有効であり、作成フレームがスタック上にある場合に限り有効です。
注:
  1. ネイティブ・ストレージ内のローカル参照またはグローバル参照をヌル値で上書きしても、参照はルート・セットから削除されません。適切な Delete*Ref JNI 関数を使用して、ルート・セットから関数を削除してください。
  2. 多くの JNI 関数 (FindClass および NewObject など) では、保留中の例外がある場合にヌル値が戻されます。このような呼び出しについて戻り値をヌル値と比較することは、JNI ExceptionCheck 関数を呼び出すことと同義です。詳しくは、JNI 仕様を参照してください。
  3. JNI ローカル参照は、環境に関係なく、作成フレームが戻された後には使用してはなりません。JNI ローカル参照を任意のプロセスの静的ストレージに格納することは危険です。