(五)JNI 引用、缓存

版权声明:本文为博主原创文章,未经博主允许不得转载。

本文纯个人学习笔记,由于水平有限,难免有所出错,有发现的可以交流一下。

一、JNI 引用

C 通过 JNI 进行操作 java 的对象的方法、属性,需要从 JVM 中获取到的引用。

JNI 引用分为三种:局部引用、全局引用、弱全局应用。

1.局部引用

FindClass、NewObject、GetObjectClass、NewCharArray…. 、NewLocalRef() 等生成非基本数据都是局部引用。

C:

JNIEXPORT void JNICALL Java_com_xiaoyue_JNIMain_localRef
(JNIEnv * env, jobject jobj) {
    int i = 0;
    for (i = 0; i < 5; i++)
    {
        jclass cls = (*env)->FindClass(env, "java/util/Date");
        jmethodID jmid = (*env)->GetMethodID(env, cls, "<init>", "()V");
        //创建一个Date类型的局部引用
        jobject obj = (*env)->NewObject(env, cls, jmid);
        //使用这个引用

        //释放引用
        (*env)->DeleteLocalRef(env, cls);
        (*env)->DeleteLocalRef(env, obj);

    }
}

释放:局部引用的释放有两种方式: 1 方法调用完JVM 会自动释放。 2.主动调用 DeleteLocalRef 进行释放。

在安卓的环境下,最多只能有 512 个局部引用,所以有时候需要自己手动对局部引用进行释放,否则会溢出。

局部引用不能应用于多线程中。

2.全局引用

全局引用可以跨方法、跨线程进行引用,JVM 不会自动进行释放,需要自己进行创建于释放的管理,相对比较简单。

C:

jstring global_str;
//创建全局引用
JNIEXPORT void JNICALL Java_com_xiaoyue_JNIMain_createGlobalRef
(JNIEnv * env, jobject jobj) {
    jobject obj = (*env)->NewStringUTF(env, "JNI is intersting");
    global_str = (*env)->NewGlobalRef(env, obj);
}

//调用全局引用
JNIEXPORT jstring JNICALL Java_com_xiaoyue_JNIMain_getGlobalRef
(JNIEnv * env, jobject jobj) {
    return global_str;
}

//释放全局引用
JNIEXPORT void JNICALL Java_com_xiaoyue_JNIMain_delGlobalRef
(JNIEnv * env, jobject jobj) {
    (*env)->DeleteGlobalRef(env, global_str);

}

3.弱全局引用

弱全局引用基于全局引用,类似 java 中的弱引用,不会阻止 GC,在内存不足的时候,可能会被回收而释放,也可以自己调用 DeleteWeakGlobalRef 进行手动释放。

C:

jclass g_weak_cls;
JNIEXPORT jstring JNICALL Java_com_xiaoyue_JNIMain_createWeakRef
(JNIEnv * env, jobject jobj) {
    jclass cls_string = (*env)->FindClass(env, "java/lang/String");
    g_weak_cls = (*env)->NewWeakGlobalRef(env, cls_string);
    return g_weak_cls;
}

二、缓存

JNI 的缓存是使用 C 中的关键字 static,分为局部静态变量缓存全局静态变量缓存

1.局部静态变量缓存

C:

// 局部静态变量进行缓存
JNIEXPORT void JNICALL Java_com_xiaoyue_JNIMain_cached
(JNIEnv * env, jobject jobj) {
    jclass cls = (*env)->GetObjectClass(env, jobj);
    static jfieldID fid = NULL;
    if (fid == NULL) {
        fid = (*env)->GetFieldID(env, cls, "key", "Ljava/lang/String;");
        printf("GetFieldID\n");
    }
}

这样在 java 中多次调用 cached 方法,除了第一次进来 fid 为空,会执行 if 下的代码,后面在调用改方法的时候,fid 不为空,不会再执行 if 下的代码。

使用 static 修饰的局部变量,会存储在静态区,只会定义一次,方法执行完之后 JVM 不会进行释放,后面访问的时候直接获取这个已经定义的。

2.全局静态变量缓存

C:

//全局的变量
jfieldID fid_glb;
JNIEXPORT void JNICALL Java_com_xiaoyue_JNIMain_cachedGlobal
(JNIEnv *env, jobject jobj) {
    if (fid_glb == NULL) {
        jclass cls = (*env)->GetObjectClass(env, jobj);
        fid_glb = (*env)->GetFieldID(env, cls, "key", "Ljava/lang/String;");
        printf("GetFieldID\n");
    }
}

全局变量,只要在 fid_glb 的定义之后的代码,都可以使用 fid_glb。(fid_glb 定义前有没有使用 static 都是全局变量)

3.缓存和局部引用联合使用带来的问题

C:

JNIEXPORT jstring JNICALL Java_com_xiaoyue_JNIMain_AcessCacheNewString
(JNIEnv * env, jobject jobj) {
    //定义一个静态的局部变量
    static jclass cls_string = NULL;
    if (cls_string == NULL)
    {
        printf("Java_JniMain_AcessCache_newString out: \n");
        //给局部静态变量赋一个局部引用
        cls_string = (*env)->FindClass(env, "com/xiaoyue/Refrence");
    }
    //使用这个静态局部变量 
    jmethodID jmid = (*env)->GetMethodID(env, cls_string, "getRef", "(I)I");
    jthrowable ex = (*env)->ExceptionOccurred(env);
    if (ex != NULL)
    {
        jclass newExc;
        // 让java 继续运行
        (*env)->ExceptionDescribe(env);//输出关于这个异常的描述
        (*env)->ExceptionClear(env);
        printf("C exceptions happend\n");
    }

    printf("alvin out Java_JniMain_AcessCache_newString\n");
    return NULL;
}

这是在 JNI 中将缓存策略与局部引用相结合使用,看过是感觉是没有问题。但是局部引用在方法调用完之后会进行释放,等待 JVM 回收。这个释放的是局部缓存 cls_string 所指向的内容,而不是 cls_string 本身。当内存不足的时候,虚拟机把 cls_string 指向的内容进行释放,但是 cls_string 本身还在,则 cls_string 变成了一个野指针,后续的使用则会报错。

猜你喜欢

转载自blog.csdn.net/qq_18983205/article/details/79007262