欧美三级国产三级日韩三级_亚洲熟妇丰满大屁股熟妇_欧美亚洲成人一区二区三区_国产精品久久久久久模特

Android-JNI開發(fā)系列《五》局部引用&全局引用&全局弱引用&緩存策略 - 新聞資訊 - 云南小程序開發(fā)|云南軟件開發(fā)|云南網(wǎng)站建設(shè)-昆明葵宇信息科技有限公司

159-8711-8523

云南網(wǎng)建設(shè)/小程序開發(fā)/軟件開發(fā)

知識

不管是網(wǎng)站,軟件還是小程序,都要直接或間接能為您產(chǎn)生價值,我們在追求其視覺表現(xiàn)的同時,更側(cè)重于功能的便捷,營銷的便利,運營的高效,讓網(wǎng)站成為營銷工具,讓軟件能切實提升企業(yè)內(nèi)部管理水平和效率。優(yōu)秀的程序為后期升級提供便捷的支持!

您當前位置>首頁 » 新聞資訊 » 技術(shù)分享 >

Android-JNI開發(fā)系列《五》局部引用&全局引用&全局弱引用&緩存策略

發(fā)表時間:2020-10-17

發(fā)布人:葵宇科技

瀏覽次數(shù):58

人間觀察

好像什么都來得及,又好像什么都來不及。

本篇文章主要介紹在jni開發(fā)中常見的三種引用的使用方法和注意事項以及jni和java交互的緩存策略。

我們知道Java是一門純面象對象的語言,除了基本數(shù)據(jù)類型外,其它任何類型所創(chuàng)建的對象的內(nèi)存都存在堆空間中。內(nèi)存由JVM 的GC(Garbage Collection)垃圾回收進行管理。

但是對于c,c++中以及用c/c++編寫的jni來說同樣需要手動管理和處理內(nèi)存,特別是引用類型的對象。malloc,realloc,free ,delete ,不像java有jvm對每個進程內(nèi)存的限制,特別是Android移動端使用不當給你oom好不啦。而在c++/c只要你想要多大的來隨便搞(只要系統(tǒng)內(nèi)存充足)但是需要釋放。各有各的優(yōu)勢。

在jni中分為局部引用,全局引用,全局弱引用,個人認為有點類似于java中
局部引用,強引用,軟引用SoftReference。在使用介紹之前我們先看一下jni中的基本類型和引用類型有哪些以及對應(yīng)關(guān)系。

jni數(shù)據(jù)類型

基本數(shù)據(jù)類型

java與Native映射關(guān)系如下表所示:

Java類型Native 類型Descriptionbooleanjbooleanunsigned 8 bitsbytejbytesigned 8 bitscharjcharunsigned 16 bitsshortjshortsigned 16 bitsintjint signed32 bitslong jlongsigned64 bitsfloatjfloat32 bitsdoublejdouble64 bitsvoidvoidnot applicable

引用數(shù)據(jù)類型

外面的為jni中的,括號中的java中的。

  • jobject
    • jclass (java.lang.Class objects)
    • jstring (java.lang.String objects)
    • jarray (arrays)
      • jobjectArray (object arrays)
      • jbooleanArray (boolean arrays)
      • jbyteArray (byte arrays)
      • jcharArray (char arrays)
      • jshortArray (short arrays)
      • jintArray (int arrays)
      • jlongArray (long arrays)
      • jfloatArray (float arrays)
      • jdoubleArray (double arrays)
  • jthrowable (java.lang.Throwable objects)

上面的層次中的jni的引用類型代表了繼承關(guān)系,jbooleanArray繼承jarray,jarray繼承jobject,最終都繼承jobject。

局部引用

通過調(diào)用jni的一些方法比如FindClassNewCharArrayNewStringUTF等只要是返回上面介紹的jni的引用類型都屬于局部引用,局部引用的生命周期只在方法中效,不能垮線程跨方法使用,函數(shù)退出后局部引用所引用的對象會被JVM自動釋放,或顯示調(diào)用DeleteLocalRef釋放。局部引用的也可以通過(*env)->NewLocalRef(env,local_ref)方法創(chuàng)建,一般不常用。

如下示例:

// jni_ref.cpp
// 在jni中調(diào)用java String類構(gòu)造返回String
extern "C" JNIEXPORT jstring JNICALL
Java_com_bj_gxz_jniapp_ref_JNIRef_jnilocalRef(JNIEnv *env, jobject instance) {

    // 局部引用
    jclass local_j_cls = env->FindClass("java/lang/String");

    // 調(diào)用public String(char[] value); 構(gòu)造方法。 為了演示更多的局部引用
    jmethodID j_mid = env->GetMethodID(local_j_cls, "<init>", "([C)V");
    // 局部引用
    jcharArray local_j_charArr = env->NewCharArray(8);
    // 局部引用
    jstring local_str = env->NewStringUTF("LocalRef");
    const jchar *j_char = env->GetStringChars(local_str, nullptr);

    env->SetCharArrayRegion(local_j_charArr, 0, 8, j_char);

    jstring j_str = (jstring) env->NewObject(local_j_cls, j_mid, local_j_charArr);

    // 釋放局部引用,也可以不用調(diào)用在方法結(jié)束后jvm會自動回收,最好有良好的編碼習(xí)慣
    env->DeleteLocalRef(local_j_cls);
    env->DeleteLocalRef(local_str);
    env->DeleteLocalRef(local_j_charArr);

    // 也可以通過NewLocalRef函數(shù)創(chuàng)建 (*env)->NewLocalRef(env,local_ref);這個方法一般很少用。
    // 函數(shù)返回后局部引用所引用的對象會被JVM自動釋放,或調(diào)用DeleteLocalRef釋放。(*env)->DeleteLocalRef(env,local_ref)

    // ReleaseStringChars和GetStringChars對應(yīng)
    env->ReleaseStringChars(j_str, j_char);

    return j_str;
}

例子中的local_j_cls,local_j_charArr,local_j_charArr,j_str 都是局部引用類型。最后調(diào)用了DeleteLocalRef來釋放。
有同學(xué)問了,既然局部引用不用手動釋放,可不可以不用調(diào)用DeleteLocalRef方法。
咦,你這個小可愛,好問題哦!
好問題哦

我網(wǎng)上搜索了下,大部分的文章說了下會有限制。超過512個局部引用(為什么是這個數(shù)字,一看就是一個有情懷的程序員)會造成局部引用表溢出。我還是想測試一下如下

// jni_ref.cpp
    LOG_D("localRefOverflow start");
    for (int i = 0; i < count; i++) {
        jclass local_j_cls = env->FindClass("java/util/ArrayList");
        // env->DeleteLocalRef(local_j_cls);
    }
    LOG_D("localRefOverflow end");

count =513,沒有報錯,打印了localRefOverflow end

count =2000,沒有報錯,打印了localRefOverflow end

count =10000,沒有報錯,打印了localRefOverflow end

count =10 0000,沒有報錯,打印了localRefOverflow end

count =100 0000,沒有報錯,打印了localRefOverflow end

我靠,WTF? 直接for循環(huán)900w次。異常出現(xiàn)了。

2020-10-16 18:05:11.476 26699-26699/com.bj.gxz.jniapp A/zygote: indirect_reference_table.cc:273] JNI ERROR (app bug): local reference table overflow (max=8388608)
2020-10-16 18:05:11.476 26699-26699/com.bj.gxz.jniapp A/zygote: indirect_reference_table.cc:273] local reference table dump:
2020-10-16 18:05:11.476 26699-26699/com.bj.gxz.jniapp A/zygote: indirect_reference_table.cc:273]   Last 10 entries (of 8388608):
2020-10-16 18:05:11.476 26699-26699/com.bj.gxz.jniapp A/zygote: indirect_reference_table.cc:273]     8388607: 0x706ca3a0 java.lang.Class<java.util.ArrayList>
2020-10-16 18:05:11.476 26699-26699/com.bj.gxz.jniapp A/zygote: indirect_reference_table.cc:273]     8388606: 0x706ca3a0 java.lang.Class<java.util.ArrayList>
2020-10-16 18:05:11.476 26699-26699/com.bj.gxz.jniapp A/zygote: indirect_reference_table.cc:273]     8388605: 0x706ca3a0 java.lang.Class<java.util.ArrayList>
2020-10-16 18:05:11.476 26699-26699/com.bj.gxz.jniapp A/zygote: indirect_reference_table.cc:273]     8388604: 0x706ca3a0 java.lang.Class<java.util.ArrayList>
2020-10-16 18:05:11.476 26699-26699/com.bj.gxz.jniapp A/zygote: indirect_reference_table.cc:273]     8388603: 0x706ca3a0 java.lang.Class<java.util.ArrayList>
2020-10-16 18:05:11.477 26699-26699/com.bj.gxz.jniapp A/zygote: indirect_reference_table.cc:273]     8388602: 0x706ca3a0 java.lang.Class<java.util.ArrayList>
2020-10-16 18:05:11.477 26699-26699/com.bj.gxz.jniapp A/zygote: indirect_reference_table.cc:273]     8388601: 0x706ca3a0 java.lang.Class<java.util.ArrayList>
2020-10-16 18:05:11.477 26699-26699/com.bj.gxz.jniapp A/zygote: indirect_reference_table.cc:273]     8388600: 0x706ca3a0 java.lang.Class<java.util.ArrayList>
2020-10-16 18:05:11.477 26699-26699/com.bj.gxz.jniapp A/zygote: indirect_reference_table.cc:273]     8388599: 0x706ca3a0 java.lang.Class<java.util.ArrayList>
2020-10-16 18:05:11.477 26699-26699/com.bj.gxz.jniapp A/zygote: indirect_reference_table.cc:273]     8388598: 0x706ca3a0 java.lang.Class<java.util.ArrayList>
2020-10-16 18:05:11.477 26699-26699/com.bj.gxz.jniapp A/zygote: indirect_reference_table.cc:273]   Summary:
2020-10-16 18:05:11.477 26699-26699/com.bj.gxz.jniapp A/zygote: indirect_reference_table.cc:273]     8388604 of java.lang.Class (3 unique instances)
2020-10-16 18:05:11.477 26699-26699/com.bj.gxz.jniapp A/zygote: indirect_reference_table.cc:273]         3 of java.lang.String (3 unique instances)
2020-10-16 18:05:11.477 26699-26699/com.bj.gxz.jniapp A/zygote: indirect_reference_table.cc:273]         1 of java.lang.String[] (3 elements)
2020-10-16 18:05:11.477 26699-26699/com.bj.gxz.jniapp A/zygote: indirect_reference_table.cc:273]  Resizing failed: Requested size exceeds maximum: 16777216

8388608 ,可以猜測是不同的Android 版本導(dǎo)致,Android經(jīng)常這樣不同的API或者功能在不同的版本上表現(xiàn)不一樣。 而我我用的Android 8.1的系統(tǒng)。為什么512沒有報錯了。
Android 8.0 之前局部引用表的上限是512個引用,Android 8.0后局部引用表上限提升到了8388608個引用。大家想一探究竟的話可以在如下Android 底層代碼中看一下。

需要翻墻
有關(guān)底層源碼

看源碼的同時我們也看到了比如FindClass 等方法,在最后方法的最后都有類似添加到局部引用表里的代碼,也就是說需要我們手動刪除局部引用。

 static jclass FindClass(JNIEnv* env, const char* name) {
    CHECK_NON_NULL_ARGUMENT(name);
    Runtime* runtime = Runtime::Current();
    ClassLinker* class_linker = runtime->GetClassLinker();
    std::string descriptor(NormalizeJniClassDescriptor(name));
    ScopedObjectAccess soa(env);
    mirror::Class* c = nullptr;
    if (runtime->IsStarted()) {
      StackHandleScope<1> hs(soa.Self());
      Handle<mirror::ClassLoader> class_loader(hs.NewHandle(GetClassLoader(soa)));
      c = class_linker->FindClass(soa.Self(), descriptor.c_str(), class_loader);
    } else {
      c = class_linker->FindSystemClass(soa.Self(), descriptor.c_str());
    }
    return soa.AddLocalReference<jclass>(c);
  }

綜上可以看出并不是局部引用不用調(diào)用DeleteLocalRef來釋放。而是建議調(diào)用一下。如果你的jni方法很簡單&與java交互很少也可以不調(diào)用。但是如下的一些情況需要手動顯示的調(diào)用,為了防止內(nèi)存溢出和局部引用表溢出。

  1. 如上我們模擬的情況,在for循環(huán)里或者其它操作類似頻繁創(chuàng)建局部引用的需要釋放
  2. 遍歷數(shù)組產(chǎn)生的局部引用,用完后要刪除。

全局引用

通過調(diào)用jobject NewGlobalRef(jobject obj)基于引用來創(chuàng)建,參數(shù)是jobject類型。它可以跨方法、跨線程使用。JVM不會自動釋放它,必須顯示調(diào)用DeleteGlobalRef手動釋放void DeleteGlobalRef(jobject globalRef)

如下使用示例:
在jni中調(diào)用java String類構(gòu)造返回String

// jni_ref.cpp
static jclass g_j_cls;  // 加static前綴 只對本源文件可見,對其它源文件隱藏
extern "C" JNIEXPORT jstring JNICALL
Java_com_bj_gxz_jniapp_ref_JNIRef_jniGlobalRef(JNIEnv *env, jobject instance) {

    if (g_j_cls == nullptr) {
        jclass local_j_cls = env->FindClass("java/lang/String");
        // 將local_j_cls局部引用改為全局引用
        g_j_cls = (jclass) env->NewGlobalRef(local_j_cls);
    } else {
        LOG_D("g_j_cls else");
    }

    // 調(diào)用public String(String value); 構(gòu)造
    jmethodID j_mid = env->GetMethodID(g_j_cls, "<init>", "(Ljava/lang/String;)V");

    jstring str = env->NewStringUTF("GlobalRef");
    jstring j_str = (jstring) env->NewObject(g_j_cls, j_mid, str);
    return j_str;
}

extern "C" JNIEXPORT void JNICALL
Java_com_bj_gxz_jniapp_ref_JNIRef_delGlobalRef(JNIEnv *env, jobject instance) {
    if (g_j_cls != nullptr) {
        LOG_D("DeleteGlobalRef");
        // 釋放某個全局引用
        env->DeleteGlobalRef(g_j_cls);
    }
}

java調(diào)用

    public native String jniGlobalRef();
    public native void delGlobalRef();
    
    String ret1 = jniRef.jniGlobalRef();
    Log.e(TAG, "jniGlobalRef=" + ret1);
    String ret2 = jniRef.jniGlobalRef();
    Log.e(TAG, "jniGlobalRef=" + ret2);
    jniRef.delGlobalRef();

g_j_cls就是一個全局引用,然后我們多次調(diào)用下jniRef.jniGlobalRef方法打印如下:

2020-10-16 20:30:46.074 29358-29358/com.bj.gxz.jniapp E/JNI: jniGlobalRef=GlobalRef
2020-10-16 20:30:46.074 29358-29358/com.bj.gxz.jniapp D/JNI: g_j_cls else
2020-10-16 20:30:46.074 29358-29358/com.bj.gxz.jniapp E/JNI: jniGlobalRef=GlobalRef
2020-10-16 20:30:46.074 29358-29358/com.bj.gxz.jniapp D/JNI: DeleteGlobalRef

說明全局引用可以起到緩存的效果,為什么要做這個測驗?zāi)?#xff1f; 因為頻繁調(diào)用類似JNI接口FindClass查找java中Class引用時是比較耗性能的,特別是在有交互頻繁的JNI的app中。

弱全局引用

這個有點類似于java的軟引用SoftReference,jvm在內(nèi)存不足的時候會釋放它。通過調(diào)用jweak NewWeakGlobalRef(jobject obj)來創(chuàng)建一個弱全局引用,釋放調(diào)用void DeleteWeakGlobalRef(jweak obj)jweaktypedef _jobject* jweak;
_jobject指針的別名。

如下使用示例,和全局引用一樣把全局引用的方法改為弱全局引用的方法即可。

// jni_ref.cpp
static jclass g_w_j_cls;
extern "C" JNIEXPORT jstring JNICALL
Java_com_bj_gxz_jniapp_ref_JNIRef_jniWeakGlobalRef(JNIEnv *env, jobject instance) {

    if (g_w_j_cls == nullptr) {
        jclass local_j_cls = env->FindClass("java/lang/String");
        // 將local_j_clss局部引用改為弱全局引用
        g_w_j_cls = (jclass) env->NewWeakGlobalRef(local_j_cls);
    } else {
        LOG_D("g_w_j_cls else");
    }

    jmethodID j_mid = env->GetMethodID(g_w_j_cls, "<init>", "(Ljava/lang/String;)V");

    // 使用弱引用時,必須先檢查緩存過的弱引用是指向活動的類對象,還是指向一個已經(jīng)被GC的類對象
    // 檢查弱引用是否活動,即引用的比較IsSameObject
    // 如果g_w_j_cls指向的引用已經(jīng)被回收,會返回JNI_TRUE
    // 如果仍然指向一個活動對象,會返回JNI_FALSE
    jboolean isGC = env->IsSameObject(g_w_j_cls, nullptr);
    if (isGC) {
        LOG_D("weak reference has been gc");
        return env->NewStringUTF("weak reference has been gc");
    } else {
        jstring str = env->NewStringUTF("WeakGlobalRef");
        jstring j_str = (jstring) env->NewObject(g_w_j_cls, j_mid, str);
        return j_str;
    }
}

extern "C" JNIEXPORT void JNICALL
Java_com_bj_gxz_jniapp_ref_JNIRef_delWeakGlobalRef(JNIEnv *env, jobject instance) {
    if (g_w_j_cls != nullptr) {
        // 調(diào)用DeleteWeakGlobalRef來釋放它,如果不手動調(diào)用這個函數(shù)來釋放所指向的對象,JVM仍會回收弱引用所指向的對象,但弱引用本身在引用表中所占的內(nèi)存永遠也不會被回收。
        LOG_D("DeleteWeakGlobalRef");
        env->DeleteWeakGlobalRef(g_w_j_cls);
    }
}

java調(diào)用

        String ret3 = jniRef.jniWeakGlobalRef();
        Log.e(TAG, "jniWeakGlobalRef=" + ret3);
        String ret4 = jniRef.jniWeakGlobalRef();
        Log.e(TAG, "jniWeakGlobalRef=" + ret4);
        jniRef.delWeakGlobalRef();

g_w_j_cls就是一個弱全局引用,然后我們多次調(diào)用下jniRef.jniWeakGlobalRef方法打印如下:

2020-10-16 20:30:46.075 29358-29358/com.bj.gxz.jniapp E/JNI: jniWeakGlobalRef=WeakGlobalRef
2020-10-16 20:30:46.075 29358-29358/com.bj.gxz.jniapp D/JNI: g_w_j_cls else
2020-10-16 20:30:46.075 29358-29358/com.bj.gxz.jniapp E/JNI: jniWeakGlobalRef=WeakGlobalRef
2020-10-16 20:30:46.075 29358-29358/com.bj.gxz.jniapp D/JNI: DeleteWeakGlobalRef

和全局引用一樣可以起到緩存的效果。
剛才我們說了就是弱全局引用在內(nèi)存不足的時候會被jvm回收,怎么判斷它被回收了,判null ,沒錯!當被回收了會為null。所以我們在使用弱全局引用的時候頻道弱全局引用是否還存在。怎么判斷呢?使用引用比較 。

引用比較

在jni中提供了 jboolean IsSameObject(jobject ref1, jobject ref2)方法。如果ref1和ref2指向同個對象則返回JNI_TRUE,否則返回JNI_FALSE

    jclass local_j_cls_1 = env->FindClass("java/util/ArrayList");
    jclass local_j_cls_2 = env->FindClass("java/util/ArrayList");
    jboolean same1 = env->IsSameObject(local_j_cls_1, local_j_cls_2);
    LOG_D("%d",same1);
    jboolean same2= env->IsSameObject(local_j_cls_1, nullptr);
    LOG_D("%d",same2);

輸出 1和0

緩存策略

當我們在本地代碼方法中通過FindClass查找Class、GetMethodID查找方法、GetFieldID獲取類的字段ID和GetFieldValue獲取字段的時候是需要jvm來做很多工作的,可能這個字段ID或者方法是在超類中繼承而來的,那jvm可能還需要層次遍歷。而這些負責和jni交互java中的類的全路徑,字段,方法一般是不會修改了,是固定的。這也是為什么我們在做android混淆打包的時候需要keep這些類,因為這些一般不會變,不能變,變了后jni中會找不到了具體的類,字段,方法了。既然打包后不會變我們是可以進行緩存策略來處理。

另外至于效率提高多少,沒有驗證,不過不重要,如果是頻繁這種查找一般會采用緩存,只查找一次或者在程序初始化的時候提前查找。

對于這類情況的緩存分為基本數(shù)據(jù)類型緩存和引用緩存。

基本數(shù)據(jù)類型緩存

基本數(shù)據(jù)類型的緩存在c,c++中可以借助關(guān)鍵字static處理。
學(xué)過c,c++的都知道

  1. static局部變量只初始化一次,下一次依據(jù)上一次結(jié)果值
  2. static全局變量只初使化一次,防止在其他文件中被引用
  3. 加static函數(shù)的函數(shù)為內(nèi)部函數(shù),只能在本源文件中使用, 和普通函數(shù)的作用域不同
static jclass g_j_cls_cache;
extern "C" JNIEXPORT jstring JNICALL
Java_com_bj_gxz_jniapp_ref_JNIRef_refCache(JNIEnv *env, jobject instance) {
    if (g_j_cls_cache == nullptr) {
        jclass local_j_cls = env->FindClass("java/lang/String");
        // 將local_j_cls局部引用改為全局引用
        g_j_cls_cache = (jclass) env->NewGlobalRef(local_j_cls);
    } else {
        LOG_D("g_j_cls_cache use cache");
    }

    // 調(diào)用public String(String value); 構(gòu)造
    static jmethodID j_mid;
    if (j_mid == nullptr) {
        j_mid = env->GetMethodID(g_j_cls_cache, "<init>", "(Ljava/lang/String;)V");
    } else {
        LOG_D("j_mid use cache");
    }

    jstring str = env->NewStringUTF("refCache");
    jstring j_str = (jstring) env->NewObject(g_j_cls_cache, j_mid, str);
    return j_str;
}

java調(diào)用

        String ret5 = jniRef.refCache();
        Log.e(TAG, "refCache=" + ret5);
        String ret6 = jniRef.refCache();
        Log.e(TAG, "refCache=" + ret6);
        jniRef.delRefCache();

local_j_cls局部引用變?yōu)槿忠?#xff0c;j_mid變量改為static
輸出:

10-16 22:58:21.074 4469-4469/com.bj.gxz.jniapp E/JNI: refCache=refCache
10-16 22:58:21.074 4469-4469/com.bj.gxz.jniapp D/JNI: g_j_cls_cache use cache
10-16 22:58:21.074 4469-4469/com.bj.gxz.jniapp D/JNI: j_mid use cache
10-16 22:58:21.074 4469-4469/com.bj.gxz.jniapp E/JNI: refCache=refCache

有人問local_j_cls局部引用可以加static嗎?不用全局引用/全局弱應(yīng)用? 可以加static,但是不能起到緩存的作用。因為上文說了局部引用在函數(shù)結(jié)束后會被jvm回收了,不然再次使用回到非法內(nèi)存訪問導(dǎo)致應(yīng)用crash,所以正確的做法如上用全局引用/全局弱應(yīng)用。

引用類型的緩存

可以借助上面的全局引用或者弱全局引用,弱全局引用記得在使用前判斷下是否被回收了IsSameObject,最后記得釋放 DeleteGlobalRef ,DeleteWeakGlobalRef。

最后源代碼:https://github.com/ta893115871/JNIAPP

相關(guān)案例查看更多