JNI 引用表——LocalRef / GlobalRef / WeakGlobalRef

JNI 引用到底是什么?

在 JNI 里你会看到这些类型:

cpp 复制代码
jobject
jclass
jstring
jarray
jbyteArray
jthrowable

它们看起来像 C++ 指针,但你不能把它们当普通指针理解。

更准确地说:它们是 Java 对象在 native 层的引用句柄。
native 代码不能直接操作 Java 对象内存,只能通过 JNIEnv 提供的接口间接访问。

比如

cpp 复制代码
jstring name = env->NewStringUTF("hello");
const char* chars = env->GetStringUTFChars(name, nullptr);

jstring name只是一个对java中string类型的引用,要转换成c++的string得GetStringUTFChars

Local Reference:本地引用

LocalRef 是最常见的 JNI 引用。可以说你在native层看到的东西大部分都是LocalRef

例如

cpp 复制代码
extern "C"
JNIEXPORT void JNICALL
Java_com_xxx_NativeBridge_nativeFoo(JNIEnv* env, jobject thiz) {
    jstring str = env->NewStringUTF("hello");
    jclass clazz = env->FindClass("com/xxx/Foo");
	jstring str = env->NewStringUTF("hello");
	jobject obj = env->CallObjectMethod(thiz, methodId);
}

这里面的str、clazz、str、obj甚至thiz、env都是LocalRef

它的生命周期是:当前 native 方法调用期间有效。native 方法返回后,LocalRef 通常会被 JVM 自动释放。

LocalRef 的两个典型错误

错误 1:把 LocalRef 保存到全局变量

cpp 复制代码
static jobject gCallback = nullptr;

extern "C"
JNIEXPORT void JNICALL
Java_com_xxx_NativeBridge_nativeInit(JNIEnv* env, jobject thiz, jobject callback) {
    gCallback = callback; // 错
}

callback 是 LocalRef。

nativeInit 返回后,这个引用可能失效。

后面再用 gCallback 回调 Java,可能触发 JNI DETECTED ERROR,甚至 SIGABRT。

正确做法是

cpp 复制代码
gCallback = env->NewGlobalRef(callback);

错误 2:循环里创建大量 LocalRef 不释放

cpp 复制代码
for (int i = 0; i < 100000; i++) {
    jstring str = env->NewStringUTF("hello");
    // 没有 DeleteLocalRef
}

虽然 LocalRef 会在 native 方法返回时统一释放,但如果一个 native 方法内部循环创建太多 LocalRef,可能在返回前就把 local reference table 撑爆。

最好是env->DeleteLocalRef(str);手动释放一下

Global Reference:全局引用

如果 native 层要长期保存 Java 对象,就必须用 GlobalRef。

上已经展示过写法了

GlobalRef 的特点:

  1. 可以跨 native 方法调用保存。
  2. 可以跨线程使用。
  3. 会阻止 Java 对象被 GC。
  4. 必须手动 DeleteGlobalRef。

重点是第 3 点。如果你一直不释放,那么这个 Java callback 对象就一直被 native 持有,GC 不会回收它。如果 callback 持有 Activity,就可能造成 Activity 泄漏。

Weak Global Reference:弱全局引用

WeakGlobalRef 是弱引用版本的 GlobalRef。

创建方式:

cpp 复制代码
jweak gWeakCallback = env->NewWeakGlobalRef(callback);

它的特点是:

  1. 可以跨调用、跨线程保存。
  2. 不阻止 Java 对象被 GC。
  3. 使用前必须判断对象是否还活着。