前言
简单的记录一下JNI的局部引用,全局引用和 ,这对于写程序还是很有帮助的。
正文
深入了解是,先看看JNI中引用的重点知识。
-
-
局部引用和全局引用有不同的生命周期。当本地方法返回时,局部引用会被自动释放。而全局引用和弱引用必须手动释放。
-
局部引用或者全局引用会阻止 GC 回收它们所引用的对象,而弱引用则不会 。
-
不是所有的引用可以被用在所有的场合。例如,一个本地方法创建一个局部引用并返回后,再对这个局部引用进行访问是非法的。
局部引用
局部引用(Local Reference)最常见的引用类型。
释放一个局部引用有两种方式,一个是本地函数执行完后VM自动释放,另外一个是通过DeleteLocalRef手动释放。
既然局部引用自动释放,那为什么还需手动释放?
如果不调用DeleteLocalRef ,局部引用在函数返回后会被回收;如果调用DeleteLocalRef ,局部引用会立即被回收。两种方式最后都会释放,但立即释放(调用DeleteLocalRef )可一定的缓解内存少的问题。
这个感觉跟Java中的GC原理有点类似,对象不用了,不一定马上会被回收,需要等到一定的条件才会回收。(个人理解)
全局引用
全局引用(Global Reference)可以跨方法、跨线程使用,直到它被手动释放才会失效。同局部引用一样,全局引用也会阻止它所引用的对象被 GC 回收。
全局引用需要通过NewGlobalRef()函数进行创建!
jstring MyNewString(JNIEnv *env, jchar *chars, jint len){ static jclass stringClass = NULL; ... if (stringClass == NULL) { jclass localRefCls =(*env)->FindClass(env, "java/lang/String"); if (localRefCls == NULL) { return NULL; } //创建全局引用 stringClass = (*env)->NewGlobalRef(env, localRefCls); //删除局本引用 (*env)->DeleteLocalRef(env, localRefCls); //判读全局引用是否创建成功 if (stringClass == NULL) { return NULL; } } ... }
码中通过FindClass返回的局部引用localRefCls,然后在通过NewGlobalRef创建String类的全局引用。删除localRefCls后,再检查NewGlobalRef是否成功。
与局部引用不同,全局引用的创建不是由 JNI 自动创建的,全局引用需要调用 NewGlobalRef 函数,而释放它需要使用 ReleaseGlobalRef 函数。
下面是全局引用创建和删除函数,以及局部引用的删除函数。
#全局引用 jobject (*NewGlobalRef)(JNIEnv*, jobject); void (*DeleteGlobalRef)(JNIEnv*, jobject); #局部引用 void (*DeleteLocalRef)(JNIEnv*, jobject);
弱全局引用
弱全局引用 (Weak Global Reference),简称弱引用,JDK 1.2 引入。弱引用使用 NewGlobalWeakRef 创建,使用 DeleteGlobalWeakRef 释放。
与全局引用类似,弱引用可以跨方法、线程使用。与全局引用不同的是,弱引用不会阻止 GC 回收它所指向的 VM 内部的对象。
由于弱引用会被回收,所以在使用前最好通过IsSameObject进行判断是否回收了。
缓存变量
既然介绍了,总得试试身手吧。在JNI中为了获取java属性和方法,会先获取jfieldID与jmethodID,然后进行查询属性值或调用java方法。如果将jfieldID与jmethodID缓存起来,调用时不必每次都查询。
一般缓存有如下两种方式:
-
使用时缓存
-
初始化时缓存
使用时缓存
使用时缓存,也即是第一次调用是就保存下来,等下次再次调用时直接使用缓存的值即可。
JNIEXPORT jint JNICALL native_jni_computer(JNIEnv *env, jobject object, jstring type, jint x, jint y{ int count = -1; jclass clazz = clazz = env->GetObjectClass(object); //根据type进行加减乘 const char *charType = env->GetStringUTFChars(type, 0); if (!strcmp(charType, "-")) { //静态变量,只会初始化一次 static jmethodID subMethodID = NULL; if(NULL == subMethodID){ subMethodID = env->GetMethodID(clazz, "sub", "(II)I"); } if(NULL != subMethodID){ count = env->CallIntMethod(object, subMethodID, x, y); } } else if (!strcmp(charType, "+")) { //静态变量 static jmethodID addMethodID = NULL; if(NULL == addMethodID ){ addMethodID = env->GetMethodID(clazz, "add", "(II)I"); } if(NULL != addMethodID){ count = env->CallIntMethod(object, addMethodID, x, y); } } else if (!strcmp(charType, "*")) { //静态变量 static jmethodID multiplyMethodId = NULL; if(NULL == multiplyMethodId ){ multiplyMethodId = env->GetStaticMethodID(clazz, "multiply", "(II)I"); } if(NULL != multiplyMethodId){ count = env->CallStaticIntMethod(clazz, multiplyMethodId, x, y); } } env->ReleaseStringUTFChars(type, charType); return count; }
初始化时缓存
Android Framework中就很多用这种方式,也就是提前init。
static jfieldID mBooleanFieldID; static jfieldID mIntFieldID; static jmethodID mSubMethodID; static jmethodID mAddMethodID; JNIEXPORT void JNICALL native_jni_init(JNIEnv *env, jobject obj){ jclass clazz = env->FindClass(DYNAMIC_CLASS); mjobject = env->NewWeakGlobalRef(obj); mBooleanFieldID = env->GetFieldID(clazz, "mBooleanValue", "Z"); mIntFieldID = env->GetFieldID(clazz, "mIntValue", "I"); mSubMethodID = env->GetMethodID(clazz, "sub", "(II)I"); mAddMethodID = env->GetMethodID(clazz, "add", "(II)I"); }
参考文章
-
《JNI编程指南中文版》
-
《JNI完全手册》
-
《》
-
《深入理解Android卷1(邓凡平)》