JavaTM 2 SDK のバージョン 1.2 で導入された
JNI の拡張機能

Java 2 SDK では、次の JNI の機能が拡張されています。

新しい定数

#define JNI_VERSION_1_1 0x00010001
#define JNI_VERSION_1_2 0x00010002

/* Error codes */
#define JNI_EDETACHED    (-2)              /* thread detached from the VM */
#define JNI_EVERSION     (-3)              /* JNI version error */

既存の API の拡張

新しい関数

クラス操作

拡張された Java セキュリティーモデルでは、システムクラス以外のクラスがネイティブコードをロードできます。JNI の FindClass 関数が、クラスローダーでロードされたクラスを検索できるように拡張されました。

jclass FindClass(JNIEnv *env, const char *name);

JDK 1.1 では、FindClassCLASSPATH 内のローカルクラスだけを検索しました。検索されたクラスは、クラスローダーを持っていませんでした。

Java セキュリティーモデルは拡張され、システムクラス以外のクラスによるネイティブメソッドのロードおよび呼び出しが可能になりました。Java 2 プラットフォームでは、FindClass は、現在のネイティブメソッドと関連付けられているクラスローダーを検出します。ネイティブコードがシステムクラスに属する場合、クラスローダーは検出されません。それ以外の場合には、適切なクラスローダーが呼び出され、名前が付けられたクラスのロードおよびリンクを行います。

FindClass が呼び出しインタフェースによって呼び出された場合、現在のネイティブメソッドまたはそれに関連付けられたクラスローダーは存在しません。この場合、ClassLoader.getBaseClassLoader の結果が使用されます。これは、仮想マシンがアプリケーション用に作成するクラスローダーであり、java.class.path プロパティーにリストされたクラスを検索できます。

ライブラリおよびバージョン管理

JDK 1.1 では、ネイティブライブラリを一度ロードすると、すべてのクラスローダーからそのライブラリを認識できました。そのため、異なるクラスローダーの 2 つのクラスが、同じネイティブメソッドにリンクしてしまう可能性がありました。このため次の 2 つの問題が発生します。

Java 2 SDK では、各クラスローダーは、独自のネイティブライブラリのセットを管理します。同じ JNI ネイティブライブラリを、2 つ以上のクラスローダーにロードすることはできません。そのようなことを行うと、UnsatisfiedLinkError がスローされます。たとえば、System.loadLibrary を使用して 2 つのクラスローダーにネイティブライブラリをロードしようとすると、UnsatisfiedLinkError がスローされます。この新しい手法の利点は次のとおりです。

バージョン管理およびリソース管理を容易にするために、Java 2 プラットフォームの JNI ライブラリは次の 2 つの関数をオプションでエクスポートできます。

jint JNI_OnLoad(JavaVM *vm, void *reserved);

(たとえば System.loadLibrary を介して) ネイティブライブラリがロードされると、VM は JNI_OnLoad を呼び出します。JNI_OnLoad は、ネイティブライブラリが必要とする JNI バージョンを返さなければなりません。

新しい JNI 関数のどれかを使用するために、ネイティブライブラリは JNI_VERSION_1_2 を返す JNI_OnLoad 関数をエクスポートする必要があります。ネイティブライブラリが JNI_OnLoad 関数をエクスポートしない場合、VM はライブラリが JNI バージョン JNI_VERSION_1_1 を要求しているだけであるとみなします。VM が JNI_OnLoad によって返されたバージョン番号を認識しない場合、ネイティブライブラリをロードすることはできません。

void JNI_OnUnload(JavaVM *vm, void *reserved);

ネイティブライブラリを含むクラスローダーのガベージコレクションの際に、VM は JNI_OnUnload を呼び出します。この関数は、クリーンアップオペレーションに使用されます。これは未確認のコンテキスト (ファイナライザからのコンテキストなど) で呼び出される関数なので、プログラマは慎重に Java VM サービスを使用する必要があります。また Java コールバックを任意に行うことのないようにしなければなりません。

JNI_OnLoadJNI_OnUnload は、JNI ライブラリがオプションで提供する 2 つの関数であり、VM からエクスポートされるものではありません。

ローカル参照の管理

ローカル参照は、ネイティブメソッドの呼び出し期間中有効です。ローカル参照は、ネイティブメソッドが復帰すると自動的に解放されます。各ローカル参照は、Java 仮想マシンのリソースをいくらか消費します。プログラマは、ネイティブメソッドがローカル参照を過剰に割り当てないように確認する必要があります。ローカル参照は、ネイティブメソッドが Java に復帰すると自動的に解放されますが、ローカル参照を過剰に割り当てると、ネイティブメソッドの実行中に VM がメモリーを使い果たしてしまう可能性があります。

JDK 1.1 は、プログラマがローカル参照を手動で削除できるように、DeleteLocalRef 関数を提供します。たとえば、ネイティブコードがオブジェクトの潜在的に大きな配列を繰り返しにより処理し、反復ごとに 1 つの要素を使用する場合、次の反復で新しいローカル参照が作成される前に、もう使用されない配列要素へのローカル参照を削除するのは良い方法です。

Java 2 SDK では、ローカル参照の有効期間の管理用に関数のセットが追加されました。

jint EnsureLocalCapacity(JNIEnv *env, jint capacity);

少なくとも指定された数のローカル参照を現在のスレッドで作成できることを保証します。成功した場合は 0 を返します。それ以外の場合は負の数を返し、OutOfMemoryError をスローします。

ネイティブメソッドに入る前に、VM は自動的に、少なくとも 16 のローカル参照の作成を保証します。

下位互換性のために VM は、保証された容量以上にローカル参照を割り当てます。(デバッグのサポートとして、VM がユーザーに対し、ローカル参照の作成数が多すぎるという内容の警告を発する場合があります。Java 2 SDK では、プログラマは -verbose:jni コマンド行オプションを指定して、これらのメッセージを有効にすることができます。) 保証された容量を超えてしまい、これ以上ローカル参照を作成できない場合、VM は FatalError を呼び出します。

jint PushLocalFrame(JNIEnv *env, jint capacity);

新しいローカル参照フレームを作成します。このフレームに最低限指定された数のローカル参照を作成できます。成功した場合は 0、失敗した場合は負の数と未処理の OutOfMemoryError を返します。

前回のローカルフレームで作成済みのローカル参照は、現在のローカルフレームでも引き続き有効です。

jobject PopLocalFrame(JNIEnv *env, jobject result);

現在のローカル参照フレームをポップし、すべてのローカル参照を解放し、指定された result オブジェクトに対する前回のローカル参照フレームのローカル参照を返します。

前回のフレームへの参照を返す必要がない場合は、result として NULL を渡してください。

jobject NewLocalRef(JNIEnv *env, jobject ref);

ref と同じオブジェクトを参照する新しいローカル参照を作成します。渡された ref は、グローバル参照またはローカル参照である可能性があります。refnull を参照している場合は、NULL を返します。

例外

例外オブジェクトへのローカル参照を作成せずに、未処理の例外を確認するための便利な関数を次に示します。

jboolean ExceptionCheck(JNIEnv *env);

未処理の例外がある場合は JNI_TRUE、ない場合は JNI_FALSE を返します。

弱グローバル参照

弱グローバル参照は、特別な種類のグローバル参照です。通常のグローバル参照と異なり、弱グローバル参照を使用すると、配下の Java オブジェクトをガベージコレクトすることができます。弱グローバル参照は、グローバルまたはローカル参照が使用されている状況ならどこででも使用できます。ガベージコレクタを実行すると、配下のオブジェクトが弱参照によってだけ参照されている場合、そのオブジェクトが解放されます。解放されたオブジェクトを指している弱グローバル参照は、機能的に NULL と同等です。プログラマは、IsSameObject を使用して弱参照と NULL とを比較することにより、弱グローバル参照が解放されたオブジェクトを参照しているかどうかを確認できます。

JNI の弱グローバル参照は、Java 2 プラットフォーム API (java.lang.ref パッケージおよびそのクラス) の一部として入手可能な Java 弱参照の簡易版です。

解説    (2001 年 6 月に追加)

ガベージコレクションはネイティブメソッドの実行中に発生することもあるため、弱グローバル参照で参照されているオブジェクトはいつでも解放される可能性があります。弱グローバル参照は、グローバル参照が使用されているところで使用できますが、そのように使用すると予告なしで NULL と機能的に同等になる場合があるので、一般的には不適切です。

IsSameObject は弱グローバル参照が解放されたオブジェクトを参照しているかどうかを判別するのに使用できますが、オブジェクトがその直後に解放されるのを防止するわけではありません。そのため、プログラマはこの検査に基づいて、その後の JNI 呼び出しで弱グローバル参照を (NULL 参照以外で) 使用できるかどうかを判別することはできません。

この本質的な制限を克服するため、JNI 関数 NewLocalRef または NewGlobalRef を使用して同一のオブジェクトへの標準 (強い) ローカル参照またはグローバル参照を取得し、この強い参照を使用して該当するオブジェクトにアクセスすることをお勧めします。これらの関数は、オブジェクトが解放されている場合は NULL を返し、それ以外の場合は強い参照を返します (強い参照はオブジェクトが解放されるのを防止します)。オブジェクトへの直接アクセスが不要になったときは、オブジェクトを解放できるように、新しい参照を明示的に削除するべきです。

弱グローバル参照は、ほかの種類の弱い参照 (SoftReference クラスまたは WeakReference クラスの Java オブジェクト) よりも弱い参照です。特定のオブジェクトへの弱いグローバル参照は、そのオブジェクトを参照している SoftReference オブジェクトまたは WeakReference オブジェクトが参照を解除するまで、機能的に NULL と同等にはなりません。

弱グローバル参照は、ファイナライズを必要とするオブジェクトへの Java の内部参照よりも弱い参照です。弱グローバル参照は、参照先のオブジェクトのファイナライザが存在する場合、それが完了するまで、 NULL と機能的に同等にはなりません。

弱グローバル参照と PhantomReference との相互動作は未定義です。特に、Java VM の実装は、PhantomReference のあとに弱グローバル参照を処理する場合があり、PhantomReference オブジェクトでも参照されているオブジェクトを保持するために弱グローバル参照を使用することが可能な場合があります。弱グローバル参照をこのような未定義の方法で使用するのは避けるべきです。

jweak NewWeakGlobalRef(JNIEnv *env, jobject obj);

弱グローバル参照を新規作成します。objnull を参照している場合、または VM がメモリーを使い果たしてしまった場合は、NULL を返します。VM がメモリーを使い果たしてしまった場合は、OutOfMemoryError がスローされます。

void DeleteWeakGlobalRef(JNIEnv *env, jweak obj);

渡された弱グローバル参照に必要な VM リソースを削除します。

配列操作

JDK 1.1 では、プログラマは Get/Release<PrimitiveType>ArrayElements 関数を使用して、プリミティブ配列要素へのポインタを取得できます。VM がピニングをサポートしている場合、元のデータへのポインタが返されました。サポートしていない場合には、コピーが作成されました。

新しい関数を使用すると、VM がピニングをサポートしていない場合でも、ネイティブコードは配列要素への直接ポインタを取得できます。

void * GetPrimitiveArrayCritical(JNIEnv *env, jarray array, jboolean *isCopy);
void ReleasePrimitiveArrayCritical(JNIEnv *env, jarray array, void *carray, jint mode);

これら 2 つの関数のセマンティクスは、既存の Get/Release<PrimitiveType>ArrayElements 関数と非常によく似ています。可能な場合は、VM はプリミティブ配列へのポインタを返します。そうでない場合は、コピーが作成されます。ただし、これらの関数の使用方法に関して重要な制限があります。

GetPrimitiveArrayCritical を呼び出したあと、ReleasePrimitiveArrayCritical を呼び出す前に、ネイティブコードを特定の期間実行しないようにします。この 1 組の関数内部のコードは「クリティカルリージョン」で実行されているものとして扱う必要があります。クリティカルリージョン内部においてネイティブコードは、ほかの JNI 関数を呼び出してはならず、現在のスレッドにほかの Java スレッドをブロックして待機させることを可能にするどのシステムコールも呼び出してはいけません。(たとえば、現在のスレッドは、ほかの Java スレッドが書き込んでいるストリームに対して read を呼び出してはいけません。)

これらの制限は、VM がピニングをサポートしない場合でも、ネイティブコードが配列のコピーされていないバージョンを取得する可能性を高めます。たとえば、ネイティブコードが GetPrimitiveArrayCritical によって取得された配列へのポインタを保持している場合、VM は一時的にガベージコレクションを無効にすることがあります。

GetPrimtiveArrayCriticalReleasePrimitiveArrayCritical の複数のペアを入れ子にすることができます。たとえば、

  jint len = (*env)->GetArrayLength(env, arr1); 
  jbyte *a1 = (*env)->GetPrimitiveArrayCritical(env, arr1, 0);
  jbyte *a2 = (*env)->GetPrimitiveArrayCritical(env, arr2, 0);
  /* We need to check in case the VM tried to make a copy. */
  if (a1 == NULL || a2 == NULL) {
    ... /* out of memory exception thrown */
  }
  memcpy(a1, a2, len);
  (*env)->ReleasePrimitiveArrayCritical(env, arr2, a2, 0);
  (*env)->ReleasePrimitiveArrayCritical(env, arr1, a1, 0);

VM が内部的に異なる形式で配列を表す場合、GetPrimitiveArrayCritical はまだ配列のコピーを作成する可能性があります。そのため、起こり得るメモリー不足の状況に対応するために、戻り値が NULL かどうかをチェックする必要があります。

文字列操作

JDK 1.1 では、プログラマはユーザーが提供するバッファーのプリミティブ配列要素を取得できました。Java 2 SDK では、ネイティブコードを使用して、ユーザーが提供するバッファーの Unicode または UTF-8 文字を取得できます。

void GetStringRegion(JNIEnv *env, jstring str, jsize start, jsize len, jchar *buf);

オフセット start で始まる len 個の Unicode 文字を、与えられたバッファー buf にコピーします。

StringIndexOutOfBoundsException をインデックスオーバーフロー時にスローします。

< void GetStringUTFRegion(JNIEnv *env, jstring str, jsize start, jsize len, char *buf);

オフセット start で始まる len 個の Unicode 文字を UTF-8 形式に変換し、その結果を与えられたバッファー buf に置きます。

StringIndexOutOfBoundsException をインデックスオーバーフロー時にスローします。

const jchar * GetStringCritical(JNIEnv *env, jstring string, jboolean *isCopy);
void ReleaseStringCritical(JNIEnv *env, jstring string, const jchar *carray);

これら 2 つの関数のセマンティクスは、既存の Get/ReleaseStringChars 関数に似ています。可能な場合には、VM は文字列要素へのポインタを返します。そうでない場合、コピーが作成されます。ただし、これらの関数の使用方法に関して重要な制限があります。Get/ReleaseStringCritical の呼び出しによって囲まれるコードセグメントにおいて、ネイティブコードは任意の JNI 呼び出しを行なったり、現在のスレッドにブロックさせてはなりません。

Get/ReleaseStringCritical の制限は、Get/ReleasePrimitiveArrayCritical の制限に似ています。

リフレクションのサポート

プログラマは、メソッドまたはフィールドの名前および型を把握している場合、JNI を使用して Java メソッドの呼び出しまたは Java フィールドへのアクセスを行うことができます。Java Core Reflection API を使用すると、プログラマは実行時に Java クラスの内部を調査できます。JNI は、JNI で使用されるフィールドとメソッド ID および Java Core Reflection API で使用されるメソッドオブジェクトの間の変換関数のセットを提供します。

jmethodID FromReflectedMethod(JNIEnv *env, jobject method);

java.lang.reflect.Method または java.lang.reflect.Constructor オブジェクトをメソッド ID に変換します。

jfieldID FromReflectedField(JNIEnv *env, jobject field);

java.lang.reflect.Field をフィールド ID に変換します。

jobject ToReflectedMethod(JNIEnv *env, jclass cls,
   jmethodID methodID);

cls から取得したメソッド ID を java.lang.reflect.Method または java.lang.reflect.Constructor オブジェクトに変換します。

失敗した場合は OutOfMemoryError をスローし、0 を返します。

jobject ToReflectedField(JNIEnv *env, jclass cls,
   jfieldID fieldID);

cls から取得したフィールド ID を java.lang.reflect.Field オブジェクトに変換します。

失敗した場合は OutOfMemoryError をスローし、0 を返します。

呼び出し API

jint JNI_CreateJavaVM(JavaVM **pvm, void **penv, void *args);

JDK 1.1 では、JNI_CreateJavaVM への 2 番目の引数は常に JNIEnv * へのポインタです。3 番目の引数は、JDK 1.1 に固有の構造体 (JDK1_1InitArgs) へのポインタです。JDK1_1InitArgs 構造は、すべての VM に移植性のあるものとして設計されていないことは明らかです。

Java 2 SDK では、標準 VM 初期化構造が導入されます。下位互換性は保持されます。VM 初期化引数が JDK1_1InitArgs 構造を指す場合、JNI_CreateJavaVM は JNI インタフェースポインタの 1.1 バージョンを返します。3 番目の引数が JavaVMInitArgs 構造体を指す場合、VM は JNI インタフェースポインタの 1.2 バージョンを返します。固定オプションセットを含む JDK1_1InitArgs と異なり、JavaVMInitArgs はオプション文字列を使用して、任意の VM 起動オプションをエンコードします。

typedef struct JavaVMInitArgs {
    jint version;

    jint nOptions;
    JavaVMOption *options;
    jboolean ignoreUnrecognized;
} JavaVMInitArgs;
version フィールドは JNI_VERSION_1_2 に設定する必要があります。(逆に、JDK1_1InitArgs のバージョンフィールドは、JNI_VERSION_1_1 に設定する必要があります。) options フィールドは、次の型の配列です。
typedef struct JavaVMOption {
    char *optionString;
    void *extraInfo;
} JavaVMOption;
配列のサイズは、JavaVMInitArgs の nOptions フィールドに示されます。ignoreUnrecognizedJNI_TRUE の場合、JNI_CreateJavaVM は、「-X」または「_」で始まるすべての認識できないオプション文字列を無視します。ignoreUnrecognizedJNI_FALSE の場合、JNI_CreateJavaVM は認識できないオプション文字列を検出すると、ただちに JNI_ERR を返します。すべての Java 仮想マシンは、次の標準オプションのセットを認識する必要があります。
optionString 意味
-D<name>=<value> システムプロパティーを設定する
-verbose[:class|gc|jni] 冗長出力を有効にする。各オプションの後に、VM が出力するメッセージの種類を示す、コンマで区切った名前のリストを続けることができる。たとえば、「-verbose:gc,class」は、VM に GC とクラスローディング関連のメッセージを出力するように指示する。標準的な名前には、gcclass、および jni。標準でない (VM 固有の) 名前はすべて、「X」で始まる必要がある。
vfprintf extraInfo は、vfprintf フックへのポインタ。
exit extraInfo は、exit フックへのポインタ。
abort extraInfo は、abort フックへのポインタ。

加えて、各 VM 実装は、標準でない独自のオプション文字列のセットをサポートします。標準でないオプション名は、「-X」または下線 (「_」) で始まる必要があります。たとえば、Java 2 SDK は -Xms および -Xmx オプションをサポートしているため、プログラマは初期および最大のヒープサイズを指定できます。「-X」で始まるオプションは、「java」コマンド行からアクセス可能です。

次の例は、Java 2 SDK で Java 仮想マシンを作成するコードです。

JavaVMInitArgs vm_args;
JavaVMOption options[4];

options[0].optionString = "-Djava.compiler=NONE";           /* disable JIT */
options[1].optionString = "-Djava.class.path=c:\myclasses"; /* user classes */
options[2].optionString = "-Djava.library.path=c:\mylibs";  /* set native library path */
options[3].optionString = "-verbose:jni";                   /* print JNI-related messages */

vm_args.version = JNI_VERSION_1_2;
vm_args.options = options;
vm_args.nOptions = 4;
vm_args.ignoreUnrecognized = TRUE;

/* Note that in the Java 2 SDK, there is no longer any need to call 
 * JNI_GetDefaultJavaVMInitArgs. 
 */
res = JNI_CreateJavaVM(&vm, (void **)&env, &vm_args);
if (res < 0) ...

Java 2 SDK は、JDK 1.1 と厳密に同じ方法で JDK1_1InitArgs をサポートしています。

jint AttachCurrentThread(JavaVM *vm, void **penv, void *args);

JDK 1.1 では、AttachCurrentThread への 2 番目の引数は常に JNIEnv へのポインタです。AttachCurrentThread への 3 番目の引数は予約されており、NULL に設定しなければなりません。

Java 2 SDK では、3 番目の引数として NULL を渡すと 1.1 の動作をさせることができ、次の構造体へのポインタを渡すと追加情報を指定できます。

typedef struct JavaVMAttachArgs {
    jint version;  /* must be JNI_VERSION_1_2 */
    char *name;    /* the name of the thread, or NULL */
    jobject group; /* global ref of a ThreadGroup object, or NULL */
} JavaVMAttachArgs;

jint DetachCurrentThread(JavaVM *vm);

JDK 1.1 では、VM からメインスレッドを切り離すことはできません。VM 全体をアンロードするには、DestroyJavaVM を呼び出す必要があります。

Java 2 SDK では、VM からメインスレッドを切り離すことができます。

jint DestroyJavaVM(JavaVM *vm);

1.1 では、DestroyJavaVM は完全にはサポートされていませんでした。メインスレッドだけが DestroyJavaVM を呼び出すことができます。Java 2 SDK では、接続されているかいないかにかかわらず、どのスレッドもこの関数を呼び出すことができます。現在のスレッドが接続されている場合、VM は、現在のスレッドが唯一のユーザーレベル Java スレッドになるまで待機します。現在のスレッドが接続されていない場合は、VM が現在のスレッドを接続し、現在のスレッドが唯一のユーザーレベルのスレッドになるまで待機します。ただし、VM のアンロードは、Java 2 SDK でもサポートされません。DestroyJavaVM は、常にエラーコードを返します。

jint GetEnv(JavaVM *vm, void **env, jint version);

現在のスレッドが VM に接続されていない場合は、*envNULL に設定し、JNI_EDETACHED を返します。指定されたバージョンがサポートされていない場合は、*envNULL に設定し、JNI_EVERSION を返します。それ以外の場合は、*env を適切なインタフェースに設定し、JNI_OK を返します。

Copyright © 1993, 2013, Oracle and/or its affiliates. All rights reserved.