【Android NDK 开发】JNI 引用 ( 局部引用 | 局部引用作用域 | 局部引用产生 | 局部引用释放 | 代码示例)

2023-03-27 17:54:17 浏览数 (2)

文章目录
  • I . JNI 引用数据类型
  • II . JNI 引用 与 指针
  • III . 局部引用 作用域
  • IV . 局部引用 产生 与 释放
  • V . 局部引用 代码示例

I . JNI 引用数据类型

1 . JNI 引用类型 : JNI 中 定义了 八种 Java 基本数据类型 , 其余的 jobject , jarray , jxxxArray , jclass , jstring 等都是引用类型 ;

① 规律 : 除 八种 基本数据类型之外的都是引用数据类型 ;

② 都是 Java 引用数据类型 : 这些数据类型都是 C/C 中定义的 Java 引用数据类型 , 其本质是 C/C 环境中对应的 Java 数据类型 ;

要注意将 JNI 中的 Java 类型引用 , 与 C/C 指针区分开 , 两者概念不同 ;

2 . JNI 引用类型分为三类 :

① 局部引用 : 其只在作用域内有效 , 内存不可回收 ;

② 全局引用 : 全局有效 , 内存不可回收 ;

③ 全局弱引用 : 全局有效 , 内存不足时会被 JVM 回收 ;

内存不可回收 , 如果内存不足 , 会直接 OOM 内存溢出 ;

II . JNI 引用 与 指针

在 JNI 中一定要将 引用 和 指针 区分开 ;

引用 是 Java 语言中的概念 , 指针 是 C/C 中的概念 ;

JNI 中 Java 引用类型 也是使用 C/C 指针表示的 , 这个 变量 就有了 两重含义 , 即代表 Java 中的引用类型 , 又代表了 编程环境中的 指针 ;

这里注意 , 如果引用被 JVM 释放了 , 即使指针还有值 , 也是不能用于 JNI 中与 Java 引用类型 相关的方法的 ;

本博客只讨论引用相关的内容 ;

III . 局部引用 作用域

1 . 局部引用作用域 :

局部引用只能在当前作用域有效 ;

超出作用域 手动释放 上面 两种情况 都会导致 局部引用变量 失效 ;

2 . 局部引用作用范围 :

① 空间 : 不能 跨线程 , 跨方法调用 , 仅在本作用域有效 ;

② 时间 : 创建后可以使用 , 手动释放 或 作用域结束 引用被释放不可使用 ;

IV . 局部引用 产生 与 释放

1 . 局部引用产生 与 释放 :

① 局部引用产生 : 使用 NewXXX / FindXXX 等 大多数 JNI 方法 默认创建的 Java 引用类型对象 都是局部引用 ;

② 局部引用释放 : 调用 DeleteLocalRef 方法 释放该局部引用 ;

2 . 局部引用的两种释放方式 :

① 自动释放 : 在方法作用域结束后 , JVM 自动释放上述 局部引用 变量 ;

② 手动释放 : 通过调用 DeleteLocalRef 方法手动释放 ;

3 . 局部引用推荐释放方式 :

① 内存角度考虑 : 局部引用 释放尽量灵活 , 不要等待自动释放 , 在使用完毕后 建议就手动释放 , 尽早回收内存 ;

② 自动释放情况 :如果该 引用 一直到最后都要使用 , 那么可以不进行手动释放 ;

③ 建议用法 : 局部引用建议都要手动释放 , 哪怕是在作用域最后 , 也要进行手动释放

V . 局部引用 代码示例

局部引用代码示例 :

代码语言:javascript复制
extern "C"
JNIEXPORT void JNICALL
Java_kim_hsl_jni_MainActivity_jniLocalReferenceTest(JNIEnv *env, jobject instance) {


    /*
        局部引用
            局部引用只能在当前作用域有效
                超出作用域
                手动释放
            上面 两种情况 都会导致 该局部变量都会失效


        局部引用作用范围 :
                空间 : 不能 跨线程 , 跨方法调用 , 仅在本作用域有效
                时间 : 创建后可以使用 , 手动释放 或 作用域结束 引用被释放不可使用

        局部引用 创建 : 使用 NewXXX / FindXXX 等 大多数 JNI 方法 默认创建的都是局部引用
                释放 : 调用 DeleteLocalRef 方法 释放该局部引用


        关于上面的三个创建的 局部引用 有两种释放方式
            方式一 : 在方法作用域结束后 , VM 自动释放上述变量
            方式二 : 通过调用 DeleteLocalRef 方法手动释放

        建议使用方式二 :
            局部引用 释放尽量灵活 , 不要等待自动释放 , 在使用完毕后 建议就手动释放 , 今早回收内存
            如果该 引用 一直到最后都要使用 , 那么可以不进行手动释放 ;

            建议用法 : 局部引用建议都要手动释放 , 哪怕是在作用域最后 , 也要进行手动释放

        局部引用传递到 Java 层 , 该传递是拷贝传递 , JNI 中该释放还是释放 , 不影响 Java 层使用


        引用概念 :
            这里要将 引用 和 指针的概区分清楚 ;
            class_teacher 引用在 作用域结束时 会被释放 , 不能将其用于 JNI 反射 Java 类的方法和字段
                          其指针值不为空 , 仍然有值 , 其仍然指向一个地址 , 但是地址中的数据被释放了

     */


    // 1 . 获取 Teacher 类 ( 该变量需要释放 )
    jclass class_teacher = env->FindClass("kim/hsl/jni/Teacher");

    // 2 .  查找构造方法
    jmethodID method_init = env->GetMethodID(class_teacher, "<init>", "(ILjava/lang/String;)V");


    // 3 . 准备 Java 类型参数 ( 该变量需要释放 )
    //     此处特别注意 : 传入到 Java 方法中的参数都必须是 Java 参数
    jint teacher_age = 88;
    jstring teacher_name = env->NewStringUTF("Tom Wang");

    // 4 .  创建 Teacher 对象 ( 该变量需要释放 )
    jobject teacher = env->NewObject(class_teacher, method_init, teacher_age, teacher_name);

    // 5 .  释放上面通过 FindClass NewStringUTF NewObject 创建的引用变量 , 否则会造成内存泄漏
    //     使用完这三个引用之后 , 不再使用 ; 这里特别建议手动释放三个引用
    //     如果不手动释放 , 在 该引用 作用域 结束后 , 也会自动释放掉
    env->DeleteLocalRef(teacher_name);
    env->DeleteLocalRef(teacher);
    env->DeleteLocalRef(class_teacher);

}

0 人点赞