[Java] [ Android ] [ JNI ] [ 局部引用、全局引用、弱全局引用 ] [ NewObject等、NewGlobalRef、NewWeakGlobalRef ]

1、局部引用,NewObject等众多接口返回的引用

下例以FindClass返回的引用,存储于全局变量中,或静态变量中。
第一次运行,创建,正常,
第二次之后,使用存储于全局变量的引用去操作,结果报异常

#include <jni.h>
#include <string>

//
// Created by luohy on 2018/9/4.
//

/* This code is illegal */
jclass stringClass = NULL;

extern "C"
JNIEXPORT jstring JNICALL
Java_com_lhy_comm_test_JNITest_MyNewString(
        JNIEnv* env,
        jobject /* this */) {
        /* This code is illegal */
        //static jclass stringClass = NULL;

        jmethodID cid;
        jcharArray elemArr;
        jstring result;
        if (stringClass == NULL) {
            stringClass = (env)->FindClass("java/lang/String");
            if (stringClass == NULL) {
                return NULL; /* exception thrown */
            }
        }
    
        /* It is wrong to use the cached stringClass here,
           because it may be invalid. */
        cid = (env)->GetMethodID(stringClass, "<init>", "([C)V");
        elemArr = (env)->NewCharArray( 10);
        result = (jstring)(env)->NewObject(stringClass, cid, elemArr);
        (env)->DeleteLocalRef( elemArr);

        return result;
}

运行结果:
抛出已删除引用的异常

这种保存局部引用到全局或静态变量中的方式是不正确的, 因为FindClass()返回的是对java.lang.String的局部引用。
在native代码从MyNewString返回退出时,VM 会释放所有局部引用,包括存储在stringClass变量中的指向类对象的引用。
这样当后继再次调用MyNewString时,直接使用stringClass变量去操作,可能会访问非法地址,导致内存被破坏,或者系统崩溃。

局部引用失效,有两种方式:‘ 1)系统会自动释放局部变量。
2)程序员可以显示地管理局部引用的生命周期,例如调用DeleteLocalRef。

局部对象只属于创建它们的线程,只在该线程中有效。 一个线程想要调用另一个线程创建的局部引用是不被允许的。
将一个局部引用保存到全局变量中,然后在其它线程中使用它,这是一种错误的编程。

全局引用,使用NewGlobalRef

和局部引用不一样(局部变量可以由多数JNI函数创建),全局引用只能由一个JNI函数创建(NewGlobalRef)。

/* This code is OK */
extern "C"
JNIEXPORT jstring JNICALL
Java_com_lhy_comm_test_JNITest_MyNewGlobalString(
        JNIEnv* env, jclass clazz)
{
    static jclass stringClass = NULL;
    
    if (stringClass == NULL) {
        jclass localRefCls =
                (jclass)(env)->FindClass( "java/lang/String");
        if (localRefCls == NULL) {
            return NULL; /* exception thrown */
        }
        /* Create a global reference */
        stringClass = (jclass)(env)->NewGlobalRef( localRefCls);
        /* The local reference is no longer useful */
        (env)->DeleteLocalRef( localRefCls);
        /* Is the global reference created successfully? */
        if (stringClass == NULL) {
            return NULL; /* out of memory exception thrown */
        }
    }

    jmethodID cid;
    jcharArray elemArr;
    jstring result;
    cid = (env)->GetMethodID(stringClass, "<init>", "([C)V");
    elemArr = (env)->NewCharArray( 10);
    result = (jstring)(env)->NewObject(stringClass, cid, elemArr);
    (env)->DeleteLocalRef( elemArr);

    return result;
}  

一个全局引用可以跨越多个线程 并且保证了所引用的对象不会被垃圾回收,在被程序员释放之前,一致有效 (因此,小心内存泄露哦)
所有的native方法均可以访问全局引用变量

弱全局引用NewWeakGlobalRef

弱全局引用是在java 2 SDK1.2才出现的。
它由NewGolableWeakRef函数创建,并且被DeleteGloablWeakRef函数摧毁。
和全局引用一样,它可以跨native方法调用,也可以跨越不同线程。
但是和全局引用不同的是,它不阻止对基础对象的垃圾回收。

/* This code is OK */
extern "C"
JNIEXPORT jstring JNICALL
Java_com_lhy_comm_test_JNITest_MyNewWeakGlobalRefString(
        JNIEnv* env, jclass clazz)
{
    static jclass stringClass = NULL;

    if (stringClass == NULL) {
        jclass localRefCls =
                (jclass)(env)->FindClass( "java/lang/String");
        if (localRefCls == NULL) {
            return NULL; /* exception thrown */
        }
        /* Create a global reference */
        stringClass = (jclass)(env)->NewWeakGlobalRef( localRefCls);
        /* The local reference is no longer useful */
        (env)->DeleteLocalRef( localRefCls);
        /* Is the global reference created successfully? */
        if (stringClass == NULL) {
            return NULL; /* out of memory exception thrown */
        }
    }

    jmethodID cid;
    jcharArray elemArr;
    jstring result;
    cid = (env)->GetMethodID(stringClass, "<init>", "([C)V");
    elemArr = (env)->NewCharArray( 10);
    result = (jstring)(env)->NewObject(stringClass, cid, elemArr);
    (env)->DeleteLocalRef( elemArr);

    return result;
}

弱全局引用在一个被native代码缓存着的引用不想阻止基础对象被垃圾回收时,非常有用。
如以上例子,假设是使用mypkg.MyCls2,那么通过将mypkg.MyCls2缓存到弱引用中,能够实现MyCls2类依旧可以被卸载。
因此,一般还需要在使用前判断引用是否有效,方法见下。

比较引用

可以用JNI函数IsSameObject来检查给定的两个局部引用、全局引用或者弱全局引用,是否指向同一个对象。
在java VM中NULL是null的引用。
如果一个对象obj是局部引用或者全局引用,则可以这样来检查它是否指向null对象:
(*env)->IsSameObject(env, obj, NULL) 或者 NULL == obj

而对于弱全局引用,以上规则需要改变一下:
我们可以用这个函数来判断一个非0弱全局引用wobj所指向的对象是否仍旧存活着(依旧有效)。
(*env)->IsSameObject(env, wobj, NULL)

释放引用

除了引用的对象要占用内存,每个JNI引用本身也会消耗一定内存。
作为一个JNI程序员,应该对在一段给定的时间里,程序会用到的引用的个数,做到心中有数。
特别是,尽管程序所创建的局部引用最终会被VM会被自动地释放,仍旧需要知道在程序在执行期间的任何时刻,创建的局部引用的上限个数。
创建过多的引用,即便他们是瞬间、短暂的,也会导致内存耗尽。

  • 释放局部引用
    多数情况下,在执行一个native方法时,你不需要担心局部引用的释放,java VM会在native方法返回调用者的时候释放。
    然而有时候需要JNI程序员显示的释放局部引用,来避免过高的内存使用。
    那么什么时候需要显示的释放呢,且看一下情景:
    1)在单个native方法调用中,创建了大量的局部引用。这可能会导致JNI局部引用表溢出。
    此时有必要及时地删除那些不再被使用的局部引用。
    例如以下代码,在该循环中,每次都有可能创建一个巨大的字符串数组。
    在每个迭代之后,native代码需要显示地释放指向字符串元素的局部引用:
for (i = 0; i < len; i++) {  
    jstring jstr = (*env)->GetObjectArrayElement(env, arr, i);  
    ... /* process jstr */  
    (*env)->DeleteLocalRef(env, jstr);  
}  

2)你可能要创建一个工具函数,它会被未知的上下文调用。
例如之前提到到MyNewString这个例子,它在每次返回调用者欠,都及时地将局部引用释放。

3)native方法,可能不会返回(例如,一个可能进入无限事件分发的循环中的方法)。
此时在循环中释放局部引用,是至关重要的,这样才能不会无限期地累积,进而导致内存泄露。

4)native方法可能访问一个巨大的对象,因此,创建了一个指向该对象的局部引用。
native方法在返回调用者之前,除访问对象之外,还执行了额外的计算。指向这个大对象的局部引用,将会包含该对象,以防被垃圾回收。
这个现象会持续到native方法返回到调用者时,即便这个对象不会再被使用,也依旧会受保护。
在以下例子中,由于在lengthyComputation()前,显示地调用了DeleteLocalRef,所以垃圾回收器有机会可以释放lref所指向的对象。

/* A native method implementation */  
JNIEXPORT void JNICALL  
Java_pkg_Cls_func(JNIEnv *env, jobject this)  
{  
    lref = ...              /* a large Java object */  
    ...                     /* last use of lref */  
    (*env)->DeleteLocalRef(env, lref);  
    lengthyComputation();   /* may take some time */  
    return;                 /* all local refs are freed */  
}  

这个情形的实质,就是允许程序在native方法执行期间,java的垃圾回收机制有机会回收native代码不在访问的对象。

在java 2 SDK1.2中管理局部引用
不知道java 7怎么样了,应该更强大吧,有时间,去看看,这里且按照java2的特性来吧。
SDK1.2中提供了一组额外的函数来管理局部引用的生命周期。他们是EnsureLocalCapacity、NewLocalRef、PushLocalFram以及PopLocalFram。
JNI的规范要求VM可以自动确保每个native方法可以创建至少16个局部引用。
经验显示,如果native方法中未包含和java VM的对象进行复杂的互相操作,这个容量对大多数native方法而言,已经足够了。
如果,出现这还不够的情况,需要创建更多的局部引用,那么native方法可以调用EnsureLocalCapacity来保证这些局部引用有足够的空间。

  • 释放全局引用
    在native代码不再需要访问一个全局引用的时候,应该调用DeleteGlobalRef来释放它。
    如果调用这个函数失败,Java VM将不会回收对应的对象。
    在native代码不在需要访问一个弱全局引用的时候,应该调用DeleteWeakGlobalRef来释放它。
    如果调用这个函数失败了,java VM 仍旧将会回收对应的底层对象,但是,不会回收这个弱引用本身所消耗掉的内存。

管理引用的规则

未完待续… …

参考

https://blog.csdn.net/huguohu2006/article/details/7397055

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值