全局引用/局部引用/弱全局引用
从java虚拟机创建的对象传到本地c/c++代码是会产生引用。根据java垃圾回收机制,只要有引用存在就不会触发引用指向的java对象的垃圾回收。
这些引用在JNI中分三种:
1、全局引用(Global Reference)
2、局部引用(Local Reference)
3、弱全局引用(Weak Global Reference)
---------------------------------------
1、局部引用(常用)基本上通过JNI返回来的引用都是局部引用。
例如:NewObject就会返回创建出来的实例的局部引用。局部应用只在该native函数中有效。所有在该函数中产生的局部引用,都会在函数返回的时候自动释放(freed),也可以使用DeleteLocalRef函数自动释放该引用
在局部引用的有效期中,可以传递到别的函数中,要强调的是他的有效期仍然只在一次的java本地函数调用中,所以千万不能用C++全局变量保存他或是把它定义为C++静态局部变量
2、全局引用
全局引用可以跨越当前线程,在多个native函数中有效,不过需要编程人员手动来释放该引用,全局引用存在期间会防止在java的垃圾回收的回收。
与局部引用不同,全局引用的创建不由JNI自动创建的,全局引用是需要调用NewGlobealRef函数,而释放他需要使用ReleaseGlobalRef函数。
3、弱全局引用
java1.2出来的功能,与全局引用相似,创建、删除都需要由编程人员来进行,这种引用全局引用一样,可以在多个本地代码有效,也跨越多线程有效,不过,这种引用将不会阻止垃圾回收器回收这个引用所指向的对象。
使用NewWeakGlobalRef跟ReleaseWeakGlobalRef来产生和解除引用。
关于引用的一些函数:
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);//判断两个引用是否指向同一个java对象
这个函数对于弱全局引用有一个特别的功能,把Null传入要比较的对象中,就能够判断弱全局引用所指向的java对象是否被回收
----------------------------------------------------------------------
缓存:
缓存jfieldID/jmethodID
取得jfieldID跟jmethodID的时候会通过该属性/方法名加上签名来查询相应的jfieldID/jmethodID,这种查询相对开销较大,我们可以将这些FieldID/MethodID缓存起来,这样只需要查询一次,以后就使用缓存起来的FieldID/MethodID了
缓存的方式:
1、在使用的时候缓存(Caching at the Point of Use)
2、在Java类初始化时缓存(Caching at the Defining Class's Inititalizer)
----------------
在第一次使用的时候缓存:
在native code中使用static局部变量来保存已经查询过的ID,这样就不会在每次的函数调用时查询,而只要第一次查询成功后就保存起来了。
例子:JNIEXPORT void JNICALL Java_test_native(JNIEnv* env,jobject obj)

{ 

 static jfieldID fieldID_string =NULL; 

jclass clazz=env->GetObjectClass(obj); 

 if(fieldID_string==NULL) 

{ 

 fieldID_string=env->GetFieldID(class,"string","Ljava/lang/String;"); 

} 

.......................


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

public class TestNative 

{ 

 static{ initNativeIDs();} 

 static native void initNativeIDs(); 

 int propInt=0; 

 String propStr=""; 

 public native void otherNative(); 

 public static void main(String[] args) {} 

} 

c/c++代码中: 

jfieldID g_propInt_id=0; 

jfieldID g_propStr_id=0; 

JNIEXPORT void JNICALL 

Java_TestNative_initNativeIDs(JNIEnv* env,jobject clazz) 

{ 

 g_propInt_id=GetFieldID(clazz,"propInt","I"); 

 g_propStr_id=GetFieldID(clazz,"propStr","Ljava/lang/String;"); 

} 

JNIEXPORT void JNICALL 

Java_TestNative_otherNative(JNIEnv* env,jobject obj) 

{ 

 //可以直接使用下g_propInt_id和g_propStr_id的值了 

}


------------------------------------------------
使用JNI的两个弊端
1、使用JNI,那么这个Java Application将不能跨平台,如果要移植别的平台上,那么native代码就需要重新编写。
2、java是强类型的语言,而c/c++不是,因此你必须在写JNI时更小心
总之,必须在构建java程序的时候,尽量少使用本地代码