從Java虛擬機(jī)創(chuàng)建的對象傳到本地 C/C++ 代碼時就會產(chǎn)生引用擎鸠。根據(jù)Java的垃圾回收機(jī)制,只要有引用存在就不會觸發(fā)該引用指向的Java對象的垃圾回收掀泳。這些引用在 JNI 中分為三種
- 全局引用 (Global Reference)
- 局部引用 (Local Reference)
- 弱全局引用 (Weak Global Reference)土浸, JDK 1.2 引入
1. 局部引用
- 最常見的引用類型,基本上通過JNI返回來的引用都是局部引用
例如啤它,使用NewObject就會返回創(chuàng)建出來的實(shí)例的局部引用。局部引用只在該native函數(shù)中有效舱痘,所有在該函數(shù)中產(chǎn)生的局部引用变骡,都會在函數(shù)返回的時候自動釋放(freed)。也可以使用DeleteLocalRef函數(shù)進(jìn)行手動釋放該引用芭逝。
- 想一想既然局部引用能夠在函數(shù)返回時自動釋放塌碌,為什么還需要DeleteLocalRef函數(shù)呢?
實(shí)際上局部引用存在旬盯,就會防止其指向的對象被垃圾回收台妆。尤其是當(dāng)一個局部引用指向一個很龐大的對象,或是在一個循環(huán)中生成了局部應(yīng)用胖翰;最好的做法就是在使用完該對象后接剩,或在該循環(huán)尾部把這個引用釋放掉,以確保在垃圾回收器被觸發(fā)的時候被回收萨咳。
- 在局部引用的有效期中懊缺,可以傳遞到別的本地函數(shù)中,要強(qiáng)調(diào)的是它的有效期仍然只在一次的Java本地函數(shù)調(diào)用中培他,所以千萬不能用C++全局變量保存它或是把它定義為C++靜態(tài)局部變量鹃两。
2. 全局引用
- 全局引用可以跨越當(dāng)前線程遗座,在多個native函數(shù)中有效,不過需要編程人員手動來釋放該引用俊扳。全局引用存在期間會防止在Java的垃圾回收的回收员萍。
- 與局部引用不同,全局引用的創(chuàng)建不是由 JNI 自動創(chuàng)建的拣度,全局引用需要調(diào)用 NewGlobalRef 函數(shù),而釋放它需要使用 ReleaseGlobalRef 函數(shù)螃壤。
3. 弱全局引用
弱全局應(yīng)用是 JDK 1.2 新出來的功能抗果,與全局引用相似,創(chuàng)建跟釋放都需要由編程人員來進(jìn)行操作奸晴。這種引用與全局引用一樣可以在多個本地代碼有效冤馏,也可以跨越多線程有效;不一樣的是寄啼,這種引用將不會阻止垃圾回收器回收這個引用所指向的對象逮光。
使用 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ù)對于弱引用全局應(yīng)用還有一個特別的功能墩划,把NULL傳入要比較的對象中涕刚,就能夠判斷弱全局引用所指向的Java對象是否被回收。
5. 緩存jfieldID / jmethodID
獲取 jfieldID與jmethodID 的時候會通過該屬性/方法名稱加上簽名來查詢相應(yīng)的 jfieldID/jmethodID乙帮。這種查詢相對來說開銷較大杜漠。在開發(fā)中可以將這些 FieldID/MethodID 緩存起來,這樣就只需要查詢一次察净,以后就使用緩存起來的 FieldID/MethodID驾茴。
- 下面介紹兩種緩存方式
- 在使用時緩存 (Caching at the Point of Use)
- 在Java類初始化時緩存 (Caching at the Defining Class's Inititalizer)
5.1 在使用時緩存
在native 代碼中使用static局部變量來保存已經(jīng)查詢過的jfieldID/jmethodID ,這樣就不會在每次的函數(shù)調(diào)用時查詢氢卡,而只要一次查詢成功后就保存起來了锈至。
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
}
不過這種情況下,就不得不考慮多線程同時調(diào)用此函數(shù)時可能導(dǎo)致同時查詢的并發(fā)問題译秦,不過這種情況是無害的峡捡,因?yàn)椴樵兺粋€屬性或者方法的ID,通常返回的值是一樣的诀浪。
5.2 在Java類初始化時緩存
更好的一個方式就是在任何native函數(shù)調(diào)用之前把id全部緩存起來棋返。
可以讓Java在第一次加載這個類的時候,首先調(diào)用本地代碼初始化所有的 jfieldID/jmethodID雷猪,這樣的話就可以省去多次判斷id是否存在的冗余代碼睛竣。當(dāng)然,這些 jfieldID/jmethodID 是定義在C/C++ 的全局求摇。
使用這種方式還有好處射沟,當(dāng)Java類卸載或者重新加載的時候殊者,也會重新調(diào)用該本地代碼來重新計算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++ 代碼
//global variables
jfieldID 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
}
6. 總結(jié)
- 最簡單的Java調(diào)用C/C++函數(shù)的方法
- 獲取方法/屬性的ID验夯;學(xué)會了獲取/設(shè)置屬性猖吴;還有Java函數(shù)的調(diào)用
- Java/C++之間的字符串的轉(zhuǎn)換問題
- 在C/C++下如何操作Java的數(shù)組
- 三種引用方式
- 如何緩存屬性/方法的ID
7. 回顧
- 使用了JNI,那么這個Java應(yīng)用將不能跨平臺了挥转。如果要移植到別的平臺上海蔽,那么native代碼就需要重新進(jìn)行編寫
- Java是強(qiáng)類型的語言,而C/C++不是绑谣。因此党窜,必須在寫JNI時倍加小心
- 總之,必須在構(gòu)建Java程序的時候借宵,盡量不用或者少用本地代碼
附
- 異常處理
- C/C++ 如何啟動JVM
- JNI與多線程
《The Java Native Interface Programmer's Guide and Specification》
《JNI++ User Guide》