Java

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

JNI ドキュメント

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

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 セキュリティモデルは拡張され、システムクラス以外のクラスによるネイティブメソッドのロードおよび呼び出しが可能になりました。 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 は一時的にガベージコレクションを無効にすることがあります。

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

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

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

jint DestroyJavaVM(JavaVM *vm);

JDK 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 © 1995-99 Sun Microsystems, Inc. All Rights Reserved.

コメントの送付先: jni@java.sun.com
Sun