💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
大多数JNI函数创建局部引用,例如,NewObject函数创建一个新的对象实例并返回指向这个实例的一个局部引用。 一个局部引用仅在创建它的Native方法以及调用这个Native方法的上下文中才是有效的。在函数执行过程中创建的所有的局部引用都会在函数返回之后被释放。 请不要使用静态变量缓存一个局部引用留作以后使用,如下示例,是一个局部引用使用的错误示例: ~~~ /* This code is illegal */ jstring MyNewString(JNIEnv *env, jchar *chars, jint len) { static jclass stringClass; jmethodID cid; jcharArray elemArr; jstring result; if(!stringClass) { stringClass = (*env)->FindClass(env, "java/lang/String"); if(!stringClass){ return 0; /* exception thrown */ } } /* It is wrong to use the cached stringClass here' * because it may be invalid. */ cid = (*env)->GetMethodID(env, stringClass, "<init>", "([C)V"); if(!cid) { return 0; /* exception thrown */ } /* Create a char[] that holds the string characters */ elemArr = (*env)->NewCharArray(env, len); if(!elemArr) { return 0; /* exception thrown */ } (*env)->SetCharArrayRegion(env, elemArr, 0, len, chars); /* Construct a java.lang.String */ result = (*env)->NewObject(env, stringClass, cid, elemArr); /* Free local references */ (*env)->DeleteLocalRef(env, elemArr); return result; } ~~~ 如上例,代码中以静态变量的形式缓存了stringClass,并希望借此减少多次执行FindClass带来的开销。 不幸的是,这种做法是错误的,因为FindClass返回了指向一个java.lang.String类的对象的局部引用。为了说明为什么这么做存在问题,假设上边的MyNewString被另一个函数C.f调用: ~~~ JNIEXPORT jstring JNICALL Java_C_f(JNIEnv* env, jobject this){ char *c_str = ...; ... return MyNewString(c_str); } ~~~ 在C.f返回之后,虚拟机释放了Java_C_f执行过程中创建的所有局部引用。这些被释放的局部引用包括函数MyNewString中用静态变量缓存的stringClass变量。当下一次MyNewString被调用的时候,就会触发访问一个非法的局部引用,并进一步导致内存异常或者系统崩溃。如下例子,连续两次调用C.f函数就会让MyNewString触发非法局部引用访问。 有两种方式让一个局部引用失效。第一种是上边介绍过的,虚拟机会在一个Native函数返回之后释放所有在该函数执行期间创建的局部引用,另一种方式,开发者可以显式使用DeleteLocalRef这样的JNI函数手动管理局部引用的声明周期。 你一定会提出一个问题,既然虚拟机会自动释放局部引用,那么什么时候需要开发者手动释放局部引用呢?我们知道,在局部引用无效之前,它都会阻止所引用的真实资源被释放。 局部引用仅仅在创建它的线程中是有效的。在线程A中被创建的局部引用是不能够在线程B中被使用的。千万不要将一个局部引用保存在全局变量中给其他线程使用!