目录
1 JNI概述
本章将介绍了Java Native interface(JNI)。JNI是一个native编程接口。它允许在Java虚拟机(VM)中运行的Java代码与用其他编程语言(如C、C++)编写的应用程序和库进行互操作。
JNI最重要的好处是,它对底层Java VM的实现没有施加任何限制。因此,Java VM 厂商可以添加对JNI的支持,而不影响VM的其他部分。程序员可以编写一个本地应用程序或库的版本,并期望它与支持JNI的所有Java VM一起工作。
在某些情况下,Java不能满足应用程序的需求。程序员使用JNI编写Java native方法来处理当应用程序不能完全用Java编写时的那些情况。
下面的示例说明了您何时需要使用Java native方法:
-
-
- 标准的Java类库不支持应用程序所需的依赖于平台的特性;
- 已经有了一个用另一种语言编写的库,并希望通过JNI让Java代码访问它;
- 希望用C/C++等程序集语言实现一小部分对性能要求高的关键性代码;
-
通过通过JNI进行编程,您可以使用native方法:
-
-
- 创建、检查和更新Java对象(包括数组和字符串);
- 调用java方法;
- 捕获及上抛异常;
- 加载类及获取类信息;
- 执行运行时类型检查;
-
还可以通过调用API使JNI让任意的native应用程序嵌入至Java VM。这允许程序员能轻松地使用他们现有的应用程序,而不必链接到VM源代码。
2 设计概述
2.1 JNI的接口函数与接口指针
native代码通过调用JNI函数来访问Java VM特性。可以通过一个接口指针来使用JNI函数。一个接口指针是一个指向一个指针(Pointer X)的指针。这个指针(Pointer X)指向一个指针数组,每个指针都指向一个接口函数。每个接口函数都位于数组内部的预定义偏移量处。下图说明了接口指针的组织结构:
JNI接口的管理类似于C++虚拟函数表或COM接口。使用接口表而不是硬连接函数项的优点是,JNI命名空间与native代码是独立的。虚拟机可以轻松地提供多个版本的JNI函数表。例如,VM可能支持两个JNI函数表:
-
-
- 一个是执行彻底的非法参数检查,适合于调试;
- 另一种是执行JNI规范所要求的最小的检查量,因此更有效;
-
JNI接口指针仅在当前线程中有效。因此,一个native方法不能将接口指针从一个线程传递到另一个线程。实现JNI的虚拟机可以在JNI接口指针指向的区域内分配和存储线程本地数据。
native方法接收JNI接口指针作为参数。当VM从同一个Java线程多次调用native方法时,它可以保证将相同的接口指针传递给native方法。但是,可以从不同的Java线程调用native方法,因此可能接收到不同的JNI接口指针。即native方法会涉及多线程操作,需要考虑并发情况。
2.2 Native方法的编译、加载、链接
由于JavaVM是多线程的,所以native库也应该使用带多线程属性的编译器进行编译和链接。例如对于GNU GCC编译器,应该使用-D_REENTRANT或-D_POSIX_C_SOURCE标识来进行编译。
使用System.loadLibrary()方法加载native库(静态库、动态库)。在下面的示例中,类初始化方法加载一个特定于平台的native库,库中定义了native方法f:
package p.q.r;
class A {
native double f(int i, String s);
static {
System.loadLibrary("p_q_r_A");
}
}
System.loadLibrary的参数是由程序员任意选择的一个库名。
程序员可以使用单个库来存储任意数量的类所需的所有本机方法,只要这些类要用相同的类加载器来加载即可。VM内部为每个类加载器维护一个加载native库列表。native名称应尽量减少名称冲突。同时支持动态链接库和静态链接的库。
2.2.1 解析Native方法名称
JNI定义了一个从在Java中的native方法声明到位于native库中的native方法名的1:1映射关系。VM使用此映射,动态地将Java调用的native方法链接到native库中相应的native实现。
映射通过以下组件来生成native方法名:
-
-
- 前缀(Java_);
- 给定声明native方法的类的内部形式的二进制名称:转义该名称的结果;
- 下划线(“_”);
- 转义方法名称;
- 如果native方法声明重载:两个下划线(“__”),后面是方法声明的转义参数描述符;
-
转义使每个字母数字ASCII字符(A-Za-z0-9)保持不变,并用相应的转义序列替换下表中的每个UTF-16代码单元。如果要转义的名称包含一个代理对,则高代理代码单元和低代理代码单元将分别转义。转义的结果是一个只由ASCII字符a-Za-z0-9和下划线("_")组成的字符串。
转义是必要的,原因有二。首先,为了确保Java源代码中可能包含Unicode字符的类和方法名,可以转换为C源代码中的有效函数名。其次,为了确保使用“;”和“[”字符作为参数类型的native方法可以在C函数名编码。
当Java程序调用native方法时,VM会首先搜索native方法名称的简短版本,即没有转义参数签名的名称。如果没有找到具有短名称的native方法,则VM将查找native方法名称的长版本,即包含转义参数签名的名称。
首先查找较短的名称可以使在native库中声明实现更容易。例如,给定了Java中的这个native方法:
package p.q.r;
class A {
native double f(int i, String s);
}
对应的C函数可以被命名:Java_p_q_r_A_f,而不是Java_p_q_r_A_f__ILjava_lang_String_2
只有当一个类中的两个或多个native方法具有相同的名称时,才需要在native库中使用长名称来声明实现。例如,给定Java中的这些本机方法:
package p.q.r;
class A {
native double f(int i, String s);
native double f(int i, Object s);
}
对应的C函数命名Java_p_q_r_A_f__ILjava_lang_String_2和Java_p_q_r_A_f__ILjava_lang_Object_2
如果Java中的native方法只被非native方法重载,则不需要native库中的长名称。
native方法也可以使用RegisterNatives函数显式链接。请注意,RegisterNatives可以通过更改为给定Java方法执行的native代码来更改VM的行为(包括加密算法、正确性、安全性、类型安全性)。因此,谨慎使用使用RegisterNatives函数的native库的应用程序。
2.2.2 Native方法的形参
JNI接口指针是指向native方法的第一个参数。JNI接口指针的类型是JNIEnv。第二个参数的不同取决于native方法是静态的还是非静态的。对非静态native方法的第二个参数是对该对象的引用。静态native方法的第二个参数是对其Java类的引用。
其余的参数分别对应于常规的Java方法参数。native方法调用通过返回值将其结果传递回调用例程。
下面的代码示例说明了使用C函数来实现本机方法f。native方法f的声明如下:
package p.q.r;
class A {
native double f(int i, String s);
// ...
}
长名为Java_p_q_r_A_f_ILjava_lang_String_2的C函数实现了本机方法f:
jdouble Java_p_q_r_A_f__ILjava_lang_String_2 ( JNIEnv *env, /* interface pointer */
jobject obj, /* "this" pointer */
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 ...;
}
请注意,我们总是使用接口指针env来操作Java对象。
使用C++,您可以编写一个稍微干净一些的代码版本,如下面的代码示例所示
extern "C" /* specify the C calling convention */
jdouble Java_p_q_r_A_f__ILjava_lang_String_2 ( JNIEnv *env, /* interface pointer */
jobject obj, /* "this" pointer */
jint i, /* argument #1 */
jstring s) /* argument #2 */
{
const char *str = env->GetStringUTFChars(s, 0);
...;
env->ReleaseStringUTFChars(s, str);
return ...
}
使用C++时,额外的间接级别和接口指针参数将从源代码中消失。然而,其潜在机制与C完全相同。在C++中,JNI函数被定义为扩展到C对应函数的内联成员函数。
2.3 引用Java对象
原始类型,如整数、字符等,可以在Java和本机代码之间进行复制。另一方面,任意的Java对象都是通过引用来传递的。VM必须跟踪已传递给本机代码的所有对象,以便垃圾收集器不会释放这些对象。反过来,本机代码必须有一种方法来通知VM它不再需要这些对象。此外,垃圾收集器必须能够移动native代码引用的对象。
2.3.1 全局引用和局部引用
JNI将native代码使用的对象引用分为两类:本地引用和全局引用。本地引用在native方法调用期间有效,并且在native方法返回后自动释放。全局引用在它们被显式释放之前一直保持有效。
对象将作为本地引用传递给native方法。由JNI函数返回的所有Java对象都是本地引用。JNI允许程序员从本地引用中创建全局引用。JNI函数期望Java对象同时接受全局引用和本地引用。native方法可以返回对VM的本地或全局引用作为其结果。
在大多数情况下,程序员应该依赖VM在native方法返回后释放所有本地引用。但是,有时程序员应该显式地释放一个本地引用。例如,请考虑以下情况:
-
- native方法访问大型Java对象,从而创建对Java对象的本地引用。然后,native方法在返回给调用者之前执行额外的计算。对大型Java对象的本地引用将防止该对象被垃圾收集,即使该对象在计算的其余部分中不再使用;
- native方法创建大量的本地引用,尽管不是所有引用都同时使用。由于虚拟机需要一定数量的空间来跟踪本地引用,因此创建过多的本地引用可能会导致系统内存不足;
JNI允许程序员在native方法中的任何地方手动删除本地引用。为了确保程序员可以手动释放本地引用,JNI函数不允许创建额外的本地引用,除了它们作为结果返回的引用之外。本地引用仅在创建它们的线程中有效。native代码不能将本地引用从一个线程传递到另一个线程。
2.3.2 本地引用的实现
为了实现本地引用,Java VM为从Java到native方法的每个控制转换创建一个注册表。注册表将不可移动的本地引用映射到Java对象,并防止这些对象被垃圾收集。传递给native方法的所有Java对象(包括那些作为JNI函数调用的结果返回的对象)都将自动添加到注册表中。在native方法返回后删除注册表,允许垃圾收集其所有条目。
有不同的方法来实现注册表,例如使用表、链接列表或哈希表。尽管引用计数可用于避免注册表中的重复项,但JNI实现没有义务检测和折叠重复项。
2.4 访问Java对象
JNI对全局和本地引用提供了一组丰富的访问器函数。这意味着无论VM如何在内部表示Java对象,相同的native方法实现都可以工作。这也是为什么JNI可以被各种VM实现所支持的一个关键原因。
通过使用不透明的引用访问器函数的开销高于直接访问C数据结构的开销。但在大多数情况下,Java程序员使用native方法来执行重要的任务,我们认为这个接口的开销可以被接受。
2.4.1 访问原始数组
对于包含许多原始数据类型的大型Java对象,如整数数组和字符串,这种开销是不可接受的(比如用于执行vector和矩阵计算的native方法)。遍历Java数组并使用函数调用检索每个元素的效率非常低。
一种解决方案引入了“pinning”(固定)的概念,这样native方法就可以要求虚拟机查明数组的内容。然后,本机方法会接收到一个指向这些元素的直接指针。然而,这种方法有两个含义:
-
- 垃圾收集器(GC)必须支持"pinning";
- VM必须在内存中连续地布局原始数组。虽然大多数原始数组是这样布局的,但布尔型数组可以实现为打包或解打包。因此,依赖于精确布局的布尔型数组的native代码将不可移植的。
我们采取了一种克服了上述两个问题的折衷方案:
-
- 首先,我们提供了一组函数来在Java数组的一个段区间和native内存缓冲区之间复制基本数组元素。如果native方法只需要访问大数组中的少量元素,请使用这些函数。
- 其次,程序员可以使用另一组函数来检索固定版本元素的数组。请记住,这些函数可能需要JavaVM来执行存储分配和复制。这些函数是否实际上复制了数组,这取决于VM的实现:
-
-
- 如果垃圾收集器支持固定,并且数组的布局与native方法的预期相同,则不需要复制。
- 否则,数组将复制到不可移动的内存块(例如,在C堆中),并执行必要的格式转换。此时将返回一个指向该副本的指针。
-
-
- 最后,接口提供功能,通知VM native代码不再需要访问数组元素。当您调用这些函数时,系统要么取消固定数组,要么将原始数组与其不可移动的副本进行协调,并释放副本。
JNI实现必须确保在多个线程中运行的native方法可以同时访问同一个数组。例如,JNI可以为每个固定的数组保留一个内部计数器,这样一个线程就不会取消被另一个线程固定的数组。请注意,JNI不需要锁定基本数组以通过native方法进行独家访问。同时从不同的线程更新一个Java数组会导致不确定性的结果。
2.4.2 访问字段和方法
JNI允许native代码访问Java对象的字段及方法调用。JNI通过其符号名称和类型签名来标识方法和字段。两步过程的成本是从名称和签名中定位字段或方法。例如,要调用类cls中的方法f,native代码首先获得一个方法ID,如下所示:
jmethodID mid = env->GetMethodID(cls, "f", "(ILjava/lang/String;)D");
然后,native代码可以重复使用方法ID,而无需花费方法查找的成本,如下所示:
jdouble result = env->CallDoubleMethod(obj, mid, 10, str);
字段或方法ID不会阻止VM卸载已生成该ID的类。卸载类后,方法或字段ID将无效。因此,如果它打算长期使用一种方法或字段ID,native代码必须确保:
-
- 保留对底层类的实时引用;
- 或重新获取方法或字段ID;
JNI没有对内部实现字段和方法id施加任何限制。
2.4.2.1 调用敏感方法
少数的Java方法有一个特殊的属性,称为敏感性方法(caller sensitivity)。敏感方法可以根据其调用者的身份进行不同的行为。
当native代码调用这样的方法时,调用堆栈上可能没有任何Java调用者。程序员有责任知道从它们的native代码中调用的Java方法是否是敏感方法,以及如果没有Java调用者,这些敏感方法将如何响应。如果有必要,程序员可以提供Java代码来调用native代码,进而调用原始的Java方法。
2.5 报告程序错误
JNI不检查编程错误,如传递空指针或非法的参数类型。JNI不检查这些编程错误,原因如下:
-
- 强制JNI函数检查所有可能的错误条件会降低正常(正确的)native方法的性能。
- 在许多情况下,没有足够的运行时类型信息来执行这种检查。
程序员不能将错误类型的非法指针或参数传递给JNI函数。这样做可能会导致任意的后果,包括损坏的系统状态或VM崩溃。
2.6 Java异常
JNI允许native方法引发任意的Java异常。native代码还可以处理突出的Java异常。未被处理的Java异常将被传播回VM。
3 JNI类型和数据结构
本章讨论了JNI如何将Java类型映射到native C语音类型。
3.1 原始类型
下表描述了Java原始类型及其依赖于机器的native等价物
为方便起见,我们提供了以下定义:
#define JNI_FALSE 0
#define JNI_TRUE 1
jsize整数类型用于描述大小:
typedef jint jsize;
3.2 引用类型
JNI包括许多对应于不同类型的Java对象的引用类型。JNI引用类型被组织在以下层次结构中:
在C中,所有其他的JNI引用类型都被定义相同类型:jobject。例如:
typedef jobject jclass;
在C++中,JNI引入了一组虚拟类来加强子类型关系。例如
class _jobject {};
class _jclass : public _jobject {};
// ...
typedef _jobject *jobject;
typedef _jclass *jclass;
3.3 字段和方法ID
方法和字段id是常规的C指针类型。
struct _jfieldID; /* opaque structure */
typedef struct _jfieldID *jfieldID; /* field IDs */
struct _jmethodID; /* opaque structure */
typedef struct _jmethodID *jmethodID; /* method IDs */
3.4 Value值类型
jvalue联合类型被用作参数数组中的元素类型。其声明如下:
typedef union jvalue {
jboolean z;
jbyte b;
jchar c;
jshort s;
jint i;
jlong j;
jfloat f;
jdouble d;
jobject l;
} jvalue;
3.5 类型签名
JNI使用Java VM对类型签名的表示。下表显示了这些类型的签名。
例如,Java方法:
long f(int n, String s, int[] arr);
具有以下类型签名:
4 JNI函数
4.1 接口功能表
每个函数都可以通过JNIEnv参数以一个固定的偏移量进行访问。JNIEnv类型是一个指向存储所有JNI函数指针的结构的指针。其定义如下:
typedef const struct JNINativeInterface *JNIEnv;
VM初始化函数表,如下代码示例所示。请注意,前三个条目是为将来与COM兼容而保留的。
请注意,该函数表可以在所有JNI接口指针之间进行共享。
const struct JNINativeInterface ... = {
NULL,
NULL,
NULL,
NULL,
GetVersion,
DefineClass,
FindClass,
FromReflectedMethod,
FromReflectedField,
ToReflectedMethod,
GetSuperclass,
IsAssignableFrom,
ToReflectedField,
Throw,
ThrowNew,
ExceptionOccurred,
ExceptionDescribe,
ExceptionClear,
FatalError,
PushLocalFrame,
PopLocalFrame,
NewGlobalRef,
DeleteGlobalRef,
DeleteLocalRef,
IsSameObject,
NewLocalRef,
EnsureLocalCapacity,
AllocObject,
NewObject,
NewObjectV,
NewObjectA,
GetObjectClass,
IsInstanceOf,
GetMethodID,
CallObjectMethod,
CallObjectMethodV,
CallObjectMethodA,
CallBooleanMethod,
CallBooleanMethodV,
CallBooleanMethodA,
CallByteMethod,
CallByteMethodV,
CallByteMethodA,
CallCharMethod,
CallCharMethodV,
CallCharMethodA,
CallShortMethod,
CallShortMethodV,
CallShortMethodA,
CallIntMethod,
CallIntMethodV,
CallIntMethodA,
CallLongMethod,
CallLongMethodV,
CallLongMethodA,
CallFloatMethod,
CallFloatMethodV,
CallFloatMethodA,
CallDoubleMethod,
CallDoubleMethodV,
CallDoubleMethodA,
CallVoidMethod,
CallVoidMethodV,
CallVoidMethodA,
CallNonvirtualObjectMethod,
CallNonvirtualObjectMethodV,
CallNonvirtualObjectMethodA,
CallNonvirtualBooleanMethod,
CallNonvirtualBooleanMethodV,
CallNonvirtualBooleanMethodA,
CallNonvirtualByteMethod,
CallNonvirtualByteMethodV,
CallNonvirtualByteMethodA,
CallNonvirtualCharMethod,
CallNonvirtualCharMethodV,
CallNonvirtualCharMethodA,
CallNonvirtualShortMethod,
CallNonvirtualShortMethodV,
CallNonvirtualShortMethodA,
CallNonvirtualIntMethod,
CallNonvirtualIntMethodV,
CallNonvirtualIntMethodA,
CallNonvirtualLongMethod,
CallNonvirtualLongMethodV,
CallNonvirtualLongMethodA,
CallNonvirtualFloatMethod,
CallNonvirtualFloatMethodV,
CallNonvirtualFloatMethodA,
CallNonvirtualDoubleMethod,
CallNonvirtualDoubleMethodV,
CallNonvirtualDoubleMethodA,
CallNonvirtualVoidMethod,
CallNonvirtualVoidMethodV,
CallNonvirtualVoidMethodA,
GetFieldID,
GetObjectField,
GetBooleanField,
GetByteField,
GetCharField,
GetShortField,
GetIntField,
GetLongField,
GetFloatField,
GetDoubleField,
SetObjectField,
SetBooleanField,
SetByteField,
SetCharField,
SetShortField,
SetIntField,
SetLongField,
SetFloatField,
SetDoubleField,
GetStaticMethodID,
CallStaticObjectMethod,
CallStaticObjectMethodV,
CallStaticObjectMethodA,
CallStaticBooleanMethod,
CallStaticBooleanMethodV,
CallStaticBooleanMethodA,
CallStaticByteMethod,
CallStaticByteMethodV,
CallStaticByteMethodA,
CallStaticCharMethod,
CallStaticCharMethodV,
CallStaticCharMethodA,
CallStaticShortMethod,
CallStaticShortMethodV,
CallStaticShortMethodA,
CallStaticIntMethod,
CallStaticIntMethodV,
CallStaticIntMethodA,
CallStaticLongMethod,
CallStaticLongMethodV,
CallStaticLongMethodA,
CallStaticFloatMethod,
CallStaticFloatMethodV,
CallStaticFloatMethodA,
CallStaticDoubleMethod,
CallStaticDoubleMethodV,
CallStaticDoubleMethodA,
CallStaticVoidMethod,
CallStaticVoidMethodV,
CallStaticVoidMethodA,
GetStaticFieldID,
GetStaticObjectField,
GetStaticBooleanField,
GetStaticByteField,
GetStaticCharField,
GetStaticShortField,
GetStaticIntField,
GetStaticLongField,
GetStaticFloatField,
GetStaticDoubleField,
SetStaticObjectField,
SetStaticBooleanField,
SetStaticByteField,
SetStaticCharField,
SetStaticShortField,
SetStaticIntField,
SetStaticLongField,
SetStaticFloatField,
SetStaticDoubleField,
NewString,
GetStringLength,
GetStringChars,
ReleaseStringChars,
NewStringUTF,
GetStringUTFLength,
GetStringUTFChars,
ReleaseStringUTFChars,
GetArrayLength,
NewObjectArray,
GetObjectArrayElement,
SetObjectArrayElement,
NewBooleanArray,
NewByteArray,
NewCharArray,
NewShortArray,
NewIntArray,
NewLongArray,
NewFloatArray,
NewDoubleArray,
GetBooleanArrayElements,
GetByteArrayElements,
GetCharArrayElements,
GetShortArrayElements,
GetIntArrayElements,
GetLongArrayElements,
GetFloatArrayElements,
GetDoubleArrayElements,
ReleaseBooleanArrayElements,
ReleaseByteArrayElements,
ReleaseCharArrayElements,
ReleaseShortArrayElements,
ReleaseIntArrayElements,
ReleaseLongArrayElements,
ReleaseFloatArrayElements,
ReleaseDoubleArrayElements,
GetBooleanArrayRegion,
GetByteArrayRegion,
GetCharArrayRegion,
GetShortArrayRegion,
GetIntArrayRegion,
GetLongArrayRegion,
GetFloatArrayRegion,
GetDoubleArrayRegion,
SetBooleanArrayRegion,
SetByteArrayRegion,
SetCharArrayRegion,
SetShortArrayRegion,
SetIntArrayRegion,
SetLongArrayRegion,
SetFloatArrayRegion,
SetDoubleArrayRegion,
RegisterNatives,
UnregisterNatives,
MonitorEnter,
MonitorExit,
GetJavaVM,
GetStringRegion,
GetStringUTFRegion,
GetPrimitiveArrayCritical,
ReleasePrimitiveArrayCritical,
GetStringCritical,
ReleaseStringCritical,
NewWeakGlobalRef,
DeleteWeakGlobalRef,
ExceptionCheck,
NewDirectByteBuffer,
GetDirectBufferAddress,
GetDirectBufferCapacity,
GetObjectRefType,
GetModule
};
其他具体函数,可查询相关API接口表。可下载:JNI官方手册 进行查询。
5 调用API
5.1 JNI_CreateJavaVM
jint JNI_CreateJavaVM(JavaVM **p_vm, void **p_env, void *vm_args);
加载并初始化一个Java VM。当前的线程成为了主线程。将env参数设置为主线程的JNI接口指针。不支持在单个进程中创建多个虚拟机。JNI_CreateJavaVM的第二个参数总是指向JNIEnv*的指针,而第三个参数是指向JavaVMInitArgs结构的指针,该结构使用选项字符串来编码任意的VM启动选项。
typedef struct JavaVMInitArgs {
jint version;
jint nOptions;
JavaVMOption *options;
jboolean ignoreUnrecognized;
} JavaVMInitArgs;
typedef struct JavaVMOption {
/* the option as a string in the default platform encoding */
char *optionString;
void *extraInfo;
} JavaVMOption;
5.2 AttachCurrentThread
jint AttachCurrentThread(JavaVM *vm, void **p_env, void *thr_args);
将当前线程附加到JavaVM上。返回JNIEnv参数中的JNI接口指针。尝试附加一个已经附加的线程是禁止操作的。一个native线程不能同时连接到两个Java VM上。当线程附加到VM时,上下文类加载器就是引导加载程序。
5.3 DetachCurrentThread
jint DetachCurrentThread(JavaVM *vm);
从Java VM中分离出当前的线程。此线程所持有的所有Java监视器都将被释放。所有等待此线程死亡的Java线程都将得到通知。主线程可以与VM分离。试图分离未附加的线程是不可操作的。如果在调用DetachCurrentThread时出现异常,VM可以选择报告其存在。
5.4 JNI_OnLoad
jint JNI_OnLoad(JavaVM *vm, void *reserved);
由动态链接的库定义的可选函数。当加载native库时,VM调用JNI_OnLoad(例如通过System.loadLibrary)。
为了使用在JNI API的特定版本中定义的函数,JNI_OnLoad必须返回一个至少定义该版本的常量。例如,希望使用JDK1.4中引入的AttachCurrentThreadAsDaemon函数,至少需要返回JNI_VERSION_1_4。如果native库不能导出JNI_OnLoad函数,则VM假定该库只需要JNI版本的JNI_VERSION_1_1。如果VM不能识别JNI_OnLoad返回的版本号,则VM将卸载库,并像从未加载库一样。
5.5 JNI_OnLoad_L
jint JNI_Onload_<L>(JavaVM *vm, void *reserved);
必须由静态链接的库来定义的强制性函数。
如果一个名为“libDemo‘的库是静态链接的,那么在第一次调用System.loadLibrary(“libDemo”)时,一个JNI_OnLoad_L函数将被调用与JNI_OnLoad函数相同的参数和期望返回值。JNI_OnLoad_L必须返回native库所需的JNI版本。此版本必须是JNI_VERSION_1_8或更高版本。如果虚拟机不能识别JNI_OnLoad_L返回的版本号,则虚拟机将像从未加载库一样。
5.6 JNI_OnUnload
void JNI_OnUnload(JavaVM *vm, void *reserved);
由动态链接的库定义的可选函数。当包含native库的类加载器被GC垃圾收集时,VM调用JNI_OnUnload。
此函数可用于执行清理操作。因为这个函数是在一个未知的上下文中调用的(例如来自finalizer),所以程序员在使用Java VM服务时应该保持警惕,避免任意的Java回调。
5.7 JNI_OnUnload_L
void JNI_OnUnload_<L>(JavaVM *vm, void *reserved);
由静态链接的库定义的可选函数。当包含静态链接的本机库“libDemo”的类加载器被GC垃圾收集时,VM将调用该库的JNI_OnUnload_L函数。
此函数可用于执行清理操作。因为这个函数是在一个未知的上下文中调用的(比如来自finalizer),所以程序员在使用Java VM服务时应该保持警惕,避免任意的Java回调。