JNI之四:JNI函数(上)

本文作为JNI函数参考,列出了完整的JNI函数,并呈现了JNI函数表的布局。

注意,属于“必须(must)”用于描述对于JNI程序员的限制。例如,当你遇到一些JNI函数必须接受一个非空对象时,你的责任就是确保不会传一个空对象到JNI函数。因此JNI实现不必在JNI函数中做空指针检查。明确不允许的情况下传入空,将引起未捕获异常或奔溃。

函数的定义可以在出现错误时同时返回NULL和抛出异常,也可以在出错时不抛出异常,只返回NULL。例如JNI实现可考虑“out of memroy”的临时条件,而不希望抛出OutOfMemoryError异常,从而出现fatal(JDK API java.lang.Error文档:代表严重的问题,应用程序不应该catch它)。

本文的一部分内容改编自Netscape的JRI文档。

接口函数表

可轻松地通过JNIEnv参数以一个固定的偏移量访问每个函数。JNIEnv类型是一个指向存储着所有JNI函数指针的结构体的指针。其定义如下:

typedef const struct JNINativeInterface *JNIEnv;

虚拟机初始化这个函数表,如下代码所示。注意,前三个条目为未来兼容COM预留。此外还在函数表开头保留了一些额外的条目,以便将来class相关的JNI操作能够在FindClass之后添加,而不是添加到函数表末尾。

注意,函数表能够在所有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
  };

常量

有一些通用常量总是在JNI API中使用

Boolean值

#define JNI_FALSE 0
#define JNI_TRUE 1

返回码

JNI函数的通用返回码常量:

#define JNI_OK           0                 /* 成功 */
#define JNI_ERR          (-1)              /* 未知错误 */
#define JNI_EDETACHED    (-2)              /* 线程从虚拟机detached */
#define JNI_EVERSION     (-3)              /* JNI版本错误 */
#define JNI_ENOMEM       (-4)              /* 内存不足 */
#define JNI_EEXIST       (-5)              /* 虚拟机已创建 */
#define JNI_EINVAL       (-6)              /* 参数无效 */

版本信息


GetVersion

jint GetVersion(JNIEnv *env);
此函数返回native方法接口的版本号。下表给出了每个Java SE发行版对应的JNI版本号:

Java SE 平台JNI版本
1.1JNI_VERSION_1_1
1.2JNI_VERSION_1_2
1.3JNI_VERSION_1_2
1.4JNI_VERSION_1_4
5.0JNI_VERSION_1_4
6JNI_VERSION_1_6
7JNI_VERSION_1_6
8JNI_VERSION_1_8
9JNI_VERSION_9
10JNI_VERSION_10
11JNI_VERSION_10
12JNI_VERSION_10
13JNI_VERSION_10
链接(LINKAGE

JNIEnv接口函数表(下称函数表)中第4个(从0开始,下同)。

参数

env:JNI接口指针,不可为NULL.

注:下文将省略JNI函数中JNIEnv *env指针的说明。

返回值

高16位为主版本号,低16位为次版本号。

JDK/JRE 1.1 GetVersion()返回JNI_VERSION_1_1;
JDK/JRE 1.2 GetVersion()返回JNI_VERSION_1_2;
JDK/JRE 1.4 GetVersion()返回JNI_VERSION_1_4;
JDK/JRE 1.6 GetVersion()返回JNI_VERSION_1_6;
JDK/JRE 1.8 GetVersion()返回JNI_VERSION_1_8;
JDK/JRE 9 GetVersion()返回JNI_VERSION_9;
JDK/JRE 10 GetVersion()返回JNI_VERSION_10;
JDK/JRE 11 GetVersion()返回JNI_VERSION_10;
JDK/JRE 12 GetVersion()返回JNI_VERSION_10;
JDK/JRE 13 GetVersion()返回JNI_VERSION_10;


版本号常量

#define JNI_VERSION_1_1 0x00010001
#define JNI_VERSION_1_2 0x00010002
#define JNI_VERSION_1_4 0x00010004
#define JNI_VERSION_1_6 0x00010006
#define JNI_VERSION_1_8 0x00010008
#define JNI_VERSION_9   0x00090000
#define JNI_VERSION_10  0x000a0000

类操作


DefineClass

jclass DefineClass(JNIEnv *env, const char *name, jobject loader, const jbyte *buf, jsize bufLen);

从原始class数据缓冲区中加载class。DefineClass调用返回后,VM不引用包含的原始class数据的缓冲区。如果需要,它将被丢弃

链接(LINKAGE

函数表中第5个

参数

name:接口或类名。字符串由MUTF-8编码,此参数可以为空,或者必须和类文件数据的名称相同。
loader:分配给已定义类的类加载器。可以为空,表示“null class loader”(或者“bootstrap class loader”)。
buf:包含.class文件数据的缓冲区,空值将引起ClassFormatError。
bufLen:缓冲区大小。

返回值

返回Java class对象,或者发生错误时返回null。

异常

ClassFormatError:class数据没有指定有效的class。
ClassCircularityError:一个类或接口称为它自己的super class或super interface。
OutOfMemoryError:内存溢出。
SecurityException:试图调用定义在“java”包中的内容。


FindClass

jclass FindClass(JNIEnv *env, const char *name);

JDK1.1中,该函数加载一个本地定义的class,它在有指定名称的class 的CLASSPATH环境变量中搜索目录和zip文件。

从JDK1.2起,Java安全模型允许非系统类加载和调用native方法。FindClass定位当前native方法关联的类加载器,即声明了这个native方法的类加载器。如果native方法属于系统类,不涉及类加载器。此外,正确的类加载器将被调用以加载和链接命名的类。

从JDK1.2起,当FindClass被Invocation接口调用,它没有当前native方法或与之关联的类加载器。那么,ClassLoader.getSystemClassLoader将被使用。他是这么为应用程序创建的类加载器,它能定位java.class.path属性中列出的类。

如果从library生命周期hook调用FindClass,类加载器确定如下:

  • 对于JNI_OnLoadJNI_OnLoad_L函数,class loader是加载native库时使用的。
  • 对于JNI_OnUnloadJNI_OnUnload_L函数,类加载器由ClassLoader.getSystemClassLoader返回(因为on-load时使用的类加载器可能不再存在)

name参数是类的权限定名,或者数组签名。如,java.lang.String的权限定名为

java/lang/String

java.lang.Object[]数组的类型签名为

[Ljava/lang/Object;
链接

函数表中第6个。

参数

name:类的全限定名(”/”分隔包名跟上类名)。如果名称由”[“(数组签名字符)开头,它将返回一个数组class。字符串由MUTF-8编码。null值将引起NoClassDefFoundError错误或奔溃。

返回值

返回一个由全限定名命名的class对象,如果这个类找不到将返回null。

异常

ClassFormatError:class数据没有指定有效的class。
ClassCircularityError:一个类或接口称为它自己的super class或super interface。
OutOfMemoryError:内存溢出。
NoClassDefFoundError:未找到类或接口定义。


GetSuperclass

jclass GetSuperclass(JNIEnv *env, jclass clazz);

如果clazz代表Object以外的其它任何类,那么,此函数将返回表示clazz指定的类的超类的对象。

如果clazz指定Object类,或clazz代表接口,该函数将返回null。

链接

函数表第10个

参数

clazz:Java class对象,不为空。

返回值

返回由clazz表示的类的超类,或null。


IsAssignableFrom

jboolean IsAssignableFrom(JNIEnv *env, jclass clazz1, jclass clazz2);

判断clazz1的对象能否安全地类型转换为clazz2

链接

函数表第11

参数

clazz1:第一个class参数,不为空。
clazz2:第二个class参数,不为空。

返回值

返回JNI_TRUE,如果下列条件为true:

  • 第一个和第二个class参数关联到同一个java class。
  • 第一个class是第二个class的子类。
  • 第一个class有第二个类作为其接口。

组件操作


GetModule

jobject GetModule(JNIEnv *env, jclass clazz);

返回class所属模块的java.lang.Module 对象。如果这个class未出现在已命名的模块中,返回该类的类加载器的未命名模块。如果这个class表示数组类型,该函数返回元素类型的Module对象。如果该class表示基本类型或void,返回java.base模块的Module对象。

链接

函数表第233

参数

clazz:Java class对象,不为空。

返回值

返回该类或接口所属的模块。

SINCE

JDK/JRE 9


Exceptions

Throw

jint Throw(JNIEnv *env, jthrowable obj);

导致抛出java.lang.Throwable

链接

函数表13

参数

objjava.lang.Throwable对象,不为空。

返回值

0:成功;失败:负值。

异常

java.lang.Throwable对象obj


ThrowNew

jint ThrowNew(JNIEnv *env, jclass clazz, const char *message);

message指定消息从指定类型构造一个异常对象,并引发该异常。

链接

函数表14

参数

clazzjava.lang.Throwable对象子类,不为空。
message:用来构造java.lang.Throwable的消息。字符串由MUTF-8编码。不为空。

返回值

0:成功;失败:负值。

异常

新构造的java.lang.Throwable对象。


ExceptionOccurred

jthrowable ExceptionOccurred(JNIEnv *env);

判断是否抛出异常。在native代码调用ExceptionClear()或java代码捕获之前,异常将一直被抛出。

链接

函数表15

返回值

返回当前正在抛出的异常对象,如果当前未抛出异常,将返回null。


ExceptionDescribe

void ExceptionDescribe(JNIEnv *env);

打印异常以及系统错误报告渠道的调用栈,例如stderr。待定异常被清除则是调用次函数的副作用。这是为调试提供的便利。

链接

函数表16


ExceptionClear

void ExceptionClear(JNIEnv *env);

清除当前正在抛出的异常,如果当前无异常抛出,这个例程没任何效果。

链接

函数表17


FatalError

void FatalError(JNIEnv *env, const char *msg);

引发fatal错误,且不期望VM恢复。此函数不返回。

参数

msg:错误消息。MUTF-8编码。可为空。


ExceptionCheck

采用便利函数检测不创建局部引用到异常对象的待定异常。

jboolean ExceptionCheck(JNIEnv *env);

有待定异常时返回JNI_TRUE,否则返回JNI_FALSE

链接

函数表228

全局引用和局部引用

全局引用


NewGlobalRef

jobject NewGlobalRef(JNIEnv *env, jobject obj);

创建一个引用到obj参数的全局引用。obj参数可能为全局引用,也可为局部引用。全局引用必须明确的调用DeleteGlobalRef()函数进行清理。

链接

函数表32

参数

obj:一个全局引用或局部引用。可为空值,函数将返回null。

返回值

返回给定obj对象的全局引用。
一下情况将返回null:

  • obj指向空对象。
  • 系统内存溢出。
  • obj为弱全局引用,且已经被垃圾收集器回收。

DeleteGlobalRef

void DeleteGlobalRef(JNIEnv *env, jobject globalRef);

删除globalRef所指向的全局引用。

链接

函数表22

参数

globalRef:全局引用。可为null,该函数则无任何效果。

局部引用

局部引用在native方法调用期间是有效的。native方法return时,他们讲被自动释放。每个局部引用都将消耗一定的JVM资源。程序员需要全包native方法没有过度分配局部引用。尽管本地引用会在native方法返回到java时自动释放,但过度分配局部引用可能会在native方法执行期间引起JVM OOM。


DeleteLocalRef

void DeleteLocalRef(JNIEnv *env, jobject localRef);

删除localRef指向的局部引用。

链接

函数表23

参数

localRef:局部引用,如果传入null,函数则不做任何操作。

注意:JDK/JRE 1.1提供了上面的DeleteLocalRef函数,因此程序员能够手动删除局部引用。例如,如果native代码遍历一个大的数组对象,并在每次迭代时使用其元素。最佳做法是在下一次迭代创建局部引用之前删除不再使用的数组元素的局部引用。作为JDK/JRE 1.2提供的管理局部引用声明周期的附加函数集和。有以下四个函数。


EnsureLocalCapacity

jint EnsureLocalCapacity(JNIEnv *env, jint capacity);

确保至少能在当前线程中创建给定数量的本地引用。成功返回0;否则返回负数且抛出OutOfMemoryError。

在进入native方法之前,VM自动确保能够创建16个局部引用。

为了向后兼容性,VM分配局部引用超过确保容量(ensured capacity)。(作为debug支持,VM可能给出创建过多局部引用的警告。在JDK中,程序员能使用-verbose:jni命令打开这些消息)。如果不能创建超过确保容量的更多局部引用,VM则调用FatalError

有些JVM实现可能会限制最大capacity,超过会导致函数返回错误(如 JNI_ERR or JNI_EINVAL)。例如,HotSpot虚拟机实现使用-XX:+MaxJNILocalCapacity (默认值65535)。

链接

函数表26

参数

capacity:局部引用的最小数量。必须大于等于0。

返回值

成功JNI_OK

SINCE:

JDK/JRE 1.2


PushLocalFrame

jint PushLocalFrame(JNIEnv *env, jint capacity);

创建一个新的局部引用帧,其中至少能创建给定数量的局部引用。成功返回0,失败返回负数并产生待定的OOM异常。

注意,已经在前一个局部帧中创建的局部引用在当前局部帧依然有效。

EnsureLocalCapacity一样,有些JVM实现可能限制最大capacity,这可能会导致函数返回错误。

链接

函数表19

参数

capacity:所需的最小局部引用数量,必须大于0。

返回值

成功JNI_OK

SINCE:

JDK/JRE 1.2


PopLocalFrame

jobject PopLocalFrame(JNIEnv *env, jobject result);

弹出当前局部引用帧,释放所有局部引用,并返回给定result对象在先前局部引用帧中的局部引用

如果不需要返回前一个帧的引用,result字段传null。

链接

函数表20

参数

result:要传入的先前局部引用帧对象,可为空。

返回值

返回给定result对象在先前局部引用帧中的局部引用,如果指定result对象为空,则返回null。

SINCE:

JDK/JRE 1.2


NewLocalRef

jobject NewLocalRef(JNIEnv *env, jobject ref);

创建一个指向引用与ref相同的局部引用。ref可能为全局引用、局部引用或空。如果ref指向空,则返回null。

链接

函数表25

参数

ref:函数创建一个新局部引用对象的引用。可为空。

返回值

返回新的指向与ref相同的局部引用。

可能返回null如果:

  • ref指向null
  • 系统内存不足
  • ref是弱全局引用且已被垃圾收集器回收
SINCE:

JDK/JRE 1.2

弱全局引用

弱全局引用是一种特殊的全局引用。与普通的全局引用不同,弱全局引用允许底层java对象被垃圾收集器回收。弱全局引用可以在使用全局引用或局部引用的任何情况下使用。

弱全局引用相对于虚引用 (java.lang.ref.PhantomReference)。当确定对象是否虚引用可达(见java.lang.ref)时,弱全局引用指向一个被视为特殊的虚引用引用着的对象。这样一来,弱全局引用将在功能上等价于NULL,同时,垃圾收集器将清除引用同一对象的PhantomReference

垃圾收集可能会发生在native方法运行期间,被弱全局引用引用的对象能随时被释放。虽然能使用全局引用的地方,也能使用弱全局引用,但通常这样做是不合适的,因为它在功能上等效于null,而得不到通知。

IsSameObject能用来比较弱全局引用和非空局部引用或全局引用。如果是相同对象,只要其他引用未被删除,弱全局引用就不会为null。

IsSameObject也能将弱全局引用于null比较,判断底层的对象是否被释放。但是,程序员不应该依赖这种检查去判断一个弱全局引用是否能在将来的JNI函数调用中使用,因为垃圾收集的介入会改变弱全局引用。

相反,推荐使用NewLocalRef 或 NewGlobalRef函数,获得底层对象的局部(强)引用或全局(强)引用。如果对象被释放,这些函数将返回null。否则,新引用将组织底层对象被释放。如果新引用不为空,则可用来访问底层对象,不再使用时删除即可。


NewWeakGlobalRef

jweak NewWeakGlobalRef(JNIEnv *env, jobject obj);

创建一个新的弱全局引用。弱全局引用不阻止发生在这些对象上的垃圾收集。IsSameObject能用来测试被这个引用引用的对象是否被释放。如果obj引用为null,则返回NULL。或者obj是个弱全局引用,或者VM内存溢出。如果VM内存溢出,将抛出OutOfMemoryError

链接

函数表226

参数

obj:需要创建弱全局引用的对象。

返回

返回obj的弱全局引用。

下列情况将返回null:

  • obj为null
  • 内存溢出
  • obj是弱全局引用,并且已经被垃圾收集器回收

DeleteWeakGlobalRef

void DeleteWeakGlobalRef(JNIEnv *env, jweak obj);

删除弱全局引用资源。

链接

函数表227

参数

obj:需要删除的弱全局引用。传入null无效。