美文网首页
五、JNI-局部和全局引用

五、JNI-局部和全局引用

作者: MrDecoder | 来源:发表于2020-12-15 15:24 被阅读0次
  • 局部引用和全局引用
  • 释放引用
  • 管理引用的规则

JNI提供了一些实例和数组类型(jobject,jclass,jstring,jarray等)作为不透明的引用供本地代码使用。本地代码永远不会直接操作引用指向VM内部的数据内容。要进行这些操作,必须通过使用JNI操作引用来间接操作这些数据内容。JNI中几种不同的引用:

  • JNI支持三种引用:本地引用,全局引用和弱全局引用。
  • 局部引用和全局引用有不同的生命周期。当本地方法返回时,局部引用会被自动释放。而全局引用和弱引用必须手动释放。
  • 局部引用或者全局引用会阻止GC回收它们所引用的对象,而弱引用不会。

一、局部引用和全局引用

1.1 局部引用

大多数JNI函数会创建局部引用,

1.1.1 NewLocalRef

创建指向ref的本地引用。

jobject NewLocalRef(JNIEnv *env, jobject ref);
1.1.2 DeleteLocalRef

删除localRef所指向的本地引用。一般情况下本地引用由虚拟机自动释放,但是程序员可以选择手动释放占用内存较大的临时空间,譬如数组,大字符串等。

void DeleteLocalRef(JNIEnv *env, jobject localRef);
1.1.3 示例

以下代码段演示了用全局变量记录本地引用的问题,stringClass为static变量,保存了String class的本地引用。本地引用会在函数调用之后被释放,当尝试再次调用函数从静态变量中获取时由于本地引用已经释放,会造成非法访问异常。

jstring MyNewString(JNIEnv *env, jchar *chars, jint len) {
    static jclass stringClass = nullptr;
    jmethodID cid;
    jcharArray elemArr;
    jstring ret;

    if (nullptr == stringClass) {
        stringClass = env->FindClass("java/lang/String");
        if (nullptr == stringClass) {
            return nullptr;
        }
    }

    cid = env->GetMethodID(stringClass, "<init>", "([C)V");
    elemArr = env->NewCharArray(len);
    
    //It is wrong to use the cached stringClass here,because it may be invalid.
    ret = reinterpret_cast<jstring>(env->NewObject(stringClass, cid, elemArr));
    
    //Notify garbage to release elemArr immediately. 
    env->DeleteLocalRef(elemArr); 
    return ret;
}

使用stringClass进行缓存的目的是减少FindClass的操作,这里的风险在于缓存的对象是一个局部引用。

JNIEXPORT jstring JNICALL Java_C_f(JNIEnv *env,jobject this) 
{
    char *c_str = ...;
    ...
    return MyNewString(c_str);  
}

当C.f调用返回时,虚拟机会释放所有方法调用期间产生的局部引用。

... = C.f(); // The first call is perhaps ok.
... = C.f(); // This would use an invalid local reference.
1.2 全局引用

全局引用可以跨方法、跨线程使用,直到它被手动释放才会失效。同局部引用一样,全局引用会阻止它所引用的对象被GC回收。

1.2.1 NewGlobalRef

根据obj创建新的全局引用。全局引用必须通过DeleteGlobalRef进行手动释放。

jobject NewGlobalRef(JNIEnv *env, jobject obj);
1.2.2 DeleteGlobalRef

删除全局引用。

void DeleteGlobalRef(JNIEnv *env, jobject globalRef);
1.2.3 示例

与局部引用可以被大多数JNI函数创建不同,全局引用只能使用NewGlobalRef这一个JNI函数创建:

jstring MyNewString(JNIEnv *env, jchar *chars, jint len) {
    static jclass stringClass = nullptr;
    jmethodID cid;
    jcharArray elemArr;
    jstring ret;

    if (nullptr == stringClass) {
        jclass lStringClass = env->FindClass("java/lang/String");
        if (nullptr == lStringClass) {
            return nullptr;
        }
        stringClass = reinterpret_cast<jclass>(env->NewGlobalRef(lStringClass));

        env->DeleteLocalRef(lStringClass);
        if(nullptr == stringClass) {
            return nullptr;
        }
    }

    cid = env->GetMethodID(stringClass, "<init>", "([C)V");
    elemArr = env->NewCharArray(len);

    ret = reinterpret_cast<jstring>(env->NewObject(stringClass, cid, elemArr));

    env->DeleteLocalRef(elemArr);
    return ret;
}

上述代码中,一个由FindClass返回的局部引用被传入NewGlobalRef,用来创建一个对String类的全局引用。删除lStringClass后,校验NewGlobalRef是否成功创建stringClass。

1.3 弱全局引用

弱全局引用使用NewWeakGlobalRef创建,使用DeleteWeakGlobalRef释放。与全局引用类似,弱全局引用可以跨方法、线程使用。与全局引用不同的是,弱全局引用不会阻止GC回收它所引用的VM内部对象。

弱全局引用在引用强度上比弱引用(SoftReference&WeakReference)还要弱。

1.3.1 NewWeakGlobalRef

创建一个新的弱引用。

jweak NewWeakGlobalRef(JNIEnv *env, jobject obj);
1.3.2 DeleteWeakGlobalRef

删除弱引用。

void DeleteWeakGlobalRef(JNIEnv *env, jweak obj);
1.4 引用比较

给定两个引用(不管是全局、局部还是弱引用),可以使用IsSameObject来判断它们两个是否指向相同的对象。

jboolean IsSameObject(jobject ref1, jobject ref2)
{ return functions->IsSameObject(this, ref1, ref2); }

如果ref1和ref2指向相同的对象,返回JNI_TRUE,否则返回JNI_FALSE。

二、释放引用

每一个JNI引用被建立时,除了它所指向的JVM中的对象以外,引用本身也会消耗掉一个数量的内存。短时间内创建大量不会被立即回收的引用会导致内存溢出。

2.1 释放局部引用

大多数情况下,在实现本地方法时不必担心局部引用的释放问题,因为本地方法被调用完成后,JVM会自动回收这些引用。尽管如此,以下几种情况下,为了避免内存溢出,JNI程序员应该手动释放局部引用:
1. 在本地方法中需要创建大量的局部引用。这种情况可能会导致JNI局部引用表的溢出,所以,最好是在局部引用不需要时手动删除。

for (int i = 0; i < len; i++) {
    jstring jstr = env->GetObjectArrayElement(arr,i);
    /*Process jstr*/
    env->DeleteLocalRef(jstr);
}
  1. 本地方法访问大对象,因此创建了对大对象的引用。如果本地方法返回前会有一个做大量计算的过程,而这个过程中是不需要前面创建的对大对象的引用的。但是,计算过程,对大对象的引用会阻止GC回收大对象。
2.2 管理局部引用

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

2.2.1 EnsureLocalCapacity

保证能够创建至少指定capacity大小的本地引用。返回0代表成功,否则失败。

在进行本地方法之前,VM会自动确保至少16个本地引用可以被创建。

jint EnsureLocalCapacity(JNIEnv *env, jint capacity);
2.2.2 PushLocalFrame

创建新的局部引用帧,在帧中至少可以创建指定capacity大小的局部引用。返回0代表成功,否则失败。

jint PushLocalFrame(JNIEnv *env, jint capacity);
2.2.3 PopLocalFrame

弹出当前的局部引用帧,释放所有的局部引用。返回之前局部引用帧中对result对象的局部引用。

jobject PopLocalFrame(JNIEnv *env, jobject result);
2.3 释放全局引用

当本地代码不再需要一个全局引用时,应该调用DeleteGlobalRef来释放它。否则,JVM不会回收这个全局引用所指向的对象。

当本地代码不再需要一个弱引用时,应该调用DeleteWeakGlobalRef来释放它,否则,虽然JVM会回收弱引用所指向的对象,但弱引用本身在引用表所占的内存永远不会被回收。

三、管理引用的规则

JNI引用的管理规则为:减少内存使用和对象被引用而不能释放:

  • 在编写本地方法代码时,当心不要造成全局引用和弱引用的累加,因为本地方法执行完毕后,这两种应用不会被自动释放。
  • 当编写工具函数的本地代码时,当心不要在函数的调用轨迹上面遗漏任何的局部引用,因为工具函数被调用的场合是不确定的,一旦被大量调用,很有可能造成内存溢出。

编写工具函数时,请遵守下面的规则:

  1. 一个返回值为基本类型的工具函数被调用时,决不能造成局部、全局、弱引用不被回收的累加。
  2. 一个返回值为引用类型的工具函数被调用时,除了返回的引用以外,决不能造成其他局部、全局、弱引用的累加。

相关文章

网友评论

      本文标题:五、JNI-局部和全局引用

      本文链接:https://www.haomeiwen.com/subject/rojwgktx.html