獲取字符串
錯誤?:沒有正確釋放,會導(dǎo)致內(nèi)存泄漏
const char *str = env->GetStringUTFChars(jstr, nullptr);
正確?:必須調(diào)用 ReleaseStringUTFChars 釋放
const char *str = env->GetStringUTFChars(jstr, nullptr);
// TODO use str
env->ReleaseStringUTFChars(jstr, str);
錯誤?:Release 之后就不能再使用
const char *str = env->GetStringUTFChars(jstr, nullptr);
env->ReleaseStringUTFChars(jstr, str);
// TODO use str
正確?:可以把 char* 轉(zhuǎn)換成 std::string再使用
const char *c_str = env->GetStringUTFChars(jstr, nullptr);
std::string str(c_str, env->GetStringLength(jstr));
env->ReleaseStringUTFChars(jstr, c_str);
// TODO use str
正確?:自己分配空間,自己進(jìn)行 delete 釋放。如果數(shù)據(jù)不大,推薦先轉(zhuǎn)換成 std::string。
int size = env->GetStringLength(jstr);
char *c_arr = new char[size];
env->GetStringRegion(jstr, 0, size, (jchar *) c_arr);
// TODO use c_arr
delete[] c_arr;
獲取數(shù)組數(shù)據(jù)
錯誤?:沒有正確釋放,會導(dǎo)致內(nèi)存泄漏
auto int_arr = env->GetIntArrayElements(jint_arr, nullptr);
正確?
auto int_arr = env->GetIntArrayElements(jint_arr, nullptr);
// TODO use int_arr
env->ReleaseIntArrayElements(jint_arr, int_arr, 0);
錯誤?:不能在 Release 之后使用,會導(dǎo)致野指針。
auto int_arr = env->GetIntArrayElements(jint_arr, nullptr);
env->ReleaseIntArrayElements(jint_arr, int_arr, 0);
// TODO use int_arr
正確?:如果需要在 Release 之后使用,那么就要自行分配內(nèi)存,然后拷貝,但是自己分配的內(nèi)存也要釋放。
int size = env->GetArrayLength(jint_arr);
auto int_arr = env->GetIntArrayElements(jint_arr, nullptr);
int *int_arr2 = new int[size];
memcpy(int_arr2, int_arr, sizeof(int) * size);
env->ReleaseIntArrayElements(jint_arr, int_arr, 0);
// TODO use int_arr
delete [] int_arr2;
正確?:自己分配內(nèi)存,使用 GetIntArrayRegion 拷貝數(shù)據(jù),無需調(diào)用 ReleaseIntArrayElements 函數(shù)。但是也要注意釋放自己分配的空間。
int size = env->GetArrayLength(jint_arr);
int *int_arr = new int[size];
env->GetIntArrayRegion(jint_arr, 0, size, int_arr);
// TODO use int_arr
delete[] int_arr;
基本原則
- GetStringUTFChars 和 ReleaseStringUTFChars,GetXXArrayElements 和 ReleaseXXArrayElements,必須對應(yīng)起來,否則會導(dǎo)致內(nèi)存泄漏。
- GetXXArrayElements 生成的數(shù)據(jù)不能在 ReleaseXXArrayElements 之后使用。
- 如果是在JNI函數(shù)內(nèi)通過NewStringUTF、NewXXXArray或NewObject創(chuàng)建的java對象無論是否需要返回java層,都不需要手動釋放,jvm會自動管理。但是如果是通過 AttachCurrentThread 創(chuàng)建的 JNIEnv 去New的對象,必須通過 DeleteLocalRef 方式及時刪除,因?yàn)樵诰€程銷毀之前,創(chuàng)建的對象無法自動回收。
- 通過 NewGlobalRef 創(chuàng)建的對象必須手動釋放。
- FindClass 和 GetMethodID 不需要釋放。
- 如果不是通過 NewGlobalRef 函數(shù)創(chuàng)建的java對象不能跨線程調(diào)用,jclass 也是 jobject,如果是在 JNI_OnLoad 創(chuàng)建,那么必須通過 NewGlobalRef 函數(shù)處理后才能正常使用。
問題
GetStringUTFChars 和 GetStringRegion,GetByteArrayElements 和 GetByteArrayRegion 區(qū)別
GetStringUTFChars 和 GetByteArrayElements 函數(shù)都是jni幫我們分配內(nèi)存然后拷貝jvm對象的信息到native層,需要通知jni去釋放內(nèi)存。而 GetStringRegion 和 GetByteArrayRegion 是我們自己分配好內(nèi)存,然后把指針傳給jni,jni往指針寫入數(shù)據(jù),所以不需要jni去釋放內(nèi)存,但是我們自己分配的空間不需要使用后必須釋放。
GetByteArrayElements 獲取的數(shù)據(jù),執(zhí)行ReleaseByteArrayElements后是否還能使用?
不能,會導(dǎo)致出現(xiàn)野指針。剛Release就使用看起來是正常,實(shí)際上是因?yàn)閮?nèi)存的數(shù)據(jù)沒有馬上被抹除,一段時間后再使用就會出現(xiàn)出現(xiàn)野指針問題。
僅允許在ReleaseByteArrayElements之前使用,如果需要使用可以自己分配內(nèi)存把內(nèi)存拷貝出來,但是要注意,要自己銷毀。如果需要離開jni函數(shù)后繼續(xù)使用數(shù)據(jù),推薦使用GetByteArrayRegion。
GetByteArrayElements/ReleaseByteArrayElements 的數(shù)據(jù),是否可以手動 delete 銷毀?
不能,由于已經(jīng)被銷毀,不能重復(fù)銷毀。
JNIEnv 是否可以復(fù)用
JNIEnv 只允許在相同的線程上使用,如果需要使用JNIEnv,可以在 JNI_OnLoad 函數(shù)把 JavaVM 保存下來,在使用的時候去創(chuàng)建 JNIEnv。這種情況同時是在 native 異步調(diào)用 Java 對象的時候使用。
JavaVM *jvm = nullptr;
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env = nullptr;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}
jvm = vm;
return JNI_VERSION_1_6;
}
void GetJNIEnv(JNIEnv *&env) {
int status = jvm->GetEnv((void **) &env, JNI_VERSION_1_6);
// 獲取當(dāng)前native線程是否有沒有被附加到j(luò)vm環(huán)境中
if (status == JNI_EDETACHED) {
// 如果沒有, 主動附加到j(luò)vm環(huán)境中,獲取到env
if (jvm->AttachCurrentThread(&env, nullptr) != JNI_OK) {
// Failed to attach
}
} else if (status == JNI_OK) {
// success
} else if (status == JNI_EVERSION) {
// GetEnv: version not supported
}
}
void test() {
JNIEnv *env;
GetJNIEnv(env);
jstring jstr = env->NewStringUTF("hello world");
}
子線程調(diào)用 FindClass 出錯
JNI DETECTED ERROR IN APPLICATION: JNI NewGlobalRef called with pending exception java.lang.ClassNotFoundException: Didn't find class "com.cross.Cross" on path: DexPathList[[directory "."],nativeLibraryDirectories=[/system/lib64, /system_ext/lib64, /system/lib64, /system_ext/lib64]]
java_vm_ext.cc:577] at java.lang.Class dalvik.system.BaseDexClassLoader.findClass(java.lang.String) (BaseDexClassLoader.java:207)
java_vm_ext.cc:577] at java.lang.Class java.lang.ClassLoader.loadClass(java.lang.String, boolean) (ClassLoader.java:379)
java_vm_ext.cc:577] at java.lang.Class java.lang.ClassLoader.loadClass(java.lang.String) (ClassLoader.java:312)
java_vm_ext.cc:577]
java_vm_ext.cc:577] in call to NewGlobalRef
如果在 C++ 創(chuàng)建子線程,通過 AttachCurrentThread 獲取到 JNIEnv,調(diào)用 FindClass 是會報錯的,通常而言,如果需要 FindClass ,盡量在 JNI_OnLoad 去創(chuàng)建一個全局的變量。這是因?yàn)橥ㄟ^AttachCurrentThread附加到虛擬機(jī)的線程在查找類時只會通過系統(tǒng)類加載器進(jìn)行查找,不會通過應(yīng)用類加載器進(jìn)行查找,因此可以加載系統(tǒng)類,但是不能加載非系統(tǒng)類。
JavaVM *jvm = nullptr;
jclass cross_class;
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env = nullptr;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}
cross_class = (jclass) env->NewGlobalRef(env->FindClass("com/cross/Cross"));
jvm = vm;
return JNI_VERSION_1_6;
}