講解JNI中的全局引用/局部引用/弱全局引用、緩存jfieldID和jmethodID的兩種方式,并編寫兩種緩存方式的示例代碼。
1.從Java虛擬機創建的對象傳到本地C/C++代碼時會產生引用,根據Java的垃圾回收機制,只要有引用存在就不會出發該引用指向的Java對象的垃圾回收。
2.這些引用在JNI中分為三種:
全局引用:Global Reference
局部引用:Local Reference
若全局引用:Weak Global Reference since JDK1.2
3.局部引用
1)最常見的引用類型,基本上通過JNI返回來的引用都是局部引用。例如使用NewObject就會返回創建出來的實例的局部引用,局部引用只在該native函數中有效,所有在該函數中產生的局部引用,都會在函數返回的時候自動釋放,也可以使用DeleteLocalRef函數手動釋放該引用。
2)想一想既然局部引用能夠在函數返回時自動釋放,為什么還需要DeleteLocalRef函數呢?
3)實際上,局部引用存在,就會防止其指向的對象被垃圾回收,尤其是當一個局部引用指向一個很龐大的對象,或是在一個循環中生成了局部引用,最好的做法就是在使用完該對象后,或在循環尾部把這個引用釋放掉,以確保在垃圾回收器被處罰的時候被回收。
4)在局部引用的有效期中,可以傳遞到別的本地函數中,要強調的是他的有效期仍然只在一次的Java本地函數調用中,所以千萬不能用C++全局變量保存它或者把它定義為C++靜態局部變量。
4.全局引用
1)全局引用可以跨越當前線程,在多個native函數中有效,不過需要編程人員手動來釋放該引用,全局引用存在期間會防止在Java的垃圾回收。
2)與局部引用不同,全局引用的創建不是由JNI自動創建的,全局引用時需要調用NewGlobalRef函數,而釋放它需要使用ReleaseGlobalRef函數。
5.弱全局引用
1)Java1.2新出來的功能,與全局引用相似,創建跟刪除都需要由編程人員來進行。這種引用與全局引用一樣可以再多個本地代碼有效,也跨越多線程有效,不一樣的是,這種引用將不會阻止垃圾回收器回收這個引用所指向的對象。
2)使用NewWeakGlobalRef跟ReleaseWeakGlobalRef來產生和解除引用。
6.關于引用的一些函數
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對象是否被回收。
7.緩存jfieldID,jmethodID
1)取得jieldID跟jmethodID的時候會通過該屬性、方法名稱加上簽名來查詢相應的jfieldID,jmethodID。這種查詢相對來說開銷較大,我們可以將這些FieldID,MethodID緩存起來,這樣只需要查詢一次,以后就使用緩存起來的FieldID,MethodID。
2)介紹兩種緩存方式
1.在用的時候緩存
2.在Java類初始化時緩存
11)在第一次使用的時候緩存
在native code中使用static局部變量來保存已經查詢過的id,這樣就不會再每次的函數調用時查詢,而只要第一次查詢成功后就保存起來了。
不過在這種情況下就不得不考慮多線程同時呼叫此函數時可能會招致同時查詢的危機,不過這種情況是無害的,因為查詢同一個屬性,方法的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(clazz, "string", "Ljava/lang/String;");
}
// other code...
}
22)在Java類初始化的時候緩存
更好的一個方式就是在任何native函數調用前把id全部存起來。
我們可以讓Java在第一次加載這個類的時候首先調用本地代碼初始化所有的jfieldID,jmethodID,這樣的話,就可以省去多次的確定id是否存在的語句,當然,這些jfieldID,jmethodID是定義在C/C++的全局。
使用這種方式的好處,當Java類卸載或是重新加載的時候,也會重新呼叫該本地代碼來重新計算IDs。
課程最后總結
在這一課中,我們學習了:
1.最簡單的Java調用C/C++函數的方法
2.取得方法、屬性的ID,學會了取得/設置屬性,還有Java函數的調用。
3.Java/c++之間的字符串的轉換問題。
4.在C/C++下如何操作Java數組。
5.三種引用方式
6.如何緩存屬性和方法的ID
使用JNI的兩個弊端
1.使用了JNI,那么這個應用就不能跨平臺了,如果需要移植到別的平臺上,那么native代碼就需要重新編寫。
2.Java是強類型的語言,而C/C++不是,你必須寫JNI時更小心。
3.盡量少使用本地代碼。
其它
1.異常處理
2.C/C++如何啟動JVM
3.JNI跟多線程
介紹兩本書作為參考:
1)The Java Native Interface Programmer's Guide and Specification
2))JNI++ User Guide