乐趣区

JNI内存管理之Local-Reference-和-Global-Reference知识点

最近开发过程中遇到了 JNI 的 Reference 相关问题,了解到 Local Reference 和 Global Reference 的相关知识点,整理如下:

背景 :项目需求,在 Native C/C++ 层调用上层 Android Camera Java 接口,把所有的操作包括 Camera 都沉到 Native 层去实现。但在 JNI 调试过程中遇到了 android JNI ERROR (app bug): accessed stale local reference 的报错。

现象 :在 Native 层创建 Java 的 Camera 对象,其对象的指针保存到本地,函数返回到 Java 层,之后再进入 Native 层,想通过 Native 层的 Camera 对象指针调用相应的方法,但是发现每次都是重新调用 Java 对象方法后报错。

分析 :在 Native 层创建的 Java 对象,对象创建后会有一个局部引用指向该对象,当从 Native 环境返回到 Java 环境,该局部引用失效,此对象就没有引用计数,Java 的内存回收机制会自动回收该对象,第二次再进入 Native 层访问其之前保存的地址时就会报错。

解决 :使用全局引用始终持有该对象的引用使其不被自动回收,请看下面的知识点。

Local Reference

局部引用,看如下精简代码:

env->NewStringUTF("0"); 

在 JNI 中,每次调用 NewObject 方法创建一个新的对象都会返回一个对该对象的局部引用 (Local Reference),该局部引用只在线程当前的 Native 环境中有效,返回到 Java 环境后该引用与对象之间的联系就会被断掉,引用失效,所以我们不能在 Native 方法中把局部引用缓存用于下一次调用时使用。

局部引用可以无限创建吗

如图:

这里引入局部引用表的概念,每当线程从 Java 环境进入到 Native 环境后,JVM 就会创建该线程 Native 环境的局部引用表,用来保存本次 Native 环境所创建的所有局部引用,每当 Native 中引用或者新创建一个 Java 对象,JVM 就会局部引用表创建一个局部引用,局部引用表是有大小限制的,最大是 512,如果超过限制会报 OOM 内存泄漏。

Q:那如何才能更好的避免由于局部引用过多造成 Native 环境中的 OOM 呢?

A:控制局部引用的生命周期,如果需要创建过多的局部引用,可以在 Java 对象的操作结束后,手动调用 DeleteLocalRef 函数删除局部引用,该局部引用就会在局部引用表中被移除,避免触发局部引用表的大小限制。

注意 :局部引用不是我们平时所理解的代码中的局部变量,局部变量在当前生命周期(例如函数退出)结束后就会失效,而局部引用在函数退出后可能不会失效,它的生命周期是和整个 Native 上下文环境相关联,只有从 Native 环境返回到 Java 环境后局部引用才会失效。

Global Reference

全局引用,终于到了最上面讨论的问题了,因为局部引用在 Native 环境返回到 Java 环境后就会失效,导致下次进入 Native 环境后再次使用相对应的 Java 对象就会出错,所以可以使用全局引用来解决这个问题,全局引用可以始终与 Java 对象保持联系,使得此对象不会被 JVM 回收掉,见如下代码:

JNIEXPORT jint JNICALL JNI_OnLoad (JavaVM * vm, void * reserved) {jclass tmp = env->FindClass("com/example/company/MyClass");  
    jclass class = env->NewGlobalRef(tmp);
    return JNI_VERSION_1_6;  
}

这里需要注意,在不需要使用 Java 对象后尽量手动调用 DeleteGlobalRef() 函数来使得引用失效,避免对象始终存在,产生潜在的内存泄漏。

Weak Global Reference

虚全局引用与全局引用的区别在于该类型的引用可能随时被 JVM 回收掉,这里涉及到几个函数:

NewWeakGlobalRef();
DeleteWeakGlobalRef();
isSameObject();

在使用虚引用前需要通过 isSameObject 将其和 NULL 比较,如果返回 TRUE 返回 true 表示已经被 JVM 回收掉就不能使用了,这里有可能前一行代码判断还是可用,后一行代码时就被 JVM 回收,解决办法时通过 NewLocalRef() 获取虚全局引用,避免当时被 JVM 回收。

参考资料

https://www.cnblogs.com/zhong…
https://www.cnblogs.com/young…
https://stackoverflow.com/que…
https://www.ibm.com/developer…
https://juejin.im/post/5c19bf…

更多文章请关注

更多文章,请关注我的 V X 公 主 号:程序喵大人,欢迎交流。

退出移动版