1. 程式人生 > >傳智播客JNI第七講 – JNI中的全域性引用/區域性引用/弱全域性引用、快取jfieldID和jmethodID的兩種方式

傳智播客JNI第七講 – JNI中的全域性引用/區域性引用/弱全域性引用、快取jfieldID和jmethodID的兩種方式

 講解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