从 Java 虚拟机创立的对象传到本地 C/C++ 代码时就会产生援用。依据 Java 的垃圾回收机制,只有有援用存在就不会触发该援用指向的 Java 对象的垃圾回收。这些援用在 JNI 中分为三种
- 全局援用(Global Reference)
- 部分援用(Local Reference)
- 弱全局援用(Weak Global Reference),JDK 1.2 引入
1. 部分援用
- 最常见的援用类型,基本上通过 JNI 返回来的援用都是部分援用
例如,应用 NewObject 就会返回创立进去的实例的部分援用。部分援用只在该 native 函数中无效,所有在该函数中产生的部分援用,都会在函数返回的时候主动开释(freed)。也能够应用 DeleteLocalRef 函数进行手动开释该援用。
- 想一想既然部分援用可能在函数返回时主动开释,为什么还须要 DeleteLocalRef 函数呢?
实际上部分援用存在,就会避免其指向的对象被垃圾回收。尤其是当一个部分援用指向一个很宏大的对象,或是在一个循环中生成了部分利用;最好的做法就是在应用完该对象后,或在该循环尾部把这个援用开释掉,以确保在垃圾回收器被触发的时候被回收。
- 在部分援用的有效期中,能够传递到别的本地函数中,要强调的是它的有效期依然只在一次的 Java 本地函数调用中,所以千万不能用 C ++ 全局变量保留它或是把它定义为 C ++ 动态局部变量。
2. 全局援用
- 全局援用能够逾越以后线程,在多个 native 函数中无效,不过须要编程人员手动来开释该援用。全局援用存在期间会避免在 Java 的垃圾回收的回收。
- 与部分援用不同,全局援用的创立不是由 JNI 主动创立的,全局援用须要调用 NewGlobalRef 函数,而开释它须要应用 ReleaseGlobalRef 函数。
3. 弱全局援用
弱全局利用是 JDK 1.2 新进去的性能,与全局援用类似,创立跟开释都须要由编程人员来进行操作。这种援用与全局援用一样能够在多个本地代码无效,也能够逾越多线程无效;不一样的是,这种援用将不会阻止垃圾回收器回收这个援用所指向的对象。
应用 NewWeakGlobalRef 跟 ReleaseWeakGlobalRef 来产生和开释利用。
4. 对于援用的一些函数
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 函数对于弱援用全局利用还有一个特地的性能,把 NULL 传入要比拟的对象中,就可能判断弱全局援用所指向的 Java 对象是否被回收。
5. 缓存 jfieldID / jmethodID
获取 jfieldID 与 jmethodID 的时候会通过该属性 / 办法名称加上签名来查问相应的 jfieldID/jmethodID。这种查问相对来说开销较大。在开发中能够将这些 FieldID/MethodID 缓存起来,这样就只须要查问一次,当前就应用缓存起来的 FieldID/MethodID。
- 上面介绍两种缓存形式
- 在应用时缓存(Caching at the Point of Use)
- 在 Java 类初始化时缓存(Caching at the Defining Class’s Inititalizer)
5.1 在应用时缓存
在 native 代码中应用 static 局部变量来保留曾经查问过的 jfieldID/jmethodID,这样就不会在每次的函数调用时查问,而只有一次查问胜利后就保存起来了。
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
}
不过这种状况下,就不得不思考多线程同时调用此函数时可能导致同时查问的并发问题,不过这种状况是有害的,因为查问同一个属性或者办法的 ID,通常返回的值是一样的。
5.2 在 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();
//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. 总结
- 最简略的 Java 调用 C /C++ 函数的办法
- 获取办法 / 属性的 ID;学会了获取 / 设置属性;还有 Java 函数的调用
- Java/C++ 之间的字符串的转换问题
- 在 C /C++ 下如何操作 Java 的数组
- 三种援用形式
- 如何缓存属性 / 办法的 ID
7. 回顾
- 应用了 JNI,那么这个 Java 利用将不能跨平台了。如果要移植到别的平台上,那么 native 代码就须要从新进行编写
- Java 是强类型的语言,而 C /C++ 不是。因而,必须在写 JNI 时倍加小心
- 总之,必须在构建 Java 程序的时候,尽量不必或者少用本地代码
附
- 异样解决
- C/C++ 如何启动 JVM
- JNI 与多线程
《The Java Native Interface Programmer’s Guide and Specification》
《JNI++ User Guide》