Android NDK 开发中正确释放 JNI 对象

获取字符串

错误❌:没有正确释放,会导致内存泄漏

const char *str = env->GetStringUTFChars(jstr, nullptr);

正确✅:必须调用 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* 转换成 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

正确✅:自己分配空间,自己进行 delete 释放。如果数据不大,推荐先转换成 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;

获取数组数据

错误❌:没有正确释放,会导致内存泄漏

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 之后使用,会导致野指针。

auto int_arr = env->GetIntArrayElements(jint_arr, nullptr);
env->ReleaseIntArrayElements(jint_arr, int_arr, 0);
// TODO use int_arr

正确✅:如果需要在 Release 之后使用,那么就要自行分配内存,然后拷贝,但是自己分配的内存也要释放。

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;

正确✅:自己分配内存,使用 GetIntArrayRegion 拷贝数据,无需调用 ReleaseIntArrayElements 函数。但是也要注意释放自己分配的空间。

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,必须对应起来,否则会导致内存泄漏。
  • GetXXArrayElements 生成的数据不能在 ReleaseXXArrayElements 之后使用。
  • 如果是在JNI函数内通过NewStringUTF、NewXXXArray或NewObject创建的java对象无论是否需要返回java层,都不需要手动释放,jvm会自动管理。但是如果是通过 AttachCurrentThread 创建的 JNIEnv 去New的对象,必须通过 DeleteLocalRef 方式及时删除,因为在线程销毁之前,创建的对象无法自动回收。
  • 通过 NewGlobalRef 创建的对象必须手动释放。
  • FindClass 和 GetMethodID 不需要释放。
  • 如果不是通过 NewGlobalRef 函数创建的java对象不能跨线程调用,jclass 也是 jobject,如果是在 JNI_OnLoad 创建,那么必须通过 NewGlobalRef 函数处理后才能正常使用。

问题

GetStringUTFChars 和 GetStringRegion,GetByteArrayElements 和 GetByteArrayRegion 区别

GetStringUTFChars 和 GetByteArrayElements 函数都是jni帮我们分配内存然后拷贝jvm对象的信息到native层,需要通知jni去释放内存。而 GetStringRegion 和 GetByteArrayRegion 是我们自己分配好内存,然后把指针传给jni,jni往指针写入数据,所以不需要jni去释放内存,但是我们自己分配的空间不需要使用后必须释放。

GetByteArrayElements 获取的数据,执行ReleaseByteArrayElements后是否还能使用?

不能,会导致出现野指针。刚Release就使用看起来是正常,实际上是因为内存的数据没有马上被抹除,一段时间后再使用就会出现出现野指针问题。

仅允许在ReleaseByteArrayElements之前使用,如果需要使用可以自己分配内存把内存拷贝出来,但是要注意,要自己销毁。如果需要离开jni函数后继续使用数据,推荐使用GetByteArrayRegion。

GetByteArrayElements/ReleaseByteArrayElements 的数据,是否可以手动 delete 销毁?

不能,由于已经被销毁,不能重复销毁。

JNIEnv 是否可以复用

JNIEnv 只允许在相同的线程上使用,如果需要使用JNIEnv,可以在 JNI_OnLoad 函数把 JavaVM 保存下来,在使用的时候去创建 JNIEnv。这种情况同时是在 native 异步调用 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);
    // 获取当前native线程是否有没有被附加到jvm环境中
    if (status == JNI_EDETACHED) {
        // 如果没有, 主动附加到jvm环境中,获取到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");
}

子线程调用 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++ 创建子线程,通过 AttachCurrentThread 获取到 JNIEnv,调用 FindClass 是会报错的,通常而言,如果需要 FindClass ,尽量在 JNI_OnLoad 去创建一个全局的变量。这是因为通过AttachCurrentThread附加到虚拟机的线程在查找类时只会通过系统类加载器进行查找,不会通过应用类加载器进行查找,因此可以加载系统类,但是不能加载非系统类。

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;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容