JNI 引用, DeleteLocalRef使用场景详解

局部引用:

JNI 函数内部建立的 jobject 对象及其子类( jclass 、 jstring 、 jarray 等) 对象都是局部引用,它们在 JNI 函数返回后无效;html

通常状况下,咱们应该依赖 JVM 去自动释放 JNI 局部引用;但下面两种状况必须手动调用 DeleteLocalRef() 去释放:java

  • (在循环体或回调函数中)建立大量 JNI 局部引用,即便它们并不会被同时使用,由于 JVM 须要足够的空间去跟踪全部的 JNI 引用,因此可能会形成内存溢出或者栈溢出;android

  • 若是对一个大的 Java 对象建立了 JNI 局部引用,也必须在使用完后手动释放该引用,不然 GC 迟迟没法回收该 Java 对象也会引起内存泄漏.shell

全局引用:

全局引用容许你持有一个 JNI 对象更长的时间,直到你手动销毁;但须要显式调用 NewGlobalRef() 和 DeleteGlobalRef() 缓存

 

class MyPeer {
private:
    jstring s;
public:
    MyPeer(JNIEnv* env, jstring s) {
        this->s = env->NewGlobalRef(s);
    }
    ~MyPeer() {
        env->DeleteGlobalRef(s);
        s = NULL;
    }
};

 

弱全局引用

弱全局引用相似 Java 中的弱引用,它容许对应的 Java 对象被 GC 回收;数据结构

相似地,建立和释放也是经过 NewWeakGlobalRef() 和 DeleteWeakGlobalRef() oracle

调用 IsSameObject(env, jobj, NULL) 能够判断该弱全局引用指向的 Java 对象是否已被 GC 回收。ide

jobject 对象的引用值不惟一

同一个 jobject 对象的不一样引用可能拥有不一样的值,好比同一 jobject 对象每次调用 NewGlobalRef() 可能返回不一样的值;函数

要检查两个引用是否指向同一个 jobject 对象,必须调用 IsSameObject() ,而不要使用 == 去比较;ui

用于描述一个 jobject 对象的 32 位值可能在方法屡次调用后发生变化,而两个不一样 jobject 对象却可能在屡次方法调用拥有相同的值,因此千万不能将 jobject 对象的值看成 key 使用;

jmethodID 和 jfieldID:

在 JNI 层执行 Java 代码经常使用到 FindClass() 、 GetMethodID() 、 GetFieldID() 

但只有第一个函数返回的 jclass 属于 JNI (局部)引用对象,而 jmethodID 和 jfieldID 并非,它们是指向内部 Runtime 数据结构的指针;

实际上这些 ID 是用于缓存的静态对象:第一次查找会作一次字符串比较,但后面再次调用就能直接读取而变得很快;

JVM 会保证这些 ID 是合法的,直到 Class 被 unload;

因此, jmethodID 和 jfieldID 是不须要手动释放的,固然也不能做为 JNI 全局引用。

其余非 JNI 引用:

除了上面提到的 ID,相似 GetStringUTFChars() 和 GetByteArrayElements() GetCharArrayElements() 等函数返回的也是 Raw Data 指针,而非 JNI 引用;

在调用相对应的 ReleaseXXX() 函数释放前,它们都是合法的;

批量操做 JNI 引用:

通常状况下要避免大量建立 JNI 局部引用,最好用完后当即释放(实际上目前的实现只预留了 16 个局部引用的空间);

若是确实须要大量操做 JNI 局部引用,要么调用 EnsureLocalCapacity() 指定更多的空间,要么调用 PushLocalFrame() PopLocalFrame() 批量分配/释放:

 

env->PushLocalFrame(128);
jobjectArray array = env->NewObjectArray(128, gMyClass, NULL);
for (int i = 0; i < 128; ++i) {
    env->SetObjectArrayElement(array, i, newMyClass(i));
}
env->PopLocalFrame(array);

 

开启 CheckJNI 检查 JNI 引用问题:

Android 提供了一种叫作 CheckJNI 的模式用于检测常见的 JNI 错误,其中和 JNI 引用相关的错误有:

  • 将 DeleteGlobalRef() DeleteLocalRef() 用于错误的 JNI 引用类型;

  • jfieldID jmethodID 为空或者类型不合法;

可是 CheckJNI 暂时还不能检测 JNI 局部引用的滥用问题,好比:存储了一个 JNI 局部引用,而后在 JNI 函数返回后继续使用。这种状况很显然应该使用 NewGlobalRef() 建立全局 JNI 引用。

开启 CheckJNI 只需一行命令便可:

adb shell setprop debug.checkjni 1

参考 

转自:http://ju.outofmemory.cn/entry/224516

相关文章
相关标签/搜索