局部引用:
JNI 函數內部創建的 jobject
對象及其子類( jclass
、 jstring
、 jarray
等) 對象都是局部引用,它們在 JNI 函數返回後無效;
一般情況下,我們應該依賴 JVM 去自動釋放 JNI 局部引用;但下面兩種情況必須手動調用 DeleteLocalRef()
去釋放:
-
(在循環體或回調函數中)創建大量 JNI 局部引用,即使它們並不會被同時使用,因爲 JVM 需要足夠的空間去跟蹤所有的 JNI 引用,所以可能會造成內存溢出或者棧溢出;
-
如果對一個大的 Java 對象創建了 JNI 局部引用,也必須在使用完後手動釋放該引用,否則 GC 遲遲無法回收該 Java 對象也會引發內存泄漏.
全局引用:
全局引用允許你持有一個 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()
;
調用 IsSameObject(env, jobj, NULL)
可以判斷該弱全局引用指向的 Java 對象是否已被 GC 回收。
jobject 對象的引用值不唯一
同一個 jobject
對象的不同引用可能擁有不同的值,比如同一 jobject
對象每次調用 NewGlobalRef()
可能返回不同的值;
要檢查兩個引用是否指向同一個 jobject
對象,必須調用 IsSameObject()
,而不要使用 ==
去比較;
用於描述一個 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
----------------------------------------------------------------------
在c++中new的對象,如果不返回java,必須用release掉,否則內存泄露。包括NewStringUTF,NewObject。如果返回java不必release,java會自己回收。
jstring jstr = env->NewStringUTF((*p).sess_id);
...
env->DeleteLocalRef( jstr);
jobject jobj = env->NewObject(clazz,midInit);
return jobj;
內存泄露可以先從windows資源管理器中,看到隨程序運行,內存不斷增長的趨勢,具體可以用hp jmeter檢測。在運行程序時,加jvm參數 -Xrunhprof:heap=all,cutoff=0 ,生成java.hprof.txt,用jmeter打開,Metric -> Residual Objects (Count),可以看到未回收的對象,選中要查看的對象,點Mark記錄下要查看的對象,Window -> New Window 打開新窗口,用Metric -> Reference Graph Tree,然後點Find Immediately可以看到對象被哪裏引用。
總體原則:釋放所有對object的引用
1.FindClass
jclass ref= (env)->FindClass("java/lang/String");
env->DeleteLocalRef(ref);
2.NewString / NewStringUTF / NewObject / NewByteArray
jstring (*NewString)(JNIEnv*, const jchar*, jsize);
const jchar* (*GetStringChars)(JNIEnv*, jstring, jboolean*);
void (*ReleaseStringChars)(JNIEnv*, jstring, const jchar*);
jstring (*NewStringUTF)(JNIEnv*, const char*);
const char* (*GetStringUTFChars)(JNIEnv*, jstring, jboolean*);
void (*ReleaseStringUTFChars)(JNIEnv*, jstring, const char*);
env->DeleteLocalRef(ref);
3.GetObjectField/GetObjectClass/GetObjectArrayElement
jclass ref = env->GetObjectClass(robj);
env->DeleteLocalRef(ref);
4.GetByteArrayElements和GetStringUTFChars
jbyte* array= (*env)->GetByteArrayElements(env,jarray,&isCopy);
(*env)->ReleaseByteArrayElements(env,jarray,array,0);
const char* input =(*env)->GetStringUTFChars(env,jinput, &isCopy);
(*env)->ReleaseStringUTFChars(env,jinput,input);
5.NewGlobalRef/DeleteGlobalRef
jobject (*NewGlobalRef)(JNIEnv*, jobject);
void (*DeleteGlobalRef)(JNIEnv*, jobject);
例如,
jobject ref= env->NewGlobalRef(customObj);
env->DeleteGlobalRef(customObj);
轉自:https://blog.csdn.net/wangpingfang/article/details/53945479