JNI 引用, DeleteLocalRef使用場景詳解

局部引用:

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 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章