1 前言
1.1 什么是引用
在使用JNI技术进行Java和Native代码的混编的时候,不可避免的要大量使用JNI提供的一些实例和数组类型(jobject、jclass、jstring、jarray等),而对这些类型的操作,往往不是通过该类类型变量本身进行的,而是通过使用JNI操作一个不透明的引用来间接的操作数据内容。因为只操作引用而不牵涉类型具体本身的内容,所以不必担心依赖于特定Java虚拟机实现的内部对象分布。这一机制无疑提高了程序运行效率,也增加了我们的学习内容。引用的产生随处可见,常见的,如FindClass
和 NewObject
等函数都会自动产生一个指向JVM内部数据的引用。
1.2 为什么要使用引用
引用这个机制在JNI编程中广泛出现,我们必须了解其工作原理和使用规范,才能够更好的去使用这一机制,进而保证我们的代码更加健壮、高效。尤其是在频繁的JNI交互的缓存技术中,使用好引用机制,有利于减少不必要的引用累积、不安全的引用访问以及更好的内存管理,保障程序不会在JNI内部崩溃。
本文主要参考了《JNI编程规范》一书中第五章节的内容进行整理和学习。
2. 全局引用和局部引用
JNI支持三种不同的引用:局部引用(local references)、全局引用(global references)和弱全局引用(weak global references)
- 局部引用和全局引用的生命周期不同,当本地方法返回时,局部引用会被自动释放,而全局引用和弱全局引用则需要手动释放。
- 局部引用和全局引用会阻止GC(Garbage collection)回收它们所引用的对象(注意是引用的对象,不是引用本身),但是弱引用不会。
2.1 局部引用
大多数JNI函数会创建局部引用,例如,NewObject
函数创建一个对象实例并返回这个对象的局部引用。局部引用只有在创建它的本地方法返回之前有效,本地方法返回之后,局部引用会自动释放(这一点类似于局部变量的生命周期)。因此,不能在局部方法中把局部引用存储为静态变量缓存起来以供下次使用。
下面是一个局部引用的错误使用例子:
/* 以下代码存在错误 */
jstring
MyNewString(JNIEnv *env, jchar *chars, jint len)
{
static jclass stringClass = NULL;
jmethodID cid;
jcharArray elemArr;
jstring result;
if (stringClass == NULL) {
stringClass = (*env)->FindClass(env,"java/lang/String");
if (stringClass == NULL) {
return NULL; /* 抛出异常 */
}
}
/* 此处使用缓存的变量stringClass可能错误,
因为局部引用已被释放,此时的stringClass内容非法*/
cid = (*env)->GetMethodID(env, stringClass,"<init>", "([C)V");
...
elemArr = (*env)->NewCharArray(env, len);
...
result = (*env)->NewObject(env, stringClass, cid, elemArr);
(*env)->DeleteLocalRef(env,