+-
java – 如何构造JNI调用以避免内存泄漏?
所以我在 Java中有以下JNIManager类.在这个类中,正如您所看到的,我定义了一个名为setUpBackGround()的本机方法;

public class JNIManager{
   public native void setUpBackground();
   public void messageMe(byte[] byteArray);
}

然后我有另一个用本机(C)代码实现的类.我们称这个类为Background Class.这个类做了一些后台工作,并调用JNIManager类的messageMe()方法传递一个byte [].

class Background{
   JNIEnv* mJNIEnv;
   jbyteArray mArray;
   jobject mJObject;    

   Background(JNIEnv * env, jobject jObject){
      mArray = env->NewByteArray(1040);
      mJNIEnv = env;
      mJObject = jObject;
   }

   virtual ~Background(){
      mJNIEnv->DeleteLocalRef(mArray); //is this necessary?
   }

   void someMethod(){
      jclass manager = mJNIEnv->GetObjectClass(mJObject);
      jmethodID method = mJNIEnv->GetMethodID(manager, "messageMe", "([B)V");                         
      mJNIEnv->CallVoidMethod(mJObject, method, mArray);
      mJNIEnv->DeleteLocalRef(manager); // is this necessary?
   }

}

现在,在本机方法setUpBackground中,我执行以下操作,

JNIEXPORT jlong JNICALL Java_com_example_test_JNIManager_setUpBackground
  (JNIEnv * env, jobject jo){
    Background* back = new Background(env,jo);
    return 0;
}

最后,在另一个类的方法中,我创建了一个JNIManager实例并调用本机方法setUpBackground().

otherMethod(){
    JNIManager jniManager = new JNIManager();
    jniManager.setUpBackground();
}

关于这整个设置,我有2个问题.

1)当jniManager在上述方法结束时超出范围时,我使用“new”关键字动态创建的Background类是否会自动被垃圾收集?我认为它不会,并将导致内存泄漏.它是否正确?如果是这样,我该怎么做才能纠正?

2)是否需要DeleteLocalRef()调用以避免内存泄漏,或者JVM是否会在不再使用它们时处理删除它们的任务?

————————————————– —————————————–

更新 – 遵循nneonneo的回答

public class JNIManager{
       private long nativeHandle;

       public JNIManager(){
          nativeHandle = setUpBackground();
       }

       public native long setUpBackground();
       public native void releaseBackground(long handle);
       public void messageMe(byte[] byteArray) {//do some stuff};
}

更新了背景课程

class Background{

public:

   JavaVM* mJvm;
   JNIEnv* mJNIEnv;
   jobject mJObject;
   jbyteArray mArray;
   int file;

   Background(JNIEnv * env, jobject jObject){
      env->GetJavaVM(&mJvm);
      attachToThread();
      mJObject = env->NewGlobalRef(jObject);
      mArray = env->NewByteArray(1040);
      file = 1;    //Does this need to be a globalRef ? 
   }

   void destroy(){
      mJNIEnv->DeleteGlobalRef(mArray);
      mJNIEnv->DeleteGlobalRef(mJObject);
      mJvm = NULL;
      mJNIEnv = NULL;
      file = 0;
   }

   void someMethod(){
      attachToThread();
      jclass manager = mJNIEnv->GetObjectClass(mJObject);
      jmethodID method = mJNIEnv->GetMethodID(manager, "messageMe", "([B)V");                         
      mJNIEnv->CallVoidMethod(mJObject, method, mArray);
      mJNIEnv->DeleteLocalRef(manager);
      detachFromThread();
   }

   void attachToThread(){
     mJvm->AttachCurrentThread(&mJNIEnv, NULL);
   }

   void detachFromThread(){
     mJvm->DetachCurrentThread();
   }

}

更新了原生方法

JNIEXPORT jlong JNICALL Java_com_example_test_JNIManager_setUpBackground
   (JNIEnv * env, jobject jo){
     Background* back = new Background(env,jo);
     return reinterpret_cast<jlong>(back);
 }

JNIEXPORT jlong JNICALL Java_com_example_test_JNIManager_releaseBackground
   (JNIEnv * env, jobject jo, jlong handle){
     Background* back = reinterpret_cast<Background* back>(handle);
     back.destroy();
     delete back;
}

更新了otherMethod

otherMethod(){
    JNIManager jniManager = new JNIManager();

     //Do some stuff...

    jniManager.releaseBackground();
}

我不确定第一点.

“JNInvs在JNI调用之间可能不会保持相同(特别是,两个不同的线程将具有不同的JNIEnv)”.

这是否意味着一旦setupBackground()JNI调用返回,当前线程被删除?
由于在这个线程中创建了Background类,这个类的方法(例如someMethod())不会在同一个线程上运行吗?如果没有,attachToThread()方法是否定义了获取当前线程并使用其JNIEnv *的正确方法?

我基本上需要在JNIManager的整个生命周期中创建的Background对象.然后偶尔调用Background对象的someMethod()
(通过一些外部类)然后这将调用中的messageMe()方法
JNIManager类(可以在代码中看到).

最佳答案
你做错了几件事.

>在JNI方法返回后,您无法存储对JNIEnv的引用. JNIEnvs在JNI调用之间可能不会保持相同(特别是,两个不同的线程将具有不同的JNIEnv,并且Java类终结器可以在单独的线程上运行).
>在JNI方法返回后,您无法存储LocalRefs.从JNI函数返回时,将删除所有本地引用.如果需要保留对对象的引用,请使用全局引用(然后由您负责删除). mArray和mJObject都必须是全局引用.
>是的,你有泄漏.您不会在任何地方删除,实际上您甚至不会将其地址存储在任何地方,因此它会泄漏.如果您打算将Background作为单例类,那么实际上使用适当的单例模式来实现它.如果您希望将Background的生命周期与JNIManager的生命周期联系起来,那么您必须向JNIManager添加适当的终结代码以销毁Background实例(并将Background实例存储在某处,例如在JNIManager类实例上).

点击查看更多相关文章

转载注明原文:java – 如何构造JNI调用以避免内存泄漏? - 乐贴网