jni中的本地引用和全局引用

JNI支持三种类型的java对象引用:局部引用(local reference)、全局引用(global reference)以及弱全局引用(weak global reference)。三种类型的引用具有不同的生命周期,另外垃圾回收器对这三种对象引用的管理方式也不同。创建局部引用的本地方法返回后(注意:这里是指返回到java方法),局部引用将变成无效。而全局引用以及弱全局引用在本地方法返回后,仍然有效。垃圾回收器无法回收本地方法创建的局部引用和全局引用,但可以回收本地方法创建的弱全局引用。为了编写正确而且内存安全的java本地方法,很有必要对这三种类型的对象引用做些总结。
     一 局部引用的注意事项  
    大多数jni方法创建的对象引用都属于局部引用(local reference),如FindClass方法、NewObject等。局部引用不能作为静态变量,也不能作为全局变量。如下一段程序就是错误的: 
    Jstring MyNewString(JNIEnv *env, jchar *chars, jint len) 
    { 
         static jclass stringClass = NULL; 
        jmethodID cid; 
        jcharArray elemArr; 
        jstring result; 
        if (stringClass == NULL) { 
             stringClass = (*env)->FindClass(env, "java/lang/String"); 
            if (stringClass == NULL) { 
                return NULL; /* exception thrown */ 
            }
         } 
        /*直接使用stringClass是危险的,因为stringClass是局部引用。 */ 
         cid = (*env)->GetMethodID(env, stringClass, "<init>", "([C)V"); 
        ... 
        elemArr = (*env)->NewCharArray(env, len); 
        ... 
        result = (*env)->NewObject(env, stringClass, cid, elemArr);
        (*env)->DeleteLocalRef(env, elemArr); 
        return result; 
    } 
    虚拟机中,每一个方法都对应着执行栈(interpreter stack)中的一个帧(frame)。Java方法对应着Java帧(Java Frame),本地方法对应着转变帧(Transition Frame)。局部引用是保存在帧栈(stack Of frame)中,当方法返回时帧就会从执行栈中弹出。因此,帧栈中所有的引用就全部变成无效。在本地方法返回前,局部引用的内存是不能被垃圾回收器回收的。如果一个本地方法中存在循环且循环体中会创建局部引用,那么很有可能导致outOfMemeroy异常。为了保证内存安全,程序员应该在循环体中调用DeleteLocalRef来释放局部引用。
    不同的虚拟机实现垃圾回收的算法并不相同,传统上有两种方法:引用计数法、触及法。引用计数法无法检测出循环引用,现在已经很少使用。触及法的总体思路是:首先创建一个根集合,该集合中的对象是肯定处于活跃态的;然后从根集合开始做遍历,所有可被遍历到的对象都被认为是处于活跃态的;最后,非活跃态的对象被回收。帧栈中的引用会在垃圾回收的第一步中被加入根集合,所以在执行栈将局部引用所属帧弹出前,局部引用是肯定不会被垃圾回收器回收的。 
    二 全局引用的注意事项  
    只有一个jni方法可以创建全局引用:NewGlobalRef。下面给出了一段创建全局引用的程序: 
    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 */ 
            }
             /* 创建一个全局引用 */ 
             stringClass = (*env)->NewGlobalRef(env, localRefCls);
            
 /* 局部引用localRefCls不再有效,删除局部引用localRefCls*/ 
            (*env)->DeleteLocalRef(env, localRefCls); 
            if (stringClass == NULL) { 
                return NULL; /* out of memory exception thrown */
             }
         }
         ... 
    } 
    与局部引用类似的是,在垃圾回收的第一个过程中全局引用也会被加入到根集合中。而且即使创建全局引用的帧从执行栈中弹出后,全局引用仍然有效。所以,释放全局引用的工作必须由程序员完成。调用DeleteGlobalRef来释放全局引用。
     三 弱全局引用的注意事项  
    创建弱全局引用的接口是:NewGlobalWeakRef。释放弱全局引用的接口是:DeleteWeakGlobalRef(《jni手册》中对这个接口的前后描述不一致,我是对比了phoneME虚拟机的)。在本地方法返回后,弱全局引用仍然有效。另外,在垃圾回收期运行的第一步中,弱全局对象不会被加入根集合。因此,弱全局引用可以被垃圾回收器回收。下面给出了一段使用弱全局引用的程序: 
    JNIEXPORT void JNICALL Java_mypkg_MyCls_f(JNIEnv *env, jobject self) { 
         static jclass myCls2 = NULL;  
        if (myCls2 == NULL) { 
             jclass myCls2Local = (*env)->FindClass(env, "mypkg/MyCls2");
            if (myCls2Local == NULL) {
                return; /* can’t find class */ 
            } 
             myCls2 = NewWeakGlobalRef(env, myCls2Local);
            if (myCls2 == NULL) { 
                return; /* out of memory */
            }
        } 
        ... 
        /* use myCls2 */ 
    } 
    对于上面的例程,本地方法f属于类java.mypkg.MyCls。可以认为引用myCls2的生命周期与类java.mypkg.MyCls相同。 

参考资料:
1. 《jni程序员手册》
2. 《深入Java虚拟机》
3. 《phoneME-advance-mr2》源码
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值