参考文献:
JNI学习笔记——局部和全局引用
JNI将Java层的类实例、数组类型暴露为不透明的引用,native层的代码会通过JNI提供的函数来访问这个不透明的引用。JNI中有着不同种类的引用,分别为局部引用、全局引用和弱全局应用。
JNI对Java层实例的局部引用能够被自动释放,全局引用和弱全局引用在程序员主动释放之前都是有效的。JNI的局部引用和全局引用能够使Java中对应的实例不会被JVM的垃圾回收机制所回收,但是弱全局引用允许其对应的实例被回收。
局部引用JNI层要获取Java实例的引用,就是获取Java实例对应的jclass类,获取到jclass的方法有多种方式。
JNI中除了Java基本类型的数组、String、Class和Throwable之外,其余的Java对象在JNI中都用jobject表示。所以jclass类的一种方式就是从jobject获取,如下所示:
extern "C"
JNIEXPORT jboolean JNICALL
Java_android_set(
JNIEnv *env, jobject thiz, jobject ref, jstring module_name) {
...
jclass clazz = env->GetObjectClass(thiz);
...
}
thiz对应Java中的一个实例对象,在JNI层用jobject表示,通过env的GetObjectClass方法将jobject转换为jclass。
或者通过Java中的完整包名加类名来获取jclass,如下所示:
extern "C"
JNIEXPORT jobject JNICALL
Java_....getList(JNIEnv *env, jobject instance) {
....
jclass list_jclass = env->FindClass("java/util/List");
....
}
以上两种方式获取到的Java层类的引用list_jclass 和clazz都是局部引用,当程序离开这个程序块的时候,list_jclass 和clazz就不能再被使用了,因为他们已经被JVM释放掉了。
全局引用当JNI中同一个进程的多个线程需要对Java中的同一个类型的类进行重复操作时,就需要将该局部引用升级为全局引用,如下所示:
jstring
MyNewString(JNIEnv *env, jchar *chars, jint len)
{
static jclass stringClass = NULL;
...
if (stringClass == NULL) {
jclass localRefCls =
(*env)->FindClass(env, "java/lang/String");
if (localRefCls == NULL) {
return NULL;
}
stringClass = (*env)->NewGlobalRef(env, localRefCls);
(*env)->DeleteLocalRef(env, localRefCls);
...
}
...
}
以上表示使用NewGlobalRef方法将一个局部引用localRefCls转换为一个全局引用stringClass的方法,这个时候stringClass 这个引用即使在代码退出这个代码块之后也不会被释放,可以使用env的DeleteGlobalRef 方法来释放这个全局引用。
需要注意的是这里使用env的DeleteLocalRef方法来主动释放一个JNI的局部引用,DeleteLocalRef方法需要使用的场景如下有:
1、创建大量 JNI 局部引用(在循环体或回调函数中),即使它们并不会被同时使用,因为 JVM 需要足够的空间去跟踪所有的 JNI 引用,所以可能会造成内存溢出或者栈溢出。
2、如果对一个大的 Java 对象创建了 JNI 局部引用,也必须在使用完后手动释放该引用,否则 GC 迟迟无法回收该 Java 对象也会引发内存泄漏。
弱全局引用
JNIEXPORT void JNICALL
Java_mypkg_MyCls_f(JNIEnv *env, jobject self)
{
static jclass myCls2 = NULL;
if (myCls2 == NULL) {
jclass myCls2Local =
(*env)->FindClass(env, "mypkg/MyCls2");
if (myCls2Local == NULL) {
return;
}
myCls2 = NewWeakGlobalRef(env, myCls2Local);
if (myCls2 == NULL) {
return;
}
}
...
}
以上表示使用NewWeakGlobalRef将一个局部引用升级为弱全局引用的方法。弱全局引用和全局引用一样,可以跨越native的方法,以及跨线程使用。和全局引用不同的地方在于,弱全局引用不会阻止对基础对象的垃圾回收,所以使用弱全局引用之前需要检查该弱全局引用是否有效。使用DeleteWeakGlobalRef释放一个弱全局引用。



