大家都知道,如果一个Java对象没有被其它成员变量或静态变量所引用的话,就随时有可能会被GC回收掉。所以我们在编写本地代码时,要注意从JVM中获取到的引用在使用时被GC回收的可能性。

JNI中有三种引用分别是:局部引用(Local Reference)、全局引用(Global Reference)、弱全局引用(Weak Global Reference)

局部引用

局部引用可以通过NewLocalRef和FindClass、NewObject、GetObjectClass和NewCharArray等方法进行创建,一般是在方法中使用并且值在创建它的方法内有效,它会增加引用计数从而阻止GC回收所引用的对象,但是一旦出了这个方法就变得无效了。也就是说它的生命周期只在创建该局部引用的方法内部。在用完局部引用之后可以选择不手动释放而是在本地方法执行完之后由JVM自动释放,还可以手动调用DeleteLocalRef释放。但是一般最好手动在刚用完之后就立刻释放,因为JNI会将创建的局部引用都存储在一个局部引用表中,如果这个表超过了最大容量限制(Android上的JNI局部引用表最大数量是512个),就会造成局部引用表溢出,从而导致程序崩溃。并且有可能在你申请并使用完局部引用之后的操作还需要比较大的空间,这时候如果不即使释放有可能导致OOM。在使用局部引用的时候还需要注意的是局部引用不能跨线程使用,只在创建它的线程有效。不要试图在一个线程中创建局部引用并存储到全局引用中,然后在另外一个线程中使用。

全局引用

与局部引用创建方式不同的是,全局变量只能通过NewGlobalRef方法创建。JVM不会自动释放它,而是需要我们手动释放才会失效,并且它可以跨方法,跨线程使用。

jstring  
MyNewString(JNIEnv *env, jchar *chars, jint len)
{
static jclass stringClass = NULL;
...
if (stringClass == NULL) {
jclass localRefCls =
(*env)->FindClass(env, "java/lang/String");
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 */
}
}
...
}

弱全局引用

调用NewWeakGlobalRef基于局部引用或全局引用创建,不能增加引用计数,不会阻止GC回收所引用的对象,也就是说它对应的Java对象生命周期依然取决于虚拟机,这就导致即便弱全局引用没有被释放,其引用的Java对象有可能已经被释放,和全局引用一样可以跨方法、跨线程使用。引用不会自动释放,在JVM认为应该回收它的时候进行回收而被释放。或者调用DeleteWeakGlobalRef手动释放。

引用比较与弱引用是否被回收

给定两个引用(不管是全局、局部还是弱全局引用),只需要调用IsSameObject就可以判断两个是否指向相同的对象,例如:

(*env)->IsSameObject(env, obj1, obj2)

如果obj1和obj2指向相同的对象,则返回JNI_TRUE,否则返回JNI_FALSE。但是使用IsSameObject比较弱全局引用与NULL的时候,返回值的意义是有点特别的:比如:

jboolean isWeakRefGC = (*env)->IsSameObject(env,gw_obj_ref, NULL);

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

Contents
  1. 1. 局部引用
  2. 2. 全局引用
  3. 3. 弱全局引用
  4. 4. 引用比较与弱引用是否被回收