关闭

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

标签: jnireferencenullexceptionjava虚拟机
3949人阅读 评论(1) 收藏 举报
分类:
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》源码
0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:428019次
    • 积分:4737
    • 等级:
    • 排名:第6321名
    • 原创:44篇
    • 转载:141篇
    • 译文:0篇
    • 评论:46条
    最新评论