jni 全局调用java方法,JNI 局部引用和全局引用

Java 和 JNI 之间,传递基本类型对象的方式是值复制,而传递引用类型对象的方式是通过引用。在 JNI 中,有二种引用,局部引用(Local Reference)、全局引用(Global Reference)。弱全局引用(Weak Global Reference)是全局引用的一种特殊形式。

局部引用

局部引用只有在JNI层的函数调用期期间有效,函数返回后,局部引用会被自动释放。局部引用会阻止垃圾收集器回收底层对象,只有当函数返回或者手动释放局部引用,垃圾收集器才有可能回收底层对象。

虽然说局部引用会被自动释放,但是我们最好还是手动释放,并且在某些极端情况下,为了不造成内存紧张,还是需要手动释放。例如

在JNI层的方法中,引用了一个占用很大内存的Java对象,然后对这个对象执行一些操作,然后再执行一些与这个对象无关的操作,最后返回。这个Java对象在JNI层被引用期间,是无法被垃圾回收的,那么当我们执行完了与这个对象相关的操作后,最好立即释放这个局部引用,然后再执行一些与这个对象无关的操作,最后函数返回。

当在JNI函数中,通过循环创建大量的局对象时,最好及时释放局部引用,否则可能造成OOM。

那么如何释放局部引用呢?可以通过如下函数

void DeleteLocalRef(JNIEnv *env, jobject localRef);

复制代码

我在 Android 源码中找到一个释放局部引用的例子

virtual status_t scanFile(const char* path, long long lastModified,

long long fileSize, bool isDirectory, bool noMedia){

// 创建一个局部引用

jstring pathStr;

if ((pathStr = mEnv->NewStringUTF(path)) == NULL) {

mEnv->ExceptionClear();

return NO_MEMORY;

}

// 使用刚创建的局部引用作为参数,调用Java对象的方法

mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified,

fileSize, isDirectory, noMedia);

// 局部引用已经不再需要,立即释放它

mEnv->DeleteLocalRef(pathStr);

return checkAndClearExceptionFromCallback(mEnv, "scanFile");

}

复制代码

从这个源码例子可以看出,尽管不手动释放这个局部引用也没啥影响,但是源码中还是通过DeleteLocalRef()手动释放。源码都尚且如此,何况我们一个普通的开发者呢?所以,在JNI函数中创建了局部引用,使用完毕后,手动释放这个局部引用。

其实局部引用还可以通过如下函数手动创建

jobject NewLocalRef(JNIEnv *env, jobject ref);

复制代码

参数ref可以是全局引用、弱全局引用或局部引用。目前我还不清楚为全局引用、局部引用创建局部引用有什么用,但是为弱全局引用创建局部引用还是有用的,这个作用会在文章后面介绍。

全局引用

NI函数在返回后,局部引用终究是会被释放。然而有时候我们需要把Java层传下来的对象进行保存,然后在函数返回后的某个时刻再进行操作。此时就需要使用如下函数为这个对象创建一个全局引用。

jobject NewGlobalRef(JNIEnv *env, jobject obj);

复制代码

参数 obj 可以是一个局部引用,也可以是一个全局引用。这个函数执行成功会返回一个引用,然而如果发生OOM,返回NULL。

全局引用在不需要的时候,必须手动释放,使用的函数原型如下

void DeleteGlobalRef(JNIEnv *env, jobject globalRef);

复制代码

Android 源码中有如下一个例子

MyMediaScannerClient(JNIEnv *env, jobject client)

: mEnv(env),

mClient(env->NewGlobalRef(client)), // 创建全局引用

{}

virtual ~MyMediaScannerClient()

{

// 释放全局引用

mEnv->DeleteGlobalRef(mClient);

}

复制代码

MyMediaScannerClient 的构造函数的参数 client 是一个Java层的对象,它通过 NewGlobalRef() 函数创建一个全局引用,并用 mClient 变量保存。有了这个全局引用,就可以阻止垃圾收集器对它进行回收,这样就可以随时安全操作这个Java对象。

当 MyMediaScannerClient 销毁时,会调用它的析构函数,此时就调用 DeleteGlobalRef() 来删除这个全局引用。这个全局引用被释放后,JNI层就不再阻止这个对象的垃圾回收。

弱全局引用

弱全局引用(Weak Global References)是一种特殊的全局引用,当一个底层Java对象只被弱全局引用所指向,这个弱全局引用不会阻止垃圾收集器回收这个底层Java对象。

我们可以通过如下函数来创建弱全局引用

jweak NewWeakGlobalRef(JNIEnv *env, jobject obj);

复制代码

当参数obj为NULL或者发生OOM(虚拟机会抛出OOM异常)时,函数返回NULL。

弱全局引用的目的很显示,那就是不阻止垃圾回收,一般是为了防止内存泄露。

弱全局引用也需要一定的虚拟机资源,因此在不需要弱全局引用时,需要使用如下函数释放

void DeleteWeakGlobalRef(JNIEnv *env, jweak obj);

复制代码

既然弱全局引用不会阻止垃圾回收,那么它指向的底层对象可能被释放,因此在使用它时,我们最好使用下面的函数来把弱全局引用与NULL进行比较

// 测试两个引用是否指向相同的Java对象

// 如果两个引用指向相同的Java对象,或者两个引用都为NULL,

// 函数返回JNI_TRUE,否则返回JNI_FALSE

jboolean IsSameObject(JNIEnv *env, jobject ref1, jobject ref2);

复制代码

你可能还没有发现,弱全局引用的类型为jweak,那么如何使用它呢?其实我们只要搞清楚jweak和jobject的关系就行,代码如下

class _jobject {};

typedef _jobject* jobject;

typedef _jobject* jweak;

复制代码

原来,jobject和jweak都是指针,而且都指向同一种类型的对象,因此把jweak当作jobject来用就行了。

我们来看看Android源码中如何使用这个弱全局引用的

JMediaCodec::JMediaCodec(

JNIEnv *env, jobject thiz,

const char *name, bool nameIsType, bool encoder)

{

// ...

// 创建弱全局引用

mObject = env->NewWeakGlobalRef(thiz);

}

JMediaCodec::~JMediaCodec() {

// ...

// 析构函数中,删除弱全局引用

env->DeleteWeakGlobalRef(mObject);

}

void JMediaCodec::handleFrameRenderedNotification(const sp &msg){

// ...

// mOjetct是一个弱全局引用

// 调用mObject的Java方法

env->CallVoidMethod(

mObject, gFields.postEventFromNativeID,

EVENT_FRAME_RENDERED, arg1, arg2, obj);

}

复制代码

从这个例子可以看出,虽然mObject类型虽然为jweak,但是它调用Java方法的方式与jobject并没有区别。

另外,源码中并没有如下代码来检测这个弱全局指向的Java对象是否被释放了

jboolean b = env->IsSameObject(mObject, NULL);

复制代码

最后我们来讨论一个把弱全局引用转化为局部引用的情况。由于弱全局引用无法阻止垃圾回收,为了在JNI函数中安全使用这个弱全局引用,可以把它转化为局部引用,这样就能在函数返回前,暂时阻止垃圾回收,于是可以安全操作底层的Java对象。

Android源码的一个例子如下

virtual void onPositionLost(RenderNode& node, const TreeInfo* info) override{

// 为弱全局引用创建一个局部引用,在这个函数返回前,

// 可以防止底层Java对象被垃圾回收

jobject localref = env->NewLocalRef(mWeakRef);

// ...

// 局部引用阻止了垃圾回收,因此可以安全调用Java对象的方法

env->CallVoidMethod(localref, gSurfaceViewPositionLostMethod,

info ? info->canvasContext.getFrameNumber() : 0);

env->DeleteLocalRef(localref);

}

复制代码

那么有人可能会问,如果在为弱全局引用创建局部引用时,底层Java对象已经回收了,那怎么办呢?这个只能你用代码来保证,你要保证在Java层对象销毁前,释放底层对象,底层对象再释放弱全局引用。

工作感想

在我的工作经验中,几乎没有使用弱全局引用,用的最多的是全局引用。不过如果我们遇到源码中使用弱全局引用的情况,应该多思考为何这样使用,以便在后面的工作使用好弱全局引用。

参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值