局部引用:
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
參考 :