知識
不管是網(wǎng)站,軟件還是小程序,都要直接或間接能為您產(chǎn)生價值,我們在追求其視覺表現(xiàn)的同時,更側(cè)重于功能的便捷,營銷的便利,運營的高效,讓網(wǎng)站成為營銷工具,讓軟件能切實提升企業(yè)內(nèi)部管理水平和效率。優(yōu)秀的程序為后期升級提供便捷的支持!
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的一些方法比如FindClass
,NewCharArray
,NewStringUTF
等只要是返回上面介紹的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)存溢出和局部引用表溢出。
- 如上我們模擬的情況,在for循環(huán)里或者其它操作類似頻繁創(chuàng)建局部引用的需要釋放
- 遍歷數(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)
,jweak
為typedef _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++的都知道
- static局部變量只初始化一次,下一次依據(jù)上一次結(jié)果值
- static全局變量只初使化一次,防止在其他文件中被引用
- 加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)案例查看更多
相關(guān)閱讀
- 小程序開發(fā)平臺前十名
- 報廢車回收管理軟件
- 云南網(wǎng)站建設(shè)優(yōu)化
- 軟件開發(fā)
- 云南網(wǎng)站建設(shè)方法
- 前端技術(shù)
- 前端
- 網(wǎng)站建設(shè)報價
- 網(wǎng)站建設(shè)需要多少錢
- 百度自然排名
- 云南網(wǎng)站建設(shè)方案 doc
- 云南網(wǎng)站建設(shè)電話
- 昆明做網(wǎng)站建設(shè)的公司排名
- 全國前十名小程序開發(fā)公司
- 云南網(wǎng)站制作
- 云南網(wǎng)站建設(shè)首選
- 用戶登錄
- 網(wǎng)站小程序
- 小程序模板開發(fā)公司
- 制作一個小程序
- 小程序開發(fā)排名前十名
- 汽車報廢軟件
- 網(wǎng)站建設(shè)
- 海報插件
- 江蘇小程序開發(fā)
- 云南軟件開發(fā)
- 云南網(wǎng)絡(luò)公司
- 開發(fā)制作小程序
- 云南網(wǎng)絡(luò)營銷
- 昆明軟件定制公司