JNI内存泄露JNI ERROR (app bug): local reference table overflow (max=512)

本文解析了JNI在大量数据传输时出现的本地引用表溢出问题,详细介绍了LocalReference的作用机制及其可能导致的问题,并提供了有效的解决方案。

现象:当使用JNI传输很多数据时会出现JNI ERROR (app bug): local reference table overflow (max=512)的崩溃异常
原因:
当线程从 Java 环境切换到 native code 上下文时(J2N),JVM 会分配一块内存,创建一个 Local Reference 表,这个表用来存放本次 native method 执行中创建的所有的 Local Reference。每当在 native code 中引用到一个 Java 对象时,JVM 就会在这个表中创建一个 Local Reference。比如我们调用 NewStringUTF() 在 Java Heap 中创建一个 String 对象后,在 Local Reference 表中就会相应新增一个 Local Reference。

运行 nativemethod 的线程的堆栈记录着 Local Reference 表的内存位置,Local Reference 表中存放 JNI Local Reference,实现 Local Reference 到 Java 对象的映射。
native method 代码间接访问 Java 对象。通过线程堆栈中的记录着 Local Reference 表的内存位置的指针定位相应的 Local Reference 的位置,然后通过相应的 Local Reference 映射到 Java 对象。
当 nativemethod 引用一个 Java 对象时,会在 Local Reference 表中创建一个新 Local Reference。在 Local Reference 结构中写入内容,实现 Local Reference 到 Java 对象的映射。
native method 调用 DeleteLocalRef() 释放某个 JNI Local Reference 时,首先通过线程堆栈中的记录着 Local Reference 表的内存位置的指针定位相应的 Local Reference 在 Local Ref 表中的位置,然后从 Local Ref 表中删除该 Local Reference,也就取消了对相应 Java 对象的引用(Ref count 减 1)。
当越来越多的 LocalReference 被创建,这些 Local Reference 会在 Local Ref 表中占据越来越多内存。当 Local Reference 太多以至于 Local Ref 表的空间被用光,JVM 会抛出异常,从而导致 JVM 的崩溃。

产生Local Reference的操作有:
1.FindClass
2.NewString/ NewStringUTF/NewObject/NewByteArray
3.GetObjectField/GetObjectClass/GetObjectArrayElement
4.GetByteArrayElements和GetStringUTFChars

解决方法:
在native method中引用完java对象后及时调用env->DeleteLocalRef方法手动释放本地引用
如果native method返回java对象就不需要手动release,因为java会自动回收

但通过JNI传递对象数组时,由于需要在一个for循环中将C++对象数组成员中的每一个元素通过SetObjectField与java对象的元素进行对应,并调用SetObjectArrayElement将对象添加到数组中,期间可能会不断生成local reference,但是不能在循环中手动release,最终引起local reference内存泄露,因此针对与这种情况可以将对象数组分批传递
————————————————
版权声明:本文为优快云博主「xpz445094213」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.youkuaiyun.com/xpz445094213/article/details/46633889

<think> 根据用户需求,用户遇到JNI错误:'use of deleted local reference 0x89'。这个错误通常与JNI局部引用管理有关。以下将逐步解释原因并提供解决方案。 ### 错误原因 在JNI中,局部引用(Local Reference)由JNI函数创建(如`NewObject`、`NewString`等),并在当前本地方法执行期间有效。当本地方法返回时,局部引用会自动释放。然而,在以下情况下可能引发该错误: 1. **手动删除后再次使用**:调用`DeleteLocalRef`删除一个局部引用后,再次使用该引用。 2. **超出作用域后使用**:局部引用在创建它的本地方法返回后自动失效,若在另一个方法中使用该引用则会导致错误。 3. **局部引用表溢出**:创建过多局部引用未及时删除,导致局部引用表溢出(默认容量为512),此时JVM可能自动删除部分引用。 ### 解决方案 #### 1. **避免使用已删除的引用** - 确保在调用`DeleteLocalRef`后不再使用该引用。 - 示例: ```c jobject localObj = env->NewObject(...); // 创建局部引用 // 使用localObj... env->DeleteLocalRef(localObj); // 删除引用 // 之后不再使用localObj ``` #### 2. **管理局部引用生命周期** - 若需在多个本地方法间传递对象,应使用**全局引用**(Global Reference): ```c jobject globalObj = env->NewGlobalRef(localObj); // 转换为全局引用 env->DeleteLocalRef(localObj); // 删除局部引用 // 后续可通过globalObj使用对象 ``` - 使用后务必释放全局引用: ```c env->DeleteGlobalRef(globalObj); ``` #### 3. **处理局部引用表溢出** - **显式删除不再使用的局部引用**:尤其在循环中创建大量引用时。 ```c for (int i = 0; i < 1000; i++) { jobject tmp = env->NewObject(...); // 使用tmp... env->DeleteLocalRef(tmp); // 及时删除 } ``` - **扩大局部引用表容量**(谨慎使用):通过`PushLocalFrame`和`PopLocalFrame`管理批量引用: ```c env->PushLocalFrame(256); // 创建容量为256的新帧 jobject ref1 = env->NewObject(...); // 在新帧中创建引用 jobject ref2 = env->NewObject(...); // 使用ref1、ref2... env->PopLocalFrame(NULL); // 释放整个帧及其所有引用 ``` #### 4. **使用`EnsureLocalCapacity`预分配** - 提前检查局部引用表容量: ```c if (env->EnsureLocalCapacity(100) == JNI_OK) { // 安全创建100个局部引用 } else { // 处理错误 } ``` ### 调试建议 - **启用JNI检查**:在Android开发中,通过`adb shell setprop debug.checkjni 1`开启额外检查。 - **使用CheckJNI模式**:捕获无效引用使用等错误,帮助定位问题。 ### 总结 该错误的核心是**无效引用访问**,解决方案围绕: 1. 避免使用已删除的引用; 2. 跨方法传递时升级为全局引用; 3. 及时删除局部引用防止溢出。 §§相关问题§§ 1. JNI中局部引用和全局引用的区别是什么? 2. 如何避免JNI中的内存泄漏? 3. JNI常见错误有哪些及如何调试? </think> ### JNI中 "use of deleted local reference 0x89" 错误解决方案 这个错误表示你正在尝试使用一个已被删除的JNI局部引用。以下是系统性的解决方法: #### 1. **理解引用生命周期** - 局部引用在本地方法返回时**自动删除** - 若手动调用`DeleteLocalRef()`后继续使用引用会导致此错误 - 示例错误代码: ```c jobject obj = env->NewObject(...); // 创建引用 env->DeleteLocalRef(obj); // 删除引用 env->CallVoidMethod(obj, ...); // 错误!使用已删除引用 ``` #### 2. **关键解决方案** **✅ 方案1:避免重复删除** ```c jobject localRef = env->NewObject(...); // 使用引用... // 不要重复删除!下列代码仅在特定情况需要: // env->DeleteLocalRef(localRef); ``` **✅ 方案2:跨线程/函数时升级为全局引用** ```c // 创建全局引用 jobject globalRef = env->NewGlobalRef(localRef); env->DeleteLocalRef(localRef); // 删除局部引用 // 在其他函数/线程中使用全局引用 env->CallVoidMethod(globalRef, ...); // 最后必须手动释放 env->DeleteGlobalRef(globalRef); ``` **✅ 方案3:使用`Push/PopLocalFrame`管理批量引用** ```c env->PushLocalFrame(64); // 创建新引用作用域 jobject ref1 = env->NewObject(...); // 自动管理 jobject ref2 = env->NewObject(...); // 自动释放所有当前帧内的引用 env->PopLocalFrame(NULL); ``` #### 3. **预防措施** - **引用计数检查**:使用Android Studio的**Memory Profiler**检查JNI引用泄漏 - **启用严格模式**:在`AndroidManifest.xml`中添加: ```xml <application android:debuggable="true" ...> ``` - **JNI日志**:通过`adb shell setprop log.tag.JNIREF VERBOSE`开启引用日志 #### 4. **典型错误场景修复** **场景:在循环中创建引用未释放** ```c for (int i = 0; i < 1000; i++) { jstring jstr = env->NewStringUTF("test"); // 处理jstr... env->DeleteLocalRef(jstr); // 必须显式删除! } ``` **场景:跨线程使用引用** ```c // 错误做法:直接传递局部引用 std::thread th([=]{ env->CallVoidMethod(localRef, ...); // 崩溃! }); // 正确做法:使用全局引用 jobject globalRef = env->NewGlobalRef(localRef); std::thread th([=]{ env->CallVoidMethod(globalRef, ...); env->DeleteGlobalRef(globalRef); // 在线程内释放 }); ``` > **重要原则**:局部引用生命周期不超过当前本地方法调用,若需长期持有必须转换为全局引用。在Android开发中,超过98%的此类错误源于跨线程使用局部引用或手动删除后复用[^1]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值