您的位置:首页 > 编程语言 > Java开发

JNI中的全局引用、局部引用、弱全局引用 AND 缓存jfieldID和jmethodID的两种方法

2014-05-10 15:52 375 查看
本文系转载,对原文排版进行了整理,并且写了一些例子,读原文请到http://www.verydemo.com/demo_c89_i204525.html,谢谢!

JNI的引用

  从Java虚拟机创建的对象传到本地C/C++代码时会产生引用,根据Java的垃圾回收机制,只要有引用存在就不会出发该引用指向的Java对象的垃圾回收。
这些引用在JNI中分为三种:
全局引用:Global Reference
局部引用:Local Reference
弱全局引用:Weak Global Reference since JDK1.2

局部引用

1)最常见的引用类型,基本上通过JNI返回来的引用都是局部引用。例如使用NewObject就会返回创建出来的实例的局部引用,局部引用只在本native函数中有效,所有在该函数中产生的局部引用,都会在函数返回的时候自动释放,也可以使用DeleteLocalRef函数手动释放该引用。

2)想一想既然局部引用能够在函数返回时自动释放,为什么还需要DeleteLocalRef函数呢?

3)实际上,局部引用存在,就会防止其指向的对象被垃圾回收,尤其是当一个局部引用指向一个很庞大的对象,或是在一个循环中生成了局部引用,最好的做法就是在使用完该对象后,或在循环尾部把这个引用释放掉,以确保在垃圾回收器被触发的时候被回收。

4)在局部引用的有效期中,可以传递到别的本地函数中,要强调的是他的有效期仍然只在一次的Java本地函数调用中,所以千万不能用C++全局变量保存它或者把它定义为C++静态局部变量。
下面是一个我亲身体会的错误例子:这个native函数中,我把上层传下来obj引用传给了initMember函数。
void Java_com_cugNewAir_monitor_MonitorApplication_xxxx(JNIEnv *env, jobject obj, jobject monitorApplication)
{
jniHelper.initMember(obj);
}
我们接下来看一看initMember函数吧
void JNIhelper::initMember(jobject obj) {
this->monitorObj = obj;

// 获取JNIEnv, 其与线程相关
JNIEnv* env = this->getEnv();
if(env == NULL){
LOGE("get JNIEnv failed!");
return;
}
}

看到没有,我竟然还把它保存了起来,看到这里,大家应该知道,我的代码中有错误了你吧,注意哦,你发现没有,我获取了JNIEnv,为什么呢?, 告诉大家吧,JNIEnv与线程相关,不能把某处获取的JNIEnv拿到其它地方随便用,最好是啥时候用,啥时候获取,所以定义getEnv函数:
JNIEnv* JNIhelper::getEnv() {
// 获取JNIEnv, 其与线程相关
JNIEnv* env;
if (this->vm->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) {
LOGE("JNIEnv get Error!");
return NULL;
}
return env;
}
上面代码中,vm是JNI_Onload函数出现来的JavaVM的指针,这个变量保存下来是没问题的,这个地方不需要纠结。
那么上面的代码怎么改正呢?  创建一个全局引用会是一个不错的选择吧,那么接着看呗!

全局引用

1)全局引用可以跨越当前线程,在多个native函数中有效,不过需要编程人员手动来释放该引用,全局引用存在期间会防止在Java的垃圾回收。

2)与局部引用不同,全局引用的创建不是由JNI自动创建的,全局引用时需要调用NewGlobalRef函数,而释放它需要使用ReleaseGlobalRef函数。

看到这里,你是不是觉得,上面的问题就可以实现,但是是不是感觉,不知道该在什么时候释放全局引用呢? 不释放会影响Java垃圾回收,这可怎么办啊?看来还得接着看啊!

弱全局引用

1)Java1.2新出来的功能,与全局引用相似,创建跟删除都需要由编程人员来进行。这种引用全局引用一样可以再多个本地代码有效,也跨越多线程有效,不一样的是,这种引用将不会阻止垃圾回收器回收这个引用所指向的对象。

2)使用NewWeakGlobalRef跟ReleaseWeakGlobalRef来产生和解除引用

哈哈,终于找到一种比较适合我的方式了,这样就不用担心自己会影响Java垃圾回收了,hoho~

关于引用的一些函数

jobject NewGlobalRef(jobject obj);
jobject NewLocalRef(jobject obj);
jobject NewWeakGlobalRef(jobject obj);
void DeleteGlobalRef(jobject obj);
void DeleteLocalRef(jobject obj);
void DeleteWeakGlobalRef(jobject obj);
jboolean IsSameObject(jobject obj1, jobject obj2); // 这个函数对于弱全局引用还有一个特别的功能,把NULL传入要比较的对象中,就能够判断弱全局引用所指向的Java对象是否被回收。


缓存jfieldID,jmethodID

 
取得jieldID跟jmethodID的时候会通过该属性、方法名称加上签名来查询相应的jfieldID,jmethodID。这种查询相对来说开销较大,我们可以将这些FieldID,MethodID缓存起来,
这样只需要查询一次,以后就使用缓存起来的FieldID,MethodID。

介绍两种缓存方式

1.在用的时候缓存 
在native code中使用static局部变量来保存已经查询过的id,这样就不会再每次的函数调用时查询,而只要第一次查询成功后就保存起来了。

不过在这种情况下就不得不考虑多线程同时呼叫此函数时可能会招致同时查询的危机,不过这种情况是无害的,因为查询同一个属性,方法的ID通常返回的是一样的值。

2.在Java类初始化时缓存

    更好的一个方式就是在任何native函数调用前把id全部存起来。我们可以让Java在第一次加载这个类的时候首先调用本地代码初始化所有的jfieldID,jmethodID,这样的话,就可以省去多次的确定id是否存在的语句,当然,这些jfieldID,jmethodID是定义在C/C++的全局。使用这种方式的好处,当Java类卸载或是重新加载的时候,也会重新呼叫该本地代码来重新计算IDs。
void JNIhelper::initMember() {

// 获取JNIEnv, 其与线程相关
JNIEnv* env = this->getEnv();
if(env == NULL){
LOGE("get JNIEnv failed!");
return;
}

//加载监控类
jclass monitor = env->FindClass(MONITOR_APPLICATION);

if (monitor != NULL) {

//加载java层方法
this->queryConnectID = env->GetMethodID(monitor, "queryConnect",
"(Ljava/lang/String;I)Z");
if (this->queryConnectID == NULL) {
LOGE("queryConnect not found!");
}

this->queryUrlID = env->GetMethodID(monitor, "queryAdUrl",
"(Ljava/lang/String;)Z");
if (this->queryConnectID == NULL) {
LOGE("queryUrl not found!");
}

// 加载java层属性
this->bCanQueryID = env->GetFieldID(monitor, "bCanQuery", "Z");
if (this->bCanQueryID == NULL) {
LOGE("bCanQuery not found!");
}

} else {
LOGE("class not found");
}
}
顺便提一下,获取函数或者字段签名的方法是  javap -s <.class文件>
最后,希望这篇文章能为为你提供一些帮助!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java jni