java JNI编程指南 下载本文

} }

/* It is wrong to use the cached stringClass here, because it may be invalid. */

cid = (*env)->GetMethodID(env, stringClass,

\ ...

elemArr = (*env)->NewCharArray(env, len); ...

result = (*env)->NewObject(env, stringClass, cid, elemArr); (*env)->DeleteLocalRef(env, elemArr); return result; }

上面代码中,我们省略了和我们的讨论无关的代码。因为FindClass返回一个对java.lang.String对象的局部引用,上面的代码中缓存stringClassr做法是错误的。假设一个本地方法C.f调用了MyNewString: JNIEXPORT jstring JNICALL

Java_C_f(JNIEnv *env, jobject this) {

char *c_str = ...; ...

return MyNewString(c_str); }

C.f方法返回后,VM释放了在这个方法执行期间创建的所有局部引用,也包含对String类的引用stringClass。当再次调用MyNewString时,会试图访问一个无效的局部引用,从而导致非法的内存访问甚至系统崩溃。

释放一个局部引用有两种方式,一个是本地方法执行完毕后VM自动释放,另外一个是程序员通过DeleteLocalRef手动释放。

既然VM会自动释放局部引用,为什么还需要手动释放呢?因为局部引用会阻止它所引用的对象被GC回收。

局部引用只在创建它们的线程中有效,跨线程使用是被禁止的。不要在一个线程中创建局部引用并存储到全局引用中,然后到另外一个线程去使用。

5.1.2 全局引用

全局引用可以跨方法、跨线程使用,直到它被手动释放才会失效。同局部引用一样,全局引用也会阻止它所引用的对象被GC回收。 与局部引用可以被大多数JNI函数创建不同,全局引用只能使用一个JNI函数创建:NewGlobalRef。下面这个版本的MyNewString演示了怎么样使用一个全局引用:

/* This code is OK */ jstring

MyNewString(JNIEnv *env, jchar *chars, jint len) {

static jclass stringClass = NULL; ...

if (stringClass == NULL) { jclass localRefCls =

(*env)->FindClass(env, \ if (localRefCls == NULL) {

return NULL; /* exception thrown */ }

/* Create a global reference */

stringClass = (*env)->NewGlobalRef(env, localRefCls);

/* The local reference is no longer useful */ (*env)->DeleteLocalRef(env, localRefCls);

/* Is the global reference created successfully? */ if (stringClass == NULL) {

return NULL; /* out of memory exception thrown */ } } ... }

上面这段代码中,一个由FindClass返回的局部引用被传入NewGlobalRef,用来创建一个对String类的全局引用。删除localRefCls后,我们检查NewGlobalRef是否成功创建stringClass。

5.1.3 弱引用

弱引用使用NewGlobalWeakRef创建,使用DeleteGlobalWeakRef释放。与全局引用类似,弱引用可以跨方法、线程使用。与全局引用不同的是,弱引用不会阻止GC回收它所指向的VM内部的对象。 在MyNewString中,我们也可以使用弱引用来存储stringClass这个类引用,因为java.lang.String这个类是系统类,永远不会被GC回收。

当本地代码中缓存的引用不一定要阻止GC回收它所指向的对象时,弱引用就是一个最好的选择。假设,一个本地方法mypkg.MyCls.f需要缓存一个指向类mypkg.MyCls2的引用,如果在弱引用中缓存的话,仍然允许mypkg.MyCls2这个类被unload:

JNIEXPORT void JNICALL

Java_mypkg_MyCls_f(JNIEnv *env, jobject self) {

static jclass myCls2 = NULL; if (myCls2 == NULL) { jclass myCls2Local =

(*env)->FindClass(env, \ if (myCls2Local == NULL) {

return; /* can't find class */ }

myCls2 = NewWeakGlobalRef(env, myCls2Local); if (myCls2 == NULL) {

return; /* out of memory */ } }

... /* use myCls2 */ }

我们假设MyCls和MyCls2有相同的生命周期(例如,他们可能被相同的类加载器加载),因为弱引用的存在,我们不必担心MyCls和它所在的本地代码在被使用时,MyCls2这个类出现先被unload,后来又会preload的情况。

当然,真的发生这种情况时(MyCls和MyCls2的生命周期不同),我们必须检查缓存过的弱引用是指向活动的类对象,还是指向一个已经被GC给unload的类对象。下一节将告诉你怎么样检查弱引用是否活动。

5.1.4 引用比较

给定两个引用(不管是全局、局部还是弱引用),你可以使用IsSameObject来判断它们两个是否指向相同的对象。例如: (*env)->IsSameObject(env, obj1, obj2)

如果obj1和obj2指向相同的对象,上面的调用返回JNI_TRUE(或者1),否则返回JNI_FALSE(或者0)。

JNI中的一个引用NULL指向JVM中的null对象。如果obj是一个局部或者全局引用,你可以使用(*env)->IsSameObject(env, obj, NULL)或者obj == NULL来判断obj是否指向一个null对象。

在这一点儿上,弱引用有些有同,一个NULL弱引用同样指向一个JVM中的null对象,但不同的是,在一个弱引用上面使用IsSameObject时,返回值的意义是不同的:

(*env)->IsSameObject(env, wobj, NULL)

上面的调用中,如果wobj已经被回收,会返回JNI_TRUE,如果wobj仍然指向一个活动对象,会返回JNI_FALSE。

5.2 释放引用

每一个JNI引用被建立时,除了它所指向的JVM中的对象以外,引用本身也会消耗掉一个数量的内存。作为一个JNI程序员,你应该对程序在一个给定时间段内使用的引用数量十分小心。短时间内创建大量不会被立即回收的引用会导致内存溢出。

5.2.1 释放局部引用

大部分情况下,你在实现一个本地方法时不必担心局部引用的释放问题,因为本地方法被调用完成后,JVM会自动回收这些局部引用。尽管如此,以下几种情况下,为了避免内存溢出,JNI程序员应该手动释放局部引用: 1、 在实现一个本地方法调用时,你需要创建大量的局部引用。这种情况可能会导致JNI局部引用表的溢出,所以,最好是在局部引用不需要时立即手动删除。比如,在下面的代码中,本地代码遍历一个大的字符串数组,每遍历一个元素,都会创建一个局部引用,当对这个元素的遍历完成时,这个局部引用就不再需要了,你应该手动释放它: for (i = 0; i < len; i++) {

jstring jstr = (*env)->GetObjectArrayElement(env, arr, i); ... /* process jstr */

(*env)->DeleteLocalRef(env, jstr); } 2、 你想写一个工具函数,这个函数被谁调用你是不知道的。4.3节中的MyNewString演示了怎么样在工具函数中使用引用后,使用DeleteLocalRef删除。不这样做的话,每次MyNewString被调用完成后,就会有两个引用仍然占用空间。 3、 你的本地方法不会返回任何东西。例如,一个本地方法可能会在一个事件接收循环里面被调用,这种情况下,为了不让局部引用累积造成内存溢出,手动释放也是必须的。 4、 你的本地方法访问一个大对象,因此创建了一个对这个大对象的引用。然后本地方法在返回前会有一个做大量的计算过程,而在这个过程中是不需要前面创建的对大对象的引用的。但是,计算过程,对大对象的引用会阻止GC回收大对象。

在下面的程序中,因为预先有一个明显的DeleteLocalRef操作,在函数

lengthyComputation的执行过程中,GC可能会释放由引用lref指向的对象。

5.2.2 管理局部引用

JDK提供了一系列的函数来管理局部引用的生命周期。这些函数包括:

EnsureLocalCapacity、NewLocalRef、PushLocalFrame、PopLocalFrame。 JNI规范中指出,VM会确保每个本地方法可以创建至少16个局部引用。经验表明,这个数量已经满足大多数不需要和JVM中的内部对象有太多交互的本地方法。如果真的需要创建更多的引用,本地方法可以通过调用

EnsureLocalCapacity来支持更多的局部引用。在下面的代码中,对前面的例子做了些修改,不考虑内存因素的情况下,它可以为创建大量的局部引用提供足够的空间。 ? /* The number of local references to be created is equal to ? the length of the array. */ ? if ((*env)->EnsureLocalCapacity(env, len)) < 0) { ? ... /* out of memory */ ? } ? for (i = 0; i < len; i++) { ? jstring jstr = (*env)->GetObjectArrayElement(env, arr, i);

... /* process jstr */ ? /* DeleteLocalRef is no longer necessary */ ? } 当然,上面这个版本中没有立即删除不使用的局部引用,因此会比前面的版本消耗更多的内存。

另外,Push/PopLocalFrame函数对允许程序员创建作用范围层层嵌套的局部引用。例如,我们可以把上面的代码重写: ? #define N_REFS ... /* the maximum number of local references ? used in each iteration */ ? for (i = 0; i < len; i++) { ? if ((*env)->PushLocalFrame(env, N_REFS) < 0) { ? ... /* out of memory */ ? } ? jstr = (*env)->GetObjectArrayElement(env, arr, i); ? ... /* process jstr */ ? (*env)->PopLocalFrame(env, NULL); ? }

PushLocalFrame为一定数量的局部引用创建了一个使用堆栈,而PopLocalFrame负责销毁堆栈顶端的引用。

Push/PopLocalFrame函数对提供了对局部引用的生命周期更方便的管理。上面的例子中,如果处理jstr的过程中创建了局部引用,则PopLocalFrame执行时,这些局部引用全部会被销毁。

当你写一个会返回局部引用的工具函数时,NewLocalRef非常有用,我们会在5.3节中演示NewLocalRef的使用。

本地代码可能会创建大量的局部引用,其数量可能会超过16个或PushLocaFrame

和EnsureLocalCapacity调用设置的个数。VM可能会尝试分配足够的内存,但不能够保证分配成功。如果失败,VM会退出。

5.2.3 释放全局引用

当你的本地代码不再需要一个全局引用时,你应该调用DeleteGlobalRef来释放

它。如果你没有调用这个函数,即使这个对象已经没用了,JVM也不会回收这个全局引用所指向的对象。

当你的本地代码不再需要一个弱引用时,应该调用DeleteWeakGlobalRef来释放

它,如果你没有调用这个函数,JVM仍会回收弱引用所指向的对象,但弱引用本身在引用表中所占的内存永远也不会被回收。

5.3 管理引用的规则

前面已经做了一个全面的介绍,现在我们可以总结一下JNI引用的管理规则了,

目标就是减少内存使用和对象被引用保持而不能释放。

通常情况下,有两种本地代码:直接实现本地方法的本地代码和可以被使用在任

何环境下的工具函数。

?