JNI提供了一些实例和数组类型(jobject、jclass、jstring、jarray等)作为不透明的引用供本地代码使用。本地代码永远不会直接操作引用指向的VM内部的数据内容。要进行这些操作,必须通过使用JNI操作一个不引用来间接操作数据内容。因为只操作引用,你不必担心特定JVM中对象的存储方式等信息。这样的话,你有必要了解一下JNI中的几种不同的引用:

1、 JNI支持三种引用:局部引用、全局引用、弱全局引用(下文简称“弱引用”)。

2、 局部引用和全局引用有不同的生命周期。当本地方法返回时,局部引用会被自动释放。而全局引用和弱引用必须手动释放。

3、 局部引用或者全局引用会阻止GC回收它们所引用的对象,而弱引用则不会。

4、 不是所有的引用可以被用在所有的场合。例如,一个本地方法创建一个局部引用并返回后,再对这个局部引用进行访问是非法的。

本章中,我们会详细地讨论这些问题。合理地管理JNI引用是写出高质量的代码的基础。

5.1 局部引用和全局引用

什么是全局引用和局部引用?它们有什么不同?我们下面使用一些例子来说明。

 1 /* 局部引用 */
2 jclass classLocal = env->FindClass("java/lang/String");
3 env->DeleteLocalRef(classLocal);
4 /* 全局引用 */
5 jclass classLocal = env->FindClass("com/kvdb/NativeKVDB");
6 jclass classGlobal = (jclass) env->NewGlobalRef(classLocal);
7 env->DeleteGlobalRef(classGlobal);
8 /* 弱引用 */
9 jclass classLocal =env->FindClass("mypkg/MyCls2");
10 jclass classGlobal = NewWeakGlobalRef(env, classLocal);
11 env->DeleteGlobalRef(classGlobal);

5.1.1 局部引用

大多数JNI函数会创建局部引用。例如,NewObject创建一个新的对象实例并返回一个对这个对象的局部引用。

局部引用只有在创建它的本地方法返回前有效。本地方法返回后,局部引用会被自动释放。

你不能在本地方法中把局部引用存储在静态变量中缓存起来供下一次调用时使用。

 1  /* This code is illegal */
2 jstring MyNewString(JNIEnv *env, jchar *chars, jint len) {
3 static jclass stringClass = NULL;
4 jmethodID cid;
5 jcharArray elemArr;
6 jstring result;
7 stringClass = (*env)->FindClass(env, "java/lang/String");
8 if (stringClass == NULL) {
9 return NULL; /* exception thrown */
10 }
11 cid = (*env)->GetMethodID(env, stringClass,
12
13 "<init>", "([C)V");
14
15 ...
16 elemArr = (*env)->NewCharArray(env, len);
17 ...
18 result = (*env)->NewObject(env, stringClass, cid, elemArr);
19 (*env)->DeleteLocalRef(env, elemArr);
20 return result;
21 }

上面代码中,我们省略了和我们的讨论无关的代码。假设一个本地方法C.f调用了MyNewString:

JNIEXPORT jstring JNICALL Java_C_f(JNIEnv *env, jobject this) {

char *c_str = ...;

...

return MyNewString(c_str);

}

C.f方法返回后,VM释放了在这个方法执行期间创建的所有局部引用,也包含对String类的引用stringClass。

释放一个局部引用有两种方式,一个是本地方法执行完毕后VM自动释放,另外一个是程序员通过DeleteLocalRef手动释放。

既然VM会自动释放局部引用,为什么还需要手动释放呢?因为局部引用会阻止它所引用的对象被GC回收。

局部引用只在创建它们的线程中有效,跨线程使用是被禁止的。不要在一个线程中创建局部引用并存储到全局引用中,然后到另外一个线程去使用。

5.1.2全局引用

全局引用可以跨方法、跨线程使用,直到它被手动释放才会失效。同局部引用一样,全局引用也会阻止它所引用的对象被GC回收。

与局部引用可以被大多数JNI函数创建不同,全局引用只能使用一个JNI函数创建:NewGlobalRef。下面这个版本的MyNewString演示了怎么样使用一个全局引用:

 1 /* This code is OK */
2 jstring MyNewString(JNIEnv *env, jchar *chars, jint len) {
3 static jclass stringClass = NULL;
4 ...
5 if (stringClass == NULL) {
6 jclass localRefCls =
7 (*env)->FindClass(env, "java/lang/String");
8 if (localRefCls == NULL) {
9 return NULL; /* exception thrown */
10 }
11 /* Create a global reference */
12 stringClass = (*env)->NewGlobalRef(env, localRefCls);
13 /* The local reference is no longer useful */
14 (*env)->DeleteLocalRef(env, localRefCls);
15 /* Is the global reference created successfully? */
16 if (stringClass == NULL) {
17 return NULL; /* out of memory exception thrown */
18 }
19 }
20 ...
21 }

上面这段代码中,一个由FindClass返回的局部引用被传入NewGlobalRef,用来创建一个对String类的全局引用。删除localRefCls后,我们检查NewGlobalRef是否成功创建stringClass。

5.1.3弱引用

弱引用使用NewGlobalWeakRef创建,使用DeleteGlobalWeakRef释放。与全局引用类似,弱引用可以跨方法、线程使用。与全局引用不同的是,弱引用不会阻止GC回收它所指向的VM内部的对象。

在MyNewString中,我们也可以使用弱引用来存储stringClass这个类引用,因为java.lang.String这个类是系统类,永远不会被GC回收。

当本地代码中缓存的引用不一定要阻止GC回收它所指向的对象时,弱引用就是一个最好的选择。假设,一个本地方法mypkg.MyCls.f需要缓存一个指向类mypkg.MyCls2的引用,如果在弱引用中缓存的话,仍然允许mypkg.MyCls2这个类被unload:

 1 JNIEXPORT void JNICALL Java_mypkg_MyCls_f(JNIEnv *env, jobject self) {
2 static jclass myCls2 = NULL;
3 if (myCls2 == NULL) {
4 jclass myCls2Local =
5 (*env)->FindClass(env, "mypkg/MyCls2");
6 if (myCls2Local == NULL) {
7 return; /* can't find class */
8 }
9 myCls2 = NewWeakGlobalRef(env, myCls2Local);
10 if (myCls2 == NULL) {
11 return; /* out of memory */
12 }
13 }
14 ... /* use myCls2 */
15 }

我们假设MyCls和MyCls2有相同的生命周期(例如,他们可能被相同的类加载器加载),因为弱引用的存在,我们不必担心MyCls和它所在的本地代码在被使用时,MyCls2这个类出现先被unload,后来又会preload的情况。

当然,真的发生这种情况时(MyCls和MyCls2的生命周期不同),我们必须检查缓存过的弱引用是指向活动的类对象,还是指向一个已经被GC给unload的类对象。下一节将告诉你怎么样检查弱引用是否活动。

Android NDK开发篇:如何使用JNI中的global reference和local reference的更多相关文章

  1. Android NDK开发篇(六):Java与原生代码通信(异常处理)

    一.捕获异常 异常处理是Java中的功能.在Android中使用SDK进行开发的时候常常要用到.Android原生代码在运行过程中假设遇到错误,须要检測,并抛出异常给Java层.运行原生代码出现了问题 ...

  2. Android NDK开发篇(五):Java与原生代码通信(数据操作)

    尽管说使用NDK能够提高Android程序的运行效率,可是调用起来还是略微有点麻烦.NDK能够直接使用Java的原生数据类型,而引用类型,由于Java的引用类型的实如今NDK被屏蔽了,所以在NDK使用 ...

  3. Android NDK开发篇:Java与原生代码通信(异常处理)

    一.捕获异常 异常处理是Java中的功能,在Android中使用SDK进行开发的时候经常要用到.Android原生代码在执行过程中如果遇到错误,需要检测,并抛出异常给Java层.执行原生代码出现了问题 ...

  4. Android NDK开发篇:Java与原生代码通信(数据操作)

    虽然说使用NDK可以提高Android程序的执行效率,但是调用起来还是稍微有点麻烦.NDK可以直接使用Java的原生数据类型,而引用类型,因为Java的引用类型的实现在NDK被屏蔽了,所以在NDK使用 ...

  5. Android NDK开发篇(四):Java与原生代码通信(原生方法声明与定义与数据类型)

    Java与原生代码通信涉及到原生方法声明与定义.数据类型.引用数据类型操作.NIO操作.訪问域.异常处理.原生线程 1.原生方法声明与定义 关于原生方法的声明与定义在上一篇已经讲一点了,这次具体分析一 ...

  6. Android NDK开发篇:Java与原生代码通信(原生方法声明与定义与数据类型)

    Java与原生代码通信涉及到原生方法声明与定义.数据类型.引用数据类型操作.NIO操作.访问域.异常处理.原生线程 1.原生方法声明与定义 关于原生方法的声明与定义在上一篇已经讲一点了,这次详细分析一 ...

  7. !! 2.对十份论文和报告中的关于OpenCV和Android NDK开发的总结

    http://hujiaweibujidao.github.io/blog/2013/11/18/android-ndk-and-opencv-development-3/ Android Ndk a ...

  8. android NDK开发在本地C/C++源码中设置断点单步调试具体教程

    近期在学android NDK开发,折腾了一天,最终可以成功在ADT中设置断点单步调试本地C/C++源码了.网上关于这方面的资料太少了,并且大都不全,并且调试过程中会出现各种各样的问题,真是非常磨人. ...

  9. Android NDK开发 JNI操作java构造方法,普通方法,静态方法(七)

    Android NDK开发 JNI操作java普通.静态.构造方法 1.Jni实例化一个Java类的实例jobject 1.通过FindClas( ),获取Java类的的jclass 2.通过GetM ...

随机推荐

  1. KeepAlived双主模式高可用集群

    keepalived是vrrp协议的实现,原生设计目的是为了高可用ipvs服务,keepalived能够配置文件中的定义生成ipvs规则,并能够对各RS的健康状态进行检测:通过共用的虚拟IP地址对外提 ...

  2. 汇编LED实验

    汇编语言点亮LED 拿到一款全新的芯片,第一个要做的事情的就是驱动其 GPIO,控制其 GPIO 输出高低电平. GPIO口是IO口的一个功能之一. 一.接下来的步骤离不开芯片手册: 1.使能所有时钟 ...

  3. 基于Kubernetes实现前后端应用的金丝雀发布

    基于Kubernetes实现前后端应用的金丝雀发布 公司的研发管理平台实现了Gitlab+Kubernetes的Devops,在ToB和ToC场景中,由于用户量大,且预发布环境和生产环境或多或少存在差 ...

  4. [源码解析] PyTorch分布式优化器(2)----数据并行优化器

    [源码解析] PyTorch分布式优化器(2)----数据并行优化器 目录 [源码解析] PyTorch分布式优化器(2)----数据并行优化器 0x00 摘要 0x01 前文回顾 0x02 DP 之 ...

  5. 零基础学习java------day9------多态,抽象类,接口

    1. 多态 1.1  概述: 某一个事务,在不同环境下表现出来的不同状态 如:中国人可以是人的类型,中国人 p = new  中国人():同时中国人也是人类的一份,也可以把中国人称为人类,人类  d  ...

  6. 『学了就忘』Linux启动引导与修复 — 69、启动引导程序(grub)

    目录 1.启动引导程序(Boot Loader)简介 2.启动引导程序grub的作用 3.启动引导程序grub的位置 4./grub目录中其他的文件简单介绍 提示: 简单地说,Boot Loader就 ...

  7. Activity 详解

    1.活动的生命周期 1.1.返回栈 Android是使用任务(Task)来管理活动的,一个任务就是一组存放在栈里的活动的集合,这个栈也被称作返回栈.栈是一种先进后出的数据结构,在默认情况下,每当我们启 ...

  8. Xcode功能快捷键

    隐藏xcode command+h退出xcode command+q关闭窗口 command+w关闭所有窗口 command+option+w关闭当前项目 command+control+w关闭当前文 ...

  9. OSGi系列 - 使用Eclipse查看Bundle源码

    使用Eclipse开发OSGi Bundle时,会发现有很多现成的Bundle可以用.但如何使用这些Bundle呢?除了上网搜索查资料外,阅读这些Bundle的源码也是一个很好的方法. 本文以org. ...

  10. GO类型转换

    golang []byte转string golang中,字符切片[]byte转换成string最简单的方式是 package main import ( "fmt" _ &quo ...