JNI:全局引用&局部引用&弱全局引用
從Java虛擬機(jī)創(chuàng)建的對(duì)象傳到本地 C/C++ 代碼時(shí)就會(huì)產(chǎn)生引用。根據(jù)Java的垃圾回收機(jī)制,只要有引用存在就不會(huì)觸發(fā)該引用指向的Java對(duì)象的垃圾回收。這些引用在 JNI 中分為三種
全局引用 (Global Reference)
局部引用 (Local Reference)
弱全局引用 (Weak Global Reference), JDK 1.2 引入
最常見的引用類型,基本上通過JNI返回來的引用都是局部引用
例如,使用NewObject就會(huì)返回創(chuàng)建出來的實(shí)例的局部引用。局部引用只在該native函數(shù)中有效,所有在該函數(shù)中產(chǎn)生的局部引用,都會(huì)在函數(shù)返回的時(shí)候自動(dòng)釋放(freed)。也可以使用DeleteLocalRef函數(shù)進(jìn)行手動(dòng)釋放該引用。
想一想既然局部引用能夠在函數(shù)返回時(shí)自動(dòng)釋放,為什么還需要DeleteLocalRef函數(shù)呢?
實(shí)際上局部引用存在,就會(huì)防止其指向的對(duì)象被垃圾回收。尤其是當(dāng)一個(gè)局部引用指向一個(gè)很龐大的對(duì)象,或是在一個(gè)循環(huán)中生成了局部應(yīng)用;最好的做法就是在使用完該對(duì)象后,或在該循環(huán)尾部把這個(gè)引用釋放掉,以確保在垃圾回收器被觸發(fā)的時(shí)候被回收。
在局部引用的有效期中,可以傳遞到別的本地函數(shù)中,要強(qiáng)調(diào)的是它的有效期仍然只在一次的Java本地函數(shù)調(diào)用中,所以千萬不能用C++全局變量保存它或是把它定義為C++靜態(tài)局部變量。
全局引用可以跨越當(dāng)前線程,在多個(gè)native函數(shù)中有效,不過需要編程人員手動(dòng)來釋放該引用。全局引用存在期間會(huì)防止在Java的垃圾回收的回收。
與局部引用不同,全局引用的創(chuàng)建不是由 JNI 自動(dòng)創(chuàng)建的,全局引用需要調(diào)用 NewGlobalRef 函數(shù),而釋放它需要使用 ReleaseGlobalRef 函數(shù)。
弱全局應(yīng)用是 JDK 1.2 新出來的功能,與全局引用相似,創(chuàng)建跟釋放都需要由編程人員來進(jìn)行操作。這種引用與全局引用一樣可以在多個(gè)本地代碼有效,也可以跨越多線程有效;不一樣的是,這種引用將不會(huì)阻止垃圾回收器回收這個(gè)引用所指向的對(duì)象。
使用 NewWeakGlobalRef 跟 ReleaseWeakGlobalRef 來產(chǎn)生和釋放應(yīng)用。
4. 關(guān)于引用的一些函數(shù)jobject NewGlabalRef(jobject obj);jobject NewLocalRef(jobject obj);jobject NewWeakGlobalRef(jobject obj);void DeleteGlobalRef(jobject obj);void DeleteLocalRef(jobject obj);jboolean IsSameObject(jobject obj1, jobject obj2);
IsSameObject 函數(shù)對(duì)于弱引用全局應(yīng)用還有一個(gè)特別的功能,把NULL傳入要比較的對(duì)象中,就能夠判斷弱全局引用所指向的Java對(duì)象是否被回收。
獲取 jfieldID與jmethodID 的時(shí)候會(huì)通過該屬性/方法名稱加上簽名來查詢相應(yīng)的 jfieldID/jmethodID。這種查詢相對(duì)來說開銷較大。在開發(fā)中可以將這些 FieldID/MethodID 緩存起來,這樣就只需要查詢一次,以后就使用緩存起來的 FieldID/MethodID。
下面介紹兩種緩存方式
在使用時(shí)緩存 (Caching at the Point of Use)
在Java類初始化時(shí)緩存 (Caching at the Defining Class's Inititalizer)
5.1 在使用時(shí)緩存
在native 代碼中使用static局部變量來保存已經(jīng)查詢過的jfieldID/jmethodID ,這樣就不會(huì)在每次的函數(shù)調(diào)用時(shí)查詢,而只要一次查詢成功后就保存起來了。
JNIEXPORT void JNICALL Java_Test_native( JNIEnv* env, jobject ojb) {static jfieldID fieldID_str = NULL;jclass clazz = env->GetObjectClass( obj );???if(fieldID_str?==?NULL){fieldID_str = env->GetFieldID(clazz, "strField", "Ljava/lang/String");}//TODO Other codes}
不過這種情況下,就不得不考慮多線程同時(shí)調(diào)用此函數(shù)時(shí)可能導(dǎo)致同時(shí)查詢的并發(fā)問題,不過這種情況是無害的,因?yàn)椴樵兺粋€(gè)屬性或者方法的ID,通常返回的值是一樣的。
5.2 在Java類初始化時(shí)緩存
更好的一個(gè)方式就是在任何native函數(shù)調(diào)用之前把id全部緩存起來。
可以讓Java在第一次加載這個(gè)類的時(shí)候,首先調(diào)用本地代碼初始化所有的 jfieldID/jmethodID,這樣的話就可以省去多次判斷id是否存在的冗余代碼。當(dāng)然,這些 jfieldID/jmethodID 是定義在C/C++ 的全局。
使用這種方式還有好處,當(dāng)Java類卸載或者重新加載的時(shí)候,也會(huì)重新調(diào)用該本地代碼來重新計(jì)算IDs。
java代碼
public class TestNative {static {initNativeIDs();}static native void initNativeIDs();int propInt =0;String propStr = "";???public?native?void?otherNative();//TODO Other codes}
C/C++ 代碼
6. 總結(jié)//global variablesjfieldID g_propInt_id = 0;jfieldID g_propStr_id = 0;JNIEXPORT void JNICALL Java_TestNative_initNativeIDs( JNIEnv* env, jobject clazz){g_propInt_id = env->GetFieldID(clazz, "propInt", "I");g_propStr_id = env->GetFieldID(clazz, "propStr", "Ljava/lang/String;");}JNIEXPORT void JNICALL Java_TestNative_otherNative( JNIEnv* env, jobject obj){// TODO get field with g_propInt_id/g_propStr_id}
最簡單的Java調(diào)用C/C++函數(shù)的方法
獲取方法/屬性的ID;學(xué)會(huì)了獲取/設(shè)置屬性;還有Java函數(shù)的調(diào)用
Java/C++之間的字符串的轉(zhuǎn)換問題
在C/C++下如何操作Java的數(shù)組
三種引用方式
如何緩存屬性/方法的ID
使用了JNI,那么這個(gè)Java應(yīng)用將不能跨平臺(tái)了。如果要移植到別的平臺(tái)上,那么native代碼就需要重新進(jìn)行編寫
Java是強(qiáng)類型的語言,而C/C++不是。因此,必須在寫JNI時(shí)倍加小心
總之,必須在構(gòu)建Java程序的時(shí)候,盡量不用或者少用本地代碼
異常處理
C/C++ 如何啟動(dòng)JVM
JNI與多線程
《The?Java?Native?Interface?Programmer's?Guide?and?Specification》《JNI++ User Guide》

分享&在看
