快捷搜索:

JNI对象在函数调用中的生命周期详解

Java 本地编程接口(Java Native Interface,JNI) 易懂且轻易上手,但对付一些新手以致于一些中级开拓者,在编程中都没有把 JNI 工具的生命周期斟酌在内。以至于常常遇见莫名的 Crash,或者 segmentation fault。在花费了相称精力去调试后,才发明是 JNI 工具应用欠妥,当初保存的 JNI 工具已经被系统收受接收,却仍试图去应用之。

在 JNI 编程中常必要从一个通俗的 C/C++ 函数中调用 JNI 措施,比如:

longProcessKeyboardEventInJava(int keyboardEvent)

{longerror = 0;

JNIEnv*env = g_env;jclassthat = g_class;

jmethodIDmid = g_mid;……

error= env->CallStaticIntMethod(that, mid, keyboardEvent);

return error;}

ProcessKeyboardEventInJava 只是个通俗的 C 函数,目的是把参数中的键盘事故交给 Java 代码处置惩罚。它并没有 JNI 参数,却必要调用一个 JNI 措施。那么调用所必需的参数 JNIEnv,jclass 和 jmethodID,是若何得到呢?从上面的代码中可以看出,它们来自保存的全局变量 g_env,g_class 和 g_mid

JNIEnv * g_env;

jclass g_class;jmethodID g_mid;

JNIEXPORT jint JNICALL Java_SampleTest_init (JNIEnv *env, jclass cls)

{

g_class = cls;g_env = env;

g_mid = env->GetStaticMethodID(cls, "ProcessKeyboardEventInJava", "(III)I");while(true)

{….

long error;error = ProcessKeyboardEventInJava(theEvent)

….}

}

但万一 ProcessKeyboardEventInJava 在 Java_SampleTest_init(以下简称 init)返回后的其他机会被调用了,会如何呢?那时可就没有这么幸运了,初衷是肯定达不到了,而发生 crash 或者 segmentation fault 这类差错则险些是必然的。事实上,如上设计,ProcessKeyboardEventInJava 也只容许在 init 函数中被调用。在其返回后被调用,就分歧法了。以是,假如你是这个设计师,之前的设计中,是不是这个假设也有些太乐不雅了呢?

调用者原先以为这个函数能帮他的大年夜忙,现在却疑心了,到底发生了什么?做错了什么呢?

缘故原由着实很简单,上面三个全局变量中的 g_class 已经是不法的了,生命周期在退出 init 之后就停止了。

这里要澄清的是 g_mid,因为它不是一个 jobject,以是只要它对应的 class 没有被卸载,在退出 init 之后仍可以应用,没问题;而 g_env 对付同一个 thread 来说,它是独一的,以是只如果 init 和 ProcessKeyboardEventInJava 处于一个 thread,初始化后它的值在这个 thread 没有中止之前,都不停是合法的。

那若何才能办理 g_class 的不法引用带来的问题呢?

这首先涉及到 Java 和 Native 代码之间函数调用时,参数若何通报的问题。简单类型,也便是内置类型,比如 int, char 等是值通报(pass by value),而其它 Java 工具都是引用通报(pass by reference),这些工具引用由 JVM 传给 Native 代码,每个都有其生命周期。

其次,Java 工具的生命周期是由它的引用类型抉择的,这里的引用分两种:local reference 和 global reference。Native 函数参数中 jobject 或者它的子类,其参数都是 local reference。Local reference 只在这个 Native 函数中有效,Native 函数返回后,引用的工具就被开释,它的生命周期就停止了。若要留着日后应用,则需根据这个 local reference 创建 global reference。Global reference 不会被系统自动开释,它仅当被法度榜样明确调用 DeleteGlobalReference 时才被收受接收。

于是办理的法子就出来了:

在 init 函数中,不是简单地把 jcalss 参数保存,而是:

JNIEXPORT jint JNICALL Java_SampleTest_init (JNIEnv *env, jclass cls)

{

….g_class= (jclass)(env->NewGlobalRef(cls));

….}

这样,无论 ProcessKeyboardEventInJava 是在 init 返回前照样返回后,调用它都是安然的,可行的了。

您可能还会对下面的文章感兴趣: