JDK 1.2 で拡張された JNI の機能

(11/2/98)

JDK 1.2 で JNI は次のように拡張されます。

加えられる変更は、ライセンス保持者やユーザからのコメントに基づいています。コメント、提案、関心のある点について、jni@java.sun.com までお知らせください。

新しい定数

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

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

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

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

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

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

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

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

新しい 1.2 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 Virtual Machine のリソースをいくらか消費します。プログラマは、ネイティブメソッドがローカル参照を過剰に割り当てないように確認する必要があります。ローカル参照は、ネイティブメソッドが Java に復帰すると自動的に解放されますが、ローカル参照を過剰に割り当てると、ネイティブメソッドの実行中に VM がメモリを使い果たしてしまう可能性があります。

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

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

jint EnsureLocalCapacity(JNIEnv *env, jint capacity);

現在のスレッドで最低限指定された数のローカル参照を作成できることを保証します。関数が正常に実行された場合には、0 が返されます。それ以外の場合には、負の数が返され OutOfMemoryError がスローされます。

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

下位互換性のために VM は、保証された容量以上にローカル参照を割り当てます。デバッグサポートで、VM はローカル参照が過剰に作成されていることをユーザに警告する場合があります。JDK 1.2 では、プログラマはコマンド行オプション -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 の弱グローバル参照は、コア 1.2 API (java.lang.Ref およびそれに関連したクラス) の一部として入手可能な Java 弱参照の簡易版です。

jweak NewWeakGlobalRef(JNIEnv *env, jobject obj);

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

void DeleteWeakGlobalRef(JNIEnv *env, jweak obj);

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

配列操作

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

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

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

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

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

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

GetPrimtiveArrayCritical および ReleasePrimitiveArrayCritical の複数の組み合わせは、次の例に示すように入れ子にすることができます。

  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 では、プログラマはユーザが提供するバッファのプリミティブ配列要素を取得できました。JDK 1.2 ではネイティブコードを使用してユーザが提供するバッファの 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 に移植性のあるものとして設計されていないことは明らかです。

JDK 1.2 では、標準 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 VM は、次の標準オプションのセットを認識する必要があります。

optionString 説明
-D<name>=<value> システムプロパティを設定する
-verbose[:class|gc|jni] 冗長出力を有効にする。オプションのあとには、VM が出力するメッセージの種類を示す名前のリストをカンマで区切って指定する。たとえば、「-verbose:gc,class」は、GC およびクラスのロードに関連したメッセージを出力するように VM に指示する。標準的な名前には、gcclass、および jni が含まれる。標準でない (VM 固有の) 名前はすべて、「X」で始める必要がある
vfprintf extraInfo は、vfprintf フックへのポインタ
exit extraInfo は、exit フックへのポインタ
abort extraInfo は、abort フックへのポインタ

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

次の例は、JDK 1.2 で Java VM を作成するコードです。

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 JDK 1.2, there is no longer any need to call 
 * JNI_GetDefaultJavaVMInitArgs. 
 */
res = JNI_CreateJavaVM(&vm, (void **)&env, &vm_args);
if (res < 0) ...

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

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

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

JDK 1.2 で、1.1 の動作をさせる場合には 3 番目の引数として null を渡します。または次の構造にポインタを渡して、追加情報を指定することができます。

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 を呼び出す必要があります。

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

jint DestroyJavaVM(JavaVM *vm);

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

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

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

コメントの送付先: jni@java.sun.com
最終更新: 1998 年 11 月 2 日 (月) 21:37:14 PST