Java Native Interface
Java Native Interface (JNI) は、Javaプラットフォームにおいて、Javaで記述されたプログラムと、他のプログラミング言語（たとえばCやC++など）で書かれた、実際のCPU上で動作するコード（ネイティブコード）とを連携するためのインタフェース仕様である。Java言語からネイティブコードを利用するためのABIと、逆にネイティブコードからJavaバイトコードを動作させるためにバーチャルマシン (VM) を利用するためのAPIの2つから成る。
JNIを使うことで、Java言語のVMで動作させるには処理速度の面で不利とされる計算量の多いプログラムを部分的にネイティブコードに置き換えて高速化したり、標準Javaクラスライブラリからはアクセスできないオペレーティングシステムあるいはハードウェアの機能を利用するプログラムを、あたかも通常のJavaクラスのメソッドのように呼び出したりできるようになる。逆に、Javaクラスライブラリによって実装されている高水準の機能を、C/C++などで書かれた下位層から利用することもできるようになる。JNIはJava言語以外のJava VM上で動作する言語 (AltJava) からも利用可能である。
JNIによる、Java VMからのネイティブコードの呼び出しは、VMの実行環境の一貫性を保つために、通常のJavaプログラムの実行時とは異なる例外的なメモリ管理や排他制御を必要とする場合があり、しばしばプログラムの実行速度の低下を招くことがある。そのため、単純にJNIを利用することで必ずしもアプリケーション性能を改善できるというわけではない。また、ネイティブコードを含むバイナリはプロセッサアーキテクチャやオペレーティングシステムに依存してしまうことになるため、Javaのみで記述されたプログラムと比べて再利用や再配布の面で難がある。
以下ではC/C++言語によるネイティブコードの記述例を用いて説明する。なおJNI関連のシンボル定義や関数宣言を含むヘッダーファイルは、Java Development Kit (JDK) に付属している。
JNIフレームワークでは、ネイティブ関数はC/C++のソースファイル (拡張子は.cもしくは.cppなど) に分離して実装する。Java VMは、codice_1へのポインタ、コンテキストとしてJavaクラスもしくはクラスインスタンスを間接参照するポインタであるcodice_2、そしてJavaメソッドで定義されたすべてのJava引数を通して、ネイティブな関数を起動する。JNI関数は以下のようなC言語形式関数となる。C++のシグネチャは使えない。
JNIEXPORT void JNICALL
Java_PackageName_ClassName_MethodName
codice_1はJava VMへのインタフェースを含む構造体codice_4へのポインタである。これはJava VMとの相互作用および、Javaオブジェクトとの連携に必要な全ての関数ポインタを含んでいる。JNI関数の例としては、ネイティブ配列とJava配列の相互変換、ネイティブ文字列とJava文字列の相互変換、オブジェクトのインスタンス化、例外の送出などが挙げられる。基本的には、Javaコードでできることは全てcodice_1を用いて行うことができる。
以下の例ではJava文字列をネイティブな文字列に変換し、C/C++の標準ライブラリを利用して標準出力に出力する。
/* C code */
JNIEXPORT void JNICALL
Java_com_example_TestClass_printString
// C++ code
extern "C" {
JNIEXPORT void JNICALL
Java_com_example_TestClass_printString
上記のようにしてCまたはC++で記述した関数が "myjni" ライブラリモジュールに実装されていると仮定し、Javaプログラムから呼び出す例を示す。C/C++側の実装関数とJava側のメソッドバインディングは実行時に動的に解決される。
package com.example;
public class TestClass {
オブジェクト指向言語であるC++に対しては、Cよりも多少洗練されたJNI APIが提供される。C++ではJavaのように、オブジェクトのメソッド（メンバー関数）という概念を持つため、C++のJNIコードはCのそれに比べて文法的に多少簡潔である。
Cではcodice_6パラメータはcodice_7で被参照され、さらにcodice_6パラメータはオブジェクトメソッド起動セマンティクスの一部として、明示的にcodice_1メソッドに渡されなければならない。
C++ではcodice_6パラメータはcodice_11で被参照されるが、codice_6パラメータはオブジェクトメソッド起動セマンティクスの一部として暗黙的に渡される。
以降のコード例では、断りがない限りC++を前提とするものとする。
一部のネイティブ変数型とJava変数型はそれぞれ相互変換をすることができる。オブジェクトや配列、文字列などの合成型のために、ネイティブコードはcodice_1のメソッド呼び出しにおいて、明示的なデータの変換を行う必要がある。
以下の表ではJavaとネイティブ間の変数型のマッピングを示す。C/C++の型はヘッダーにてtypedefにより定義されたエイリアスであり、実装は処理系依存である。
JNIでJavaの型を識別する概念として、「型シグネチャ」が定義されている。
"L "fully-qualified-class" ;"というシグネチャは名前によって一意に識別されるJavaクラスを意味する。
例えば文字列codice_14はクラスを意味する。また、接頭辞codice_15はその型の配列を意味する。例えば、codice_16はJavaにおけるcodice_17型の配列すなわちcodice_18に相当する。
表中の対応する型同士は暗黙的に相互変換可能である。実際の変換はJNIレイヤーが担う。プログラマーは型キャスト無しで、Javaのcodice_17型と同様にcodice_20を使用することができる。codice_20型からJavaのcodice_17型への変換も同様である。
なお、codice_23はJavaのcodice_24型への間接参照であり、C/C++のポインタ型codice_25とは無関係である。
// !!! 誤ったコード !!! //
JNIEXPORT void JNICALL
Java_com_example_TestClass_printString
// 正しいコード //
JNIEXPORT void JNICALL
Java_com_example_TestClass_printString
このことはJava配列についても同様である。例えばcodice_26はJavaのcodice_18型への間接参照であり、C/C++のポインタ型codice_28とは無関係である。
配列の全ての要素の合計を得る例を用いて以下に示す。
// !!! 誤ったコード !!! //
JNIEXPORT jint JNICALL
Java_TestClass_sumIntArray
// 正しいコード //
JNIEXPORT jint JNICALL
Java_TestClass_sumIntArray
Javaのcodice_29はC/C++のcodice_30ポインタにマッピングされる。
codice_24型インスタンスやcodice_32型インスタンスなどのJavaオブジェクト参照をcodice_33型変数に代入可能であるように、codice_23やcodice_35などの「JNIオブジェクト参照」もまたcodice_2型変数に代入可能である。
JNIのAPI関数の多くはJava VM環境変数として、JNIEnvへのポインタを受け取る。
しかしながら、Javaからnativeメソッドを呼び出す際に、C/C++実装側の関数に引数として渡ってくるJNIEnvは、JNI呼び出しの間のみ有効となる。つまりスタックフレームの制御フローがJava側に戻ると無効になる。
またJNIEnvはスレッド間で共有できない。そのため、Java VMに関連付けられていないC/C++スレッド上でJNIEnvを取得するためには、以下のようにAttachCurrentThread()関数を使用してスレッドをVMにアタッチする必要がある。スレッドでJNIEnvが必要なくなったとき、あるいはスレッドを終了する際にDetachCurrentThread()関数を使用してスレッドをVMからデタッチする。
JavaVM *g_vm; // JNI_GetCreatedJavaVMs() を使って取得済みとする。
JNIEnv *env = NULL;
// 現在のスレッドを VM にアタッチする。
g_vm->AttachCurrentThread((void **)&env, NULL);
// 取得した JNIEnv を使って JNI 関数を実行する。
// 現在のスレッドを VM からデタッチする。
g_vm->DetachCurrentThread();
すでにJava VMに関連付けられているC/C++スレッド上でJNIEnvを取得するためには、GetEnv()関数を呼び出すだけでよい。
JNIEnv *env = NULL;
g_vm->GetEnv((void **)&env, JNI_VERSION_1_6);
JNIを経由することで、C/C++からJavaのクラスを利用することもできる。
C/C++からJNI関数を用いてJavaクラスをインスタンス化したり、メソッドを呼び出したり、フィールドを読み書きしたりするためには、まずJavaクラス (codice_35) およびメソッドID (codice_38) あるいはフィールドID (codice_39) を探索・取得する必要があるが、その際に前述の「型シグネチャ」の文字列が識別子として使用される。
以下はクラスの静的フィールドを取得し、静的メソッドを呼び出す例である。
void DoJniTest(JNIEnv *env) {
FindClass()関数で取得したcodice_35はJavaクラスへのJNIオブジェクト参照である。クラス、フィールド、メソッドの型シグネチャをもとに、リフレクションの要領でC/C++からアクセスすることができる。クラス、フィールド、メソッドを繰り返し利用する場合は、探索にかかる時間を節約するためにキャッシュしておく。
JNI関数が返却するJNIオブジェクト参照は、Javaオブジェクトへの「ローカル参照」を表すポインタである。これはJavaのローカル変数に相当する。ローカル参照はスタックフレームの制御フローがJava側に返る際にガベージコレクションによって自動的に破棄されるが、DeleteLocalRef()関数を使って手動で削除することもできる。
JNIオブジェクト参照を長期間保持する場合は、「グローバル参照」を使わなければならない。
具体的には、C/C++側のグローバル変数などのヒープ領域にcodice_2を格納する場合や、スレッド間でcodice_2を共有する場合に、ローカル参照ではなくグローバル参照を用いる。
Javaのスレッドから呼ばれたC関数内でローカル参照を作成した場合、スタックフレームの制御フローがJava側に戻った時点でローカル参照は自動解放される。また、C/C++のスレッド上でローカル参照を作成した場合、スレッドがJVMからデタッチされた時点でローカル参照は自動解放される。一方、グローバル参照は自動解放されず、DeleteGlobalRef()関数で明示的に削除する必要がある。
NewGlobalRef()関数を呼び出すことで、ローカル参照からグローバル参照を生成することができる。
グローバル参照はヒープメモリが許す限り自由に作成可能だが、ローカル参照の同時利用可能個数には上限がある。JNI仕様で保証されているのは少なくとも16個ということだけであり、JVMのスタック容量に依存する。
JNI参照はJavaのヒープに対する直接のポインタではなく、メモリコンパクションによるJavaオブジェクトのアドレス移動の影響を受けない。