Skip to content

JNI 引用管理

源:JNI Local and Global References

JNI 引用管理是 Native 开发中最容易出错的部分。不正确的引用管理会导致内存泄漏、崩溃或悬空指针。本文深入讲解局部引用、全局引用和弱全局引用的使用。

引用类型概览

三种引用类型对比

特性局部引用 (Local)全局引用 (Global)弱全局引用 (Weak Global)
生命周期方法调用期间手动释放前一直有效手动释放前一直有效
线程安全仅当前线程跨线程有效跨线程有效
GC 行为阻止 GC阻止 GC不阻止 GC
自动释放方法返回时自动释放需要手动释放需要手动释放
创建方式JNI 函数自动创建NewGlobalRefNewWeakGlobalRef
释放方式自动或 DeleteLocalRefDeleteGlobalRefDeleteWeakGlobalRef
使用场景临时使用跨调用缓存缓存不强制保留

局部引用 (Local References)

自动创建

JNI 函数返回的对象都是局部引用:

kotlin
@OptIn(ExperimentalForeignApi::class)
@CName("Java_com_example_LocalRefDemo_createString")
fun createString(env: CPointer<JNIEnvVar>, thiz: jobject): jstring? {
    val jniEnv = env.pointed.pointed!!
    
    // NewStringUTF 返回局部引用
    val str = jniEnv.NewStringUTF!!(env, "Hello".cstr.ptr)
    
    // str 是局部引用,函数返回后自动释放
    return str
}

传入的参数也是局部引用:

kotlin
@OptIn(ExperimentalForeignApi::class)
@CName("Java_com_example_LocalRefDemo_process")
fun process(
    env: CPointer<JNIEnvVar>,
    thiz: jobject,  // 局部引用
    input: jstring  // 局部引用
) {
    // thiz 和 input 都是局部引用
    // 函数返回后自动失效
}

手动释放局部引用

在循环中创建大量局部引用时,应该手动释放:

kotlin
@OptIn(ExperimentalForeignApi::class)
@CName("Java_com_example_BadExample_processLargeArray")
fun processLargeArray(
    env: CPointer<JNIEnvVar>,
    thiz: jobject,
    array: jobjectArray
) {
    val jniEnv = env.pointed.pointed!!
    val length = jniEnv.GetArrayLength!!(env, array)
    
    // ❌ 循环中创建大量局部引用,可能耗尽引用表
    for (i in 0 until length) {
        val element = jniEnv.GetObjectArrayElement!!(env, array, i)
        // 处理 element...
        // element 未释放,积累大量局部引用
    }
}
kotlin
@OptIn(ExperimentalForeignApi::class)
@CName("Java_com_example_GoodExample_processLargeArray")
fun processLargeArray(
    env: CPointer<JNIEnvVar>,
    thiz: jobject,
    array: jobjectArray
) {
    val jniEnv = env.pointed.pointed!!
    val length = jniEnv.GetArrayLength!!(env, array)
    
    for (i in 0 until length) {
        val element = jniEnv.GetObjectArrayElement!!(env, array, i)
        
        // 处理 element...
        
        // ✅ 手动释放局部引用
        jniEnv.DeleteLocalRef!!(env, element)
    }
}

局部引用容量

JNI 规范保证至少可以创建 16 个局部引用。超过此数量需要:

kotlin
@OptIn(ExperimentalForeignApi::class)
@CName("Java_com_example_LocalRefCapacity_ensureCapacity")
fun ensureCapacity(env: CPointer<JNIEnvVar>, thiz: jobject, count: jint) {
    val jniEnv = env.pointed.pointed!!
    
    // 确保有足够的局部引用容量
    val result = jniEnv.EnsureLocalCapacity!!(env, count)
    
    if (result != JNI_OK) {
        // 容量分配失败
        println("Failed to ensure local capacity")
        return
    }
    
    // 现在可以安全创建 count 个局部引用
    for (i in 0 until count) {
        val str = jniEnv.NewStringUTF!!(env, "Item $i".cstr.ptr)
        // ...
        jniEnv.DeleteLocalRef!!(env, str)
    }
}

PushLocalFrame / PopLocalFrame

更优雅的局部引用管理方式:

kotlin
@OptIn(ExperimentalForeignApi::class)
@CName("Java_com_example_LocalFrameExample_useFrame")
fun useFrame(env: CPointer<JNIEnvVar>, thiz: jobject): jstring? {
    val jniEnv = env.pointed.pointed!!
    
    // 创建局部引用帧,容量为 10
    if (jniEnv.PushLocalFrame!!(env, 10) < 0) {
        return null  // 内存不足
    }
    
    // 在此帧内创建的所有局部引用都会自动管理
    val str1 = jniEnv.NewStringUTF!!(env, "Hello".cstr.ptr)
    val str2 = jniEnv.NewStringUTF!!(env, "World".cstr.ptr)
    
    // 创建要返回的对象
    val result = jniEnv.NewStringUTF!!(env, "Result".cstr.ptr)
    
    // 弹出帧,释放所有局部引用,但保留 result
    return jniEnv.PopLocalFrame!!(env, result) as jstring?
}

全局引用 (Global References)

创建全局引用

全局引用用于跨方法调用持久化对象:

kotlin
@OptIn(ExperimentalForeignApi::class)
// 存储全局引用的变量
var cachedClass: jclass? = null

@CName("Java_com_example_GlobalRefDemo_cacheClass")
fun cacheClass(env: CPointer<JNIEnvVar>, thiz: jobject, className: jstring) {
    val jniEnv = env.pointed.pointed!!
    
    // 先释放旧的全局引用
    if (cachedClass != null) {
        jniEnv.DeleteGlobalRef!!(env, cachedClass)
    }
    
    // 查找类
    val classNameStr = className.toKString(env)
    val localClassRef = jniEnv.FindClass!!(env, classNameStr.cstr.ptr)
    
    if (localClassRef != null) {
        // ✅ 创建全局引用
        cachedClass = jniEnv.NewGlobalRef!!(env, localClassRef) as jclass?
        
        // 局部引用可以立即删除
        jniEnv.DeleteLocalRef!!(env, localClassRef)
    }
}

@CName("Java_com_example_GlobalRefDemo_useCachedClass")
fun useCachedClass(env: CPointer<JNIEnvVar>, thiz: jobject): jstring? {
    val jniEnv = env.pointed.pointed!!
    
    if (cachedClass == null) {
        return "No cached class".toJString(env)
    }
    
    // ✅ 全局引用可以跨方法使用
    val getNameMethod = jniEnv.GetMethodID!!(
        env,
        jniEnv.FindClass!!(env, "java/lang/Class"),
        "getName",
        "()Ljava/lang/String;"
    )
    
    val className = jniEnv.CallObjectMethod!!(env, cachedClass, getNameMethod) as jstring
    return className
}

@CName("Java_com_example_GlobalRefDemo_releaseCachedClass")
fun releaseCachedClass(env: CPointer<JNIEnvVar>, thiz: jobject) {
    val jniEnv = env.pointed.pointed!!
    
    if (cachedClass != null) {
        // ✅ 必须手动释放全局引用
        jniEnv.DeleteGlobalRef!!(env, cachedClass)
        cachedClass = null
    }
}

全局引用使用场景

场景1:缓存常用类

kotlin
@OptIn(ExperimentalForeignApi::class)
object JNICache {
    var stringClass: jclass? = null
    var integerClass: jclass? = null
    var arrayListClass: jclass? = null
    
    fun initialize(env: CPointer<JNIEnvVar>) {
        val jniEnv = env.pointed.pointed!!
        
        // 缓存常用类的全局引用
        val strLocal = jniEnv.FindClass!!(env, "java/lang/String")
        stringClass = jniEnv.NewGlobalRef!!(env, strLocal) as jclass?
        jniEnv.DeleteLocalRef!!(env, strLocal)
        
        val intLocal = jniEnv.FindClass!!(env, "java/lang/Integer")
        integerClass = jniEnv.NewGlobalRef!!(env, intLocal) as jclass?
        jniEnv.DeleteLocalRef!!(env, intLocal)
        
        val listLocal = jniEnv.FindClass!!(env, "java/util/ArrayList")
        arrayListClass = jniEnv.NewGlobalRef!!(env, listLocal) as jclass?
        jniEnv.DeleteLocalRef!!(env, listLocal)
    }
    
    fun cleanup(env: CPointer<JNIEnvVar>) {
        val jniEnv = env.pointed.pointed!!
        
        stringClass?.let { jniEnv.DeleteGlobalRef!!(env, it) }
        integerClass?.let { jniEnv.DeleteGlobalRef!!(env, it) }
        arrayListClass?.let { jniEnv.DeleteGlobalRef!!(env, it) }
        
        stringClass = null
        integerClass = null
        arrayListClass = null
    }
}

场景2:回调对象存储

kotlin
@OptIn(ExperimentalForeignApi::class)
// 存储 Java 回调对象
var callbackObject: jobject? = null

@CName("Java_com_example_CallbackManager_registerCallback")
fun registerCallback(env: CPointer<JNIEnvVar>, thiz: jobject, callback: jobject) {
    val jniEnv = env.pointed.pointed!!
    
    // 释放旧回调
    if (callbackObject != null) {
        jniEnv.DeleteGlobalRef!!(env, callbackObject)
    }
    
    // 存储新回调的全局引用
    callbackObject = jniEnv.NewGlobalRef!!(env, callback)
}

@CName("Java_com_example_CallbackManager_triggerCallback")
fun triggerCallback(env: CPointer<JNIEnvVar>, thiz: jobject, data: jstring) {
    val jniEnv = env.pointed.pointed!!
    
    if (callbackObject == null) {
        return
    }
    
    // 调用回调方法
    val clazz = jniEnv.GetObjectClass!!(env, callbackObject)
    val methodId = jniEnv.GetMethodID!!(
        env, clazz, "onData", "(Ljava/lang/String;)V"
    )
    
    jniEnv.CallVoidMethod!!(env, callbackObject, methodId, data)
}

弱全局引用 (Weak Global References)

特点与使用

弱全局引用不会阻止 GC 回收对象:

kotlin
@OptIn(ExperimentalForeignApi::class)
// 弱全局引用缓存
var weakCachedObject: jweak? = null

@CName("Java_com_example_WeakRefDemo_setWeakCache")
fun setWeakCache(env: CPointer<JNIEnvVar>, thiz: jobject, obj: jobject) {
    val jniEnv = env.pointed.pointed!!
    
    // 释放旧的弱引用
    if (weakCachedObject != null) {
        jniEnv.DeleteWeakGlobalRef!!(env, weakCachedObject)
    }
    
    // 创建弱全局引用
    weakCachedObject = jniEnv.NewWeakGlobalRef!!(env, obj)
}

@CName("Java_com_example_WeakRefDemo_useWeakCache")
fun useWeakCache(env: CPointer<JNIEnvVar>, thiz: jobject): jstring? {
    val jniEnv = env.pointed.pointed!!
    
    if (weakCachedObject == null) {
        return "No cached object".toJString(env)
    }
    
    // ✅ 检查对象是否已被 GC
    if (jniEnv.IsSameObject!!(env, weakCachedObject, null) == JNI_TRUE) {
        return "Object has been collected".toJString(env)
    }
    
    // ✅ 提升为强引用后使用
    val strongRef = jniEnv.NewLocalRef!!(env, weakCachedObject)
    
    if (strongRef == null) {
        return "Object has been collected".toJString(env)
    }
    
    // 安全使用 strongRef
    val result = "Object is still alive".toJString(env)
    
    jniEnv.DeleteLocalRef!!(env, strongRef)
    return result
}

弱引用的安全模式

kotlin
@OptIn(ExperimentalForeignApi::class)
fun unsafeWeakRefUse(env: CPointer<JNIEnvVar>, weak: jweak) {
    val jniEnv = env.pointed.pointed!!
    
    // ❌ 即使检查了 null,对象也可能在下一行被 GC
    if (jniEnv.IsSameObject!!(env, weak, null) != JNI_TRUE) {
        // 危险!对象可能在此处被 GC
        val clazz = jniEnv.GetObjectClass!!(env, weak)  // 可能崩溃
    }
}
kotlin
@OptIn(ExperimentalForeignApi::class)
fun safeWeakRefUse(env: CPointer<JNIEnvVar>, weak: jweak): jstring? {
    val jniEnv = env.pointed.pointed!!
    
    // ✅ 先提升为强引用
    val strong = jniEnv.NewLocalRef!!(env, weak)
    
    if (strong == null) {
        return "Object collected".toJString(env)
    }
    
    try {
        // 安全使用 strong 引用
        val clazz = jniEnv.GetObjectClass!!(env, strong)
        // ... 其他操作 ...
        return "Success".toJString(env)
    } finally {
        jniEnv.DeleteLocalRef!!(env, strong)
    }
}

引用比较

IsSameObject 用法

kotlin
@OptIn(ExperimentalForeignApi::class)
@CName("Java_com_example_RefComparison_compareRefs")
fun compareRefs(
    env: CPointer<JNIEnvVar>,
    thiz: jobject,
    obj1: jobject,
    obj2: jobject
): jboolean {
    val jniEnv = env.pointed.pointed!!
    
    // 比较两个引用是否指向同一对象
    val isSame = jniEnv.IsSameObject!!(env, obj1, obj2)
    
    return isSame
}

@CName("Java_com_example_RefComparison_isNull")
fun isNull(env: CPointer<JNIEnvVar>, thiz: jobject, obj: jobject?): jboolean {
    if (obj == null) return JNI_TRUE
    
    val jniEnv = env.pointed.pointed!!
    
    // 检查是否为 null
    return jniEnv.IsSameObject!!(env, obj, null)
}

最佳实践

实践1:始终释放全局引用

kotlin
@OptIn(ExperimentalForeignApi::class)
class JNIResource {
    private var globalRef: jobject? = null
    
    fun acquire(env: CPointer<JNIEnvVar>, obj: jobject) {
        val jniEnv = env.pointed.pointed!!
        release(env)  // 先释放旧的
        globalRef = jniEnv.NewGlobalRef!!(env, obj)
    }
    
    fun release(env: CPointer<JNIEnvVar>) {
        val jniEnv = env.pointed.pointed!!
        globalRef?.let {
            jniEnv.DeleteGlobalRef!!(env, it)
            globalRef = null
        }
    }
}

实践2:使用 RAII 模式管理引用

kotlin
@OptIn(ExperimentalForeignApi::class)
class ScopedLocalRef(
    private val env: CPointer<JNIEnvVar>,
    val ref: jobject?
) {
    fun use() {
        // 使用 ref...
    }
    
    fun release() {
        if (ref != null) {
            env.pointed.pointed!!.DeleteLocalRef!!(env, ref)
        }
    }
}

inline fun <T> withLocalRef(
    env: CPointer<JNIEnvVar>,
    ref: jobject?,
    block: (jobject?) -> T
): T {
    try {
        return block(ref)
    } finally {
        if (ref != null) {
            env.pointed.pointed!!.DeleteLocalRef!!(env, ref)
        }
    }
}

// 使用示例
@CName("Java_com_example_Example_process")
fun process(env: CPointer<JNIEnvVar>, thiz: jobject, array: jobjectArray) {
    val jniEnv = env.pointed.pointed!!
    val length = jniEnv.GetArrayLength!!(env, array)
    
    for (i in 0 until length) {
        val element = jniEnv.GetObjectArrayElement!!(env, array, i)
        
        withLocalRef(env, element) { ref ->
            // 使用 ref
            // 自动释放
        }
    }
}

实践3:对称性原则

kotlin
@OptIn(ExperimentalForeignApi::class)
// ✅ 好的模式:谁创建谁释放
@CName("Java_com_example_SymmetricExample_init")
fun init(env: CPointer<JNIEnvVar>, thiz: jobject): jobject? {
    val jniEnv = env.pointed.pointed!!
    
    val obj = jniEnv.NewObject!!(env, someClass, someMethod)
    val globalRef = jniEnv.NewGlobalRef!!(env, obj)
    
    jniEnv.DeleteLocalRef!!(env, obj)
    
    return globalRef  // 调用者负责释放
}

@CName("Java_com_example_SymmetricExample_cleanup")
fun cleanup(env: CPointer<JNIEnvVar>, thiz: jobject, ref: jobject) {
    val jniEnv = env.pointed.pointed!!
    jniEnv.DeleteGlobalRef!!(env, ref)  // 与 init 对称
}

实践4:避免引用泄漏检查清单

  • ✅ 每个 NewGlobalRef 都有对应的 DeleteGlobalRef
  • ✅ 循环中手动释放局部引用或使用 PushLocalFrame
  • ✅ 弱引用使用前先提升为强引用
  • ✅ 类缓存使用全局引用
  • ✅ 回调对象使用全局引用
  • ✅ 临时对象使用局部引用

常见错误

错误1:跨方法使用局部引用

kotlin
@OptIn(ExperimentalForeignApi::class)
// ❌ 错误
var savedLocalRef: jobject? = null

@CName("Java_com_example_BadExample_save")
fun save(env: CPointer<JNIEnvVar>, thiz: jobject, obj: jobject) {
    savedLocalRef = obj  // 局部引用,方法返回后失效
}

@CName("Java_com_example_BadExample_use")
fun use(env: CPointer<JNIEnvVar>, thiz: jobject) {
    // ❌ savedLocalRef 已经失效,使用会崩溃
    val clazz = env.pointed.pointed!!.GetObjectClass!!(env, savedLocalRef)
}

// ✅ 正确:使用全局引用
var savedGlobalRef: jobject? = null

@CName("Java_com_example_GoodExample_save")
fun save(env: CPointer<JNIEnvVar>, thiz: jobject, obj: jobject) {
    val jniEnv = env.pointed.pointed!!
    savedGlobalRef = jniEnv.NewGlobalRef!!(env, obj)
}

错误2:忘记释放全局引用

kotlin
@OptIn(ExperimentalForeignApi::class)
// ❌ 错误:内存泄漏
@CName("Java_com_example_LeakExample_create")
fun create(env: CPointer<JNIEnvVar>, thiz: jobject): jobject? {
    val jniEnv = env.pointed.pointed!!
    val obj = jniEnv.NewObject!!(env, someClass, someMethod)
    return jniEnv.NewGlobalRef!!(env, obj)
    // 全局引用泄漏,永不释放
}

错误3:直接使用弱引用

kotlin
@OptIn(ExperimentalForeignApi::class)
// ❌ 错误
fun directWeakRefUse(env: CPointer<JNIEnvVar>, weak: jweak) {
    val jniEnv = env.pointed.pointed!!
    // 直接使用弱引用,对象可能随时被 GC
    val clazz = jniEnv.GetObjectClass!!(env, weak)  // 危险!
}

正确的引用管理是 JNI 编程的基石。理解并遵循局部引用、全局引用和弱全局引用的使用规则,能够避免大部分内存泄漏和崩溃问题。