JNI 引用管理
源:JNI Local and Global References
JNI 引用管理是 Native 开发中最容易出错的部分。不正确的引用管理会导致内存泄漏、崩溃或悬空指针。本文深入讲解局部引用、全局引用和弱全局引用的使用。
引用类型概览
三种引用类型对比
| 特性 | 局部引用 (Local) | 全局引用 (Global) | 弱全局引用 (Weak Global) |
|---|---|---|---|
| 生命周期 | 方法调用期间 | 手动释放前一直有效 | 手动释放前一直有效 |
| 线程安全 | 仅当前线程 | 跨线程有效 | 跨线程有效 |
| GC 行为 | 阻止 GC | 阻止 GC | 不阻止 GC |
| 自动释放 | 方法返回时自动释放 | 需要手动释放 | 需要手动释放 |
| 创建方式 | JNI 函数自动创建 | NewGlobalRef | NewWeakGlobalRef |
| 释放方式 | 自动或 DeleteLocalRef | DeleteGlobalRef | DeleteWeakGlobalRef |
| 使用场景 | 临时使用 | 跨调用缓存 | 缓存不强制保留 |
局部引用 (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 编程的基石。理解并遵循局部引用、全局引用和弱全局引用的使用规则,能够避免大部分内存泄漏和崩溃问题。