来自:JNI Design
Native code 通过调用 JNI funtions 访问 Java VM features。通过 interface pointer 获取到JNI functions。Interface pointer 是一个pointer指向一个pointer,这个pointer指向一个pointer数组,数组中的每一个元素指向一个interface funtion。Every interface funtion is at a predefined offset inside the array。
JNI interface 被组织成类似于C++ virtual function table 或者 COM interface1。使用一个interface table的好处是JNI name space和native code分离开了。VM可以很容易地提供多个版本的JNI funtion tables。例如,VM可能支持两个JNI function tables:
JNI interface pointer只有在当前线程中有效。因此,native method绝不能把interface pointer从一个线程传到另一个线程。 实现JNI的VM可能在JNI interface pointer指向的地址上分配和存储了thread-local data。
Native method接收JNI interface pointer作为一个参数。
The VM is guaranteed to pass the same interface pointer to a native method when it makes multiple calls to the native method from the same Java thread(这种情况VM保证native method获取的都是同一个JNI interface pointer)。
然而,一个native method能被多个不同的Java thread调用,因此此时native method可能获取到不同的JNI interface pointer(这种情况native method可能获取到不同的JNI interface pointer)。
Java VM是多线程的,native libraries 也应该使用multithread aware native compilers编译和链接。使用GNU gcc compiler时,需要使用flag -D_REENTRANT和 -D_POSIX_C_SOURCE。
使用 System.loadLibrary()方法加载native method。
Eg:
package pkg;
class Cls {
native double f(int i, String s);
static {
// Solaris系统时,pkg_Cls应该使用libpkg_Cls.so替换;
// Win32系统时,pkg_Cls应该使用pkg_Cls.dll替换。
System.loadLibrary(“pkg_Cls”);
}
}
动态链接器基于name解析entry。Native method name由下面部分组成:
VM匹配属于native library的methods来检查method name。VM首先查看method的short name(without the argument signature),然后查看long name(with the argument signature)。当一个native method重载另一个native method时,我们需要使用long name,但是native method和Java method之间相互不影响,见下例:
class Cls1 {
// Java method
int g(int i);
// Native method,它和上面的Java method名字一样,但是相互不影响
native int g(double d);
}
JNI interface pointer是native method的第一个argument,JNI interface pointer是JNIEnv类型。第二个argument是依赖于native method是否是static的来区分的。nonstatic native method的第二个argument是这个对象的引用,static native method的是its Java class的引用。剩下的arguments符合常规的Java method argument,Java和C常规类型的对应关系见JNI Types and Data Structures
示例:
Java
package pkg;
class Cls {
native double f(int i, String s);
// ...
}
C
jdouble Java_pkg_Cls_f__ILjava_lang_String_2 ( // 方法名参考Resolving Native Method Name
JNIEnv *env, /* interface pointer */ // JNI interface pointer
jobject obj, /* "this" pointer */ // 此Native method是nonstatic的,第二个argument是这个对象的引用
jint i, /* argument #1 */
jstring s) /* argument #2 */
{
/* Obtain a C-copy of the Java string */
const char *str = (*env)->GetStringUTFChars(env, s, 0);
/* process the string */
...
/* Now we are done with str */
(*env)->ReleaseStringUTFChars(env, s, str);
return ...
}
基本数据类型是在Java和Native code之间直接复制的,而对于任意的Java对象(Objects)是传引用的。VM必须监控所有传给native code的对象的动态,这些对象是不能被GC回收释放的。Native code必须有一种方式把它不再需要的对象通知给VM。此外,GC必须能移动被native code引用的对象。
JNI把native code使用的对象引用分为两类:Local References 和 Global References。Local reference在native code调用期间是有效的,当native method返回后local reference会被自动释放。Global reference会一直保持有效,直到Global reference被显式的释放掉。
Local Reference:
Global Reference:
JNI functions that expect Java objects accept both global and local references. A native method may return a local or global reference to the VM as its result.
大部分情况,当native method返回后,开发者依赖VM释放所有的local reference。然而,有些情况需要开发者显式地释放local reference。
例如:
JNI允许开发者在native method任何一个点手动地删除local reference。为了确保开发者能手动地释放local reference, 不允许JNI function创建额外的local reference,除非是它们返回的结果。
Local reference仅仅在创建它们的线程里有效。Native code不能把local reference从一个线程传到另一个线程。
为了实现Local reference,Java VM为每个从Java到native method的控制转换创建了一个注册表(To implement local references, the Java VM creates a registry for each transition of control from Java to a native method)。注册表映射nonmovable local references到Java Objects,并保持Objects被GC。传到native method的所有的Java Objects(包括那些JNI function返回的结果)会自动地添加到注册表中。当native method返回后,这个注册表会被删除,并且允许所有的实体被回收。
我们可以使用table,linked list或者hash table来实现注册表。尽管在注册表中可以使用引用数来避免出现重复的实体,但是JNI实现不是必须要检查并销毁重复的实体。
Note that local references cannot be faithfully implemented by conservatively scanning the native stack. The native code may store local references into global or heap data structures.
JNI为global reference和local reference提供了一系列的accessor functions(存取函数)。
这个开销是不接受包含许多原始数据类型的大型Java对象,例如 integer数组和string。
native method可以使用“pinning”的方式让VM确定数组里的内容,然后native method收到直接指向元素的指针。但是这种方法有两个含义:
我们使用一种妥协的方式解决了上面的问题:
JNI implementation必须确保运行在多个线程中的native method能同时访问同一个数组。不同的线程同时更新Java数组可能会导致不确定的结果。
JNI允许native code访问Fields和调用Java对象的方法。JNI通过symbolic names和type signatures识别methods和fields。
// 调用cls类中的f方法获取method ID
jmethodID mid = env->GetMethodID(cls, “f”, “(ILjava/lang/String;)D”);
// 使用上面的method ID
jdouble result = env->CallDoubleMethod(obj, mid, 10, str);
链接:Java Exceptions