JNI中的引用

前几天在写NDK项目的时候,了解到了关于JNI引用的一些概念,因为以前对于NDK的概念比较模糊,因此在这里记录一下对应的学习资料。html

注:全部的资料能够再oracle官方文档介绍上看到。java


在NDK引用中,总共有三个引用的方式:android

  • 全局引用(Global Reference)
  • 局部引用(Local Reference)
  • 弱全局引用(Weak Global Reference)

上述的三个引用全都是引用了Java层的Object对象,也就是说在若是操做不当,会致使GC操做不能及时回收对应对象,致使内存泄漏的问题。c++

全局引用

首先了解一下全局引用,在jni.h中能够看到,全局引用的方式经过oracle

jobject NewGlobalRef(jobject obj)
    { return functions->NewGlobalRef(this, obj); }

就能够得到一个全局的引用,这里查询一下oracle的官方资料,资料解释以下:ide

Creates a new global reference to the object referred to by the obj argument. The obj argument may be a global or local reference. Global references must be explicitly disposed of by calling DeleteGlobalRef().函数

意思说的很明确:经过此方法能够建立一个对于object(即传进来的第二个参数)的全局引用,不管object是全局变量的仍是局部变量。生成全局引用后必须显式的调用DeleteGlobalRef()去删除对应的全局引用(在咱们不须要使用的时候)。工具

删除全局引用方法以下:学习

void DeleteGlobalRef(jobject globalRef)
    { functions->DeleteGlobalRef(this, globalRef); }

Demo代码示例以下:ui

static jobject globalRef;
//设置全局引用
void setGlobal(JNIEnv *env, jobject obj){
    jclass contextClass= env->FindClass("android.content.Context");
    globalRef =(env->NewGlobalRef(contextClass));
}
//删除全局引用
void deteleGlobal(JNIEnv *env){
    if (globalRef != NULL) {
        env->DeleteGlobalRef(globalRef);
    }
}

局部引用

局部引用在jni.h的实现以下:

jobject NewLocalRef(jobject ref)
    { return functions->NewLocalRef(this, ref); }
	
 void DeleteLocalRef(jobject localRef)
    { functions->DeleteLocalRef(this, localRef); }

官方资料上的解释以下:

Local references are valid for the duration of a native method call. They are freed automatically after the native method returns. Each local reference costs some amount of Java Virtual Machine resource. Programmers need to make sure that native methods do not excessively allocate local references. Although local references are automatically freed after the native method returns to Java, excessive allocation of local references may cause the VM to run out of memory during the execution of a native method.

局部变量只在执行一个native方法内有效,当native方法执行完毕后,系统会自动的把分配的内存给释放掉。每一个声明的局部变量都要消耗JVM的内存资源(这个应该是在JVM的本地方法栈以及堆上进行分配相关的资源)。开发须要确保不能再native方法中去过度的分配局部引用。即便局部引用可以在方法执行完毕主动释放,过度的分配局部引用可能会形成VM的oom的问题。

Demo示例代码以下:

void useLocal(JNIEnv *env, jobject obj){
    jclass contextClass= env->FindClass("android.content.Context");
    jobject localRef =(env->NewLocalRef(contextClass));
    ....//do sth
    env->DeleteLocalRef(localRef);
}

对于jni.h中的方法,当方法的返回为jobject对象,咱们就须要注意是局部引用,为了以防万一,能够手动进行局部引用的释放,如GetObjectArrayElement()FindClass(),NewOject()等等;固然对于若是是一些使用New...或者Get....的方法的,也须要调用相对应的Release...()方法进行释放引用。

这里引伸出一个小问题,若是咱们在一个方法中的局部引用对象数量必须超过最大的数量,那该怎么办呢? 其实也好解决,oracle提供了对应的方法容许咱们经过EnsureLocalCapacity ()进行扩容的操做。

方法实现以下:

jint EnsureLocalCapacity(JNIEnv *env, jint capacity);

经过当前方法,咱们能够肯定是否能在当前线程下建立对应capacity的数量的局部引用,返回0表明成功,不然返回负数或者报OOM异常。在默认的状况下,VM会确保每一个方法至少16个数量的局部引用能被建立。

if (env->EnsureLocalCapacity(len) != 0) {
        throw " EnsureLocalCapacity error";
        return;
    }
    for (int i = 0; i < len; i++) {
        jstring jstr = static_cast<jstring>(env->GetObjectArrayElement(arr, i));

     //不须要手动调用DeleteLocalRef

    }

除了上面的方法外,jni还提供了方法PushLocalFrame() PopLocalFrame ()来管理循环建立局部引用的操做。

//PushLocalFrame 
jint PushLocalFrame(JNIEnv *env, jint capacity);

//PopLocalFrame 
jobject PopLocalFrame(JNIEnv *env, jobject result);
  • PushLocalFrame 方法会建立一个局部栈帧来管理嵌套做用域的局部引用,参数二传入对应栈帧的大小。
  • PopLocalFrame 方法移除全部存储在栈帧的局部变量,消除对应的引用,若是result==NULL,则不返回东西,若是result不为NULL,返回对应object的引用。
//#define N_REFS ... /*最大局部引用数量*/
jstring other_jstr;
   if (env->PushLocalFrame(N_REFS) != 0) {
        ... /*内存溢出*/
    }
for (i = 0; i < len; i++) {
     jstring jstr = (*env)->GetObjectArrayElement(env, arr, i);
     ... /* 使用jstr */
     if (i == 2) {
        other_jstr = jstr;
     }
}
 other_jstr = env->PopLocalFrame(other_jstr);  // 销毁局部引用栈前返回指定的引用

在管理局部引用的生命周期中,Push/PopLocalFrame是很是方便的。你能够在本地函数的入口处调用PushLocalFrame,而后在出口处调用PopLocalFrame,这样的话,在函数对中间任何位置建立的局部引用都会被释放。

若是你在函数的入口处调用了PushLocalFrame,记住在全部的出口(有return出现的地方)调用PopLocalFrame。

大量的局部引用建立会浪费没必要要的内存。一个局部引用会致使它自己和它所指向的对象都得不到回收。尤为要注意那些长时间运行的方法、建立局部引用的循环和工具函数,充分得利用Pus/PopLocalFrame来高效地管理局部引用。

弱全局引用

关于弱引用在jni.h的声明以下:

jweak NewWeakGlobalRef(jobject obj)
    { return functions->NewWeakGlobalRef(this, obj); }

    void DeleteWeakGlobalRef(jweak obj)
    { functions->DeleteWeakGlobalRef(this, obj); }

官方资料的解释以下:

Weak global references are a special kind of global reference. Unlike normal global references, a weak global reference allows the underlying Java object to be garbage collected. Weak global references may be used in any situation where global or local references are used. When the garbage collector runs, it frees the underlying object if the object is only referred to by weak references. A weak global reference pointing to a freed object is functionally equivalent to NULL. Programmers can detect whether a weak global reference points to a freed object by using IsSameObject to compare the weak reference against NULL. Weak global references in JNI are a simplified version of the Java Weak References, available as part of the Java 2 Platform API ( java.lang.ref package and its classes).

弱全局引用相似于在Java层面的弱引用类型,容许Java对象被GC。若是Java层的对象的只被Native的变量弱全局引用了,那么当GC开始的时候,Java对象是能够被回收的,当Java对象被回收以后,Native的对应对象则等于NULL。开发同窗可使用IsSameObject()来判断当前弱全局引用对象是否被回收。

Demo代码示例以下:

static jobject globalRef;

void useLocal(JNIEnv *env, jobject obj){
    jclass contextClass= env->FindClass("android.content.Context");
    globalRef =(env->NewWeakGlobalRef(contextClass));
    ...
   
}
void deteleGlobal(JNIEnv *env){
    if (globalRef != NULL) {
        env->DeleteWeakGlobalRef(globalRef);
    }
}

参考资料:

http://www.javashuo.com/article/p-rflmqcrm-nr.html

https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/functions.html

相关文章
相关标签/搜索