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

相關文章
相關標籤/搜索