ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
我们知道,Java中创建的对象最后是由垃圾回收器来回收和释放内存的,可它对JNI有什么影响呢?下面看一个例子: **垃圾回收例子** ~~~ static jobject save_thiz = NULL; //定义一个全局的jobject static void android_media_MediaScanner_processFile(JNIEnv*env, jobject thiz, jstring path, jstringmimeType, jobject client) { ...... //保存Java层传入的jobject对象,代表MediaScanner对象 save_thiz = thiz; ...... return; } //假设在某个时间,有地方调用callMediaScanner函数 void callMediaScanner() { //在这个函数中操作save_thiz,会有问题吗? } ~~~ 上面的做法肯定会有问题,因为和save_thiz对应的Java层中的MediaScanner很有可能已经被垃圾回收了,也就是说,save_thiz保存的这个jobject可能是一个野指针,如使用它,后果会很严重。 可能有人要问,将一个引用类型进行赋值操作,它的引用计数不会增加吗?而垃圾回收机制只会保证那些没有被引用的对象才会被清理。问得对,但如果在JNI层使用下面这样的语句,是不会增加引用计数的。 ~~~ save_thiz = thiz; //这种赋值不会增加jobject的引用计数。 ~~~ 那该怎么办?不必担心,JNI规范已很好地解决了这一问题,JNI技术一共提供了三种类型的引用,它们分别是: - Local Reference:本地引用。在JNI层函数中使用的非全局引用对象都是Local Reference。它包括函数调用时传入的jobject、在JNI层函数中创建的jobject。LocalReference最大的特点就是,一旦JNI层函数返回,这些jobject就可能被垃圾回收。 - Global Reference:全局引用,这种对象如不主动释放,就永远不会被垃圾回收。 - Weak Global Reference:弱全局引用,一种特殊的GlobalReference,在运行过程中可能会被垃圾回收。所以在程序中使用它之前,需要调用JNIEnv的IsSameObject判断它是不是被回收了。 平时用得最多的是Local Reference和Global Reference,下面看一个实例,代码如下所示: **android_media_MediaScanner.cpp::MyMediaScannerClient构造函数** ~~~ MyMediaScannerClient(JNIEnv *env, jobjectclient) : mEnv(env), //调用NewGlobalRef创建一个GlobalReference,这样mClient就不用担心被回收了。 mClient(env->NewGlobalRef(client)), mScanFileMethodID(0), mHandleStringTagMethodID(0), mSetMimeTypeMethodID(0) { ...... } //析构函数 virtual ~MyMediaScannerClient() { mEnv->DeleteGlobalRef(mClient);//调用DeleteGlobalRef释放这个全局引用。 } ~~~ 每当JNI层想要保存Java层中的某个对象时,就可以使用Global Reference,使用完后记住释放它就可以了。这一点很容易理解。下面要讲有关LocalReference的一个问题,还是先看实例,代码如下所示: **android_media_MediaScanner.cpp::MyMediaScannerClient的scanFile** ~~~ virtualbool scanFile(const char* path, long long lastModified, long long fileSize) { jstringpathStr; //调用NewStringUTF创建一个jstring对象,它是Local Reference类型。 if((pathStr = mEnv->NewStringUTF(path)) == NULL) return false; //调用Java的scanFile函数,把这个jstring传进去 mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr,lastModified, fileSize); /* 根据LocalReference的说明,这个函数返回后,pathStr对象就会被回收。所以 下面这个DeleteLocalRef调用看起来是多余的,其实不然,这里解释一下原因: 1)如果不调用DeleteLocalRef,pathStr将在函数返回后被回收。 2)如果调用DeleteLocalRef的话,pathStr会立即被回收。这两者看起来没什么区别, 不过代码要是像下面这样的话,虚拟机的内存就会被很快被耗尽: for(inti = 0; i < 100; i++) { jstring pathStr = mEnv->NewStringUTF(path); ......//做一些操作 //mEnv->DeleteLocalRef(pathStr); //不立即释放Local Reference } 如果在上面代码的循环中不调用DeleteLocalRef的话,则会创建100个jstring, 那么内存的耗费就非常可观了! */ mEnv->DeleteLocalRef(pathStr); return(!mEnv->ExceptionCheck()); } ~~~ 所以,没有及时回收的Local Reference或许是进程占用过多的一个原因,请务必注意这一点。