JNI中的引用

前幾天在寫NDK項目的時候,瞭解到了關於JNI引用的一些概念,因爲以前對於NDK的概念比較模糊,因此在這裏記錄一下對應的學習資料。html

注:全部的資料能夠再oracle官方文檔介紹上看到。java


在NDK引用中,總共有三個引用的方式:android

  • 全局引用(Global Reference)
  • 局部引用(Local Reference)
  • 弱全局引用(Weak Global Reference)

上述的三個引用全都是引用了Java層的Object對象,也就是說在若是操做不當,會致使GC操做不能及時回收對應對象,致使內存泄漏的問題。c++

全局引用

首先了解一下全局引用,在jni.h中能夠看到,全局引用的方式經過oracle

jobject NewGlobalRef(jobject obj)
    { return functions->NewGlobalRef(this, obj); }

就能夠得到一個全局的引用,這裏查詢一下oracle的官方資料,資料解釋以下:ide

Creates a new global reference to the object referred to by the obj argument. The obj argument may be a global or local reference. Global references must be explicitly disposed of by calling DeleteGlobalRef().函數

意思說的很明確:經過此方法能夠建立一個對於object(即傳進來的第二個參數)的全局引用,不管object是全局變量的仍是局部變量。生成全局引用後必須顯式的調用DeleteGlobalRef()去刪除對應的全局引用(在咱們不須要使用的時候)。工具

刪除全局引用方法以下:學習

void DeleteGlobalRef(jobject globalRef)
    { functions->DeleteGlobalRef(this, globalRef); }

Demo代碼示例以下:ui

static jobject globalRef;
//設置全局引用
void setGlobal(JNIEnv *env, jobject obj){
    jclass contextClass= env->FindClass("android.content.Context");
    globalRef =(env->NewGlobalRef(contextClass));
}
//刪除全局引用
void deteleGlobal(JNIEnv *env){
    if (globalRef != NULL) {
        env->DeleteGlobalRef(globalRef);
    }
}

局部引用

局部引用在jni.h的實現以下:

jobject NewLocalRef(jobject ref)
    { return functions->NewLocalRef(this, ref); }
	
 void DeleteLocalRef(jobject localRef)
    { functions->DeleteLocalRef(this, localRef); }

官方資料上的解釋以下:

Local references are valid for the duration of a native method call. They are freed automatically after the native method returns. Each local reference costs some amount of Java Virtual Machine resource. Programmers need to make sure that native methods do not excessively allocate local references. Although local references are automatically freed after the native method returns to Java, excessive allocation of local references may cause the VM to run out of memory during the execution of a native method.

局部變量只在執行一個native方法內有效,當native方法執行完畢後,系統會自動的把分配的內存給釋放掉。每一個聲明的局部變量都要消耗JVM的內存資源(這個應該是在JVM的本地方法棧以及堆上進行分配相關的資源)。開發須要確保不能再native方法中去過度的分配局部引用。即便局部引用可以在方法執行完畢主動釋放,過度的分配局部引用可能會形成VM的oom的問題。

Demo示例代碼以下:

void useLocal(JNIEnv *env, jobject obj){
    jclass contextClass= env->FindClass("android.content.Context");
    jobject localRef =(env->NewLocalRef(contextClass));
    ....//do sth
    env->DeleteLocalRef(localRef);
}

對於jni.h中的方法,當方法的返回爲jobject對象,咱們就須要注意是局部引用,爲了以防萬一,能夠手動進行局部引用的釋放,如GetObjectArrayElement()FindClass(),NewOject()等等;固然對於若是是一些使用New...或者Get....的方法的,也須要調用相對應的Release...()方法進行釋放引用。

這裏引伸出一個小問題,若是咱們在一個方法中的局部引用對象數量必須超過最大的數量,那該怎麼辦呢? 其實也好解決,oracle提供了對應的方法容許咱們經過EnsureLocalCapacity ()進行擴容的操做。

方法實現以下:

jint EnsureLocalCapacity(JNIEnv *env, jint capacity);

經過當前方法,咱們能夠肯定是否能在當前線程下建立對應capacity的數量的局部引用,返回0表明成功,不然返回負數或者報OOM異常。在默認的狀況下,VM會確保每一個方法至少16個數量的局部引用能被建立。

if (env->EnsureLocalCapacity(len) != 0) {
        throw " EnsureLocalCapacity error";
        return;
    }
    for (int i = 0; i < len; i++) {
        jstring jstr = static_cast<jstring>(env->GetObjectArrayElement(arr, i));

     //不須要手動調用DeleteLocalRef

    }

除了上面的方法外,jni還提供了方法PushLocalFrame() PopLocalFrame ()來管理循環建立局部引用的操做。

//PushLocalFrame 
jint PushLocalFrame(JNIEnv *env, jint capacity);

//PopLocalFrame 
jobject PopLocalFrame(JNIEnv *env, jobject result);
  • PushLocalFrame 方法會建立一個局部棧幀來管理嵌套做用域的局部引用,參數二傳入對應棧幀的大小。
  • PopLocalFrame 方法移除全部存儲在棧幀的局部變量,消除對應的引用,若是result==NULL,則不返回東西,若是result不爲NULL,返回對應object的引用。
//#define N_REFS ... /*最大局部引用數量*/
jstring other_jstr;
   if (env->PushLocalFrame(N_REFS) != 0) {
        ... /*內存溢出*/
    }
for (i = 0; i < len; i++) {
     jstring jstr = (*env)->GetObjectArrayElement(env, arr, i);
     ... /* 使用jstr */
     if (i == 2) {
        other_jstr = jstr;
     }
}
 other_jstr = env->PopLocalFrame(other_jstr);  // 銷燬局部引用棧前返回指定的引用

在管理局部引用的生命週期中,Push/PopLocalFrame是很是方便的。你能夠在本地函數的入口處調用PushLocalFrame,而後在出口處調用PopLocalFrame,這樣的話,在函數對中間任何位置建立的局部引用都會被釋放。

若是你在函數的入口處調用了PushLocalFrame,記住在全部的出口(有return出現的地方)調用PopLocalFrame。

大量的局部引用建立會浪費沒必要要的內存。一個局部引用會致使它自己和它所指向的對象都得不到回收。尤爲要注意那些長時間運行的方法、建立局部引用的循環和工具函數,充分得利用Pus/PopLocalFrame來高效地管理局部引用。

弱全局引用

關於弱引用在jni.h的聲明以下:

jweak NewWeakGlobalRef(jobject obj)
    { return functions->NewWeakGlobalRef(this, obj); }

    void DeleteWeakGlobalRef(jweak obj)
    { functions->DeleteWeakGlobalRef(this, obj); }

官方資料的解釋以下:

Weak global references are a special kind of global reference. Unlike normal global references, a weak global reference allows the underlying Java object to be garbage collected. Weak global references may be used in any situation where global or local references are used. When the garbage collector runs, it frees the underlying object if the object is only referred to by weak references. A weak global reference pointing to a freed object is functionally equivalent to NULL. Programmers can detect whether a weak global reference points to a freed object by using IsSameObject to compare the weak reference against NULL. Weak global references in JNI are a simplified version of the Java Weak References, available as part of the Java 2 Platform API ( java.lang.ref package and its classes).

弱全局引用相似於在Java層面的弱引用類型,容許Java對象被GC。若是Java層的對象的只被Native的變量弱全局引用了,那麼當GC開始的時候,Java對象是能夠被回收的,當Java對象被回收以後,Native的對應對象則等於NULL。開發同窗可使用IsSameObject()來判斷當前弱全局引用對象是否被回收。

Demo代碼示例以下:

static jobject globalRef;

void useLocal(JNIEnv *env, jobject obj){
    jclass contextClass= env->FindClass("android.content.Context");
    globalRef =(env->NewWeakGlobalRef(contextClass));
    ...
   
}
void deteleGlobal(JNIEnv *env){
    if (globalRef != NULL) {
        env->DeleteWeakGlobalRef(globalRef);
    }
}

參考資料:

http://www.javashuo.com/article/p-rflmqcrm-nr.html

https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/functions.html

相關文章
相關標籤/搜索