Java 如何构造JNI调用以避免内存泄漏?

Java 如何构造JNI调用以避免内存泄漏?,java,c++,c,memory-leaks,java-native-interface,Java,C++,C,Memory Leaks,Java Native Interface,因此,我有以下Java中的JNIManager类。在这个类中,正如您所看到的,我定义了一个名为setUpBackGround()的本机方法 然后我用本机(C)代码实现了另一个类。让我们把这个类称为背景类。此类执行一些后台工作,并调用JNIManager类的messageMe()方法,将字节[]传递给它 class Background{ JNIEnv* mJNIEnv; jbyteArray mArray; jobject mJObject; Backgroun

因此,我有以下Java中的JNIManager类。在这个类中,正如您所看到的,我定义了一个名为setUpBackGround()的本机方法

然后我用本机(C)代码实现了另一个类。让我们把这个类称为背景类。此类执行一些后台工作,并调用JNIManager类的messageMe()方法,将字节[]传递给它

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()

关于整个设置,我有两个问题

1) 当jniManager在上述方法结束时超出范围时,我用“new”关键字动态创建的后台类会自动被垃圾收集吗?我认为这不会也会导致内存泄漏。这是正确的吗?如果是,我可以做些什么来纠正它

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;
}
我不确定第一点

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

这是否意味着一旦setupBackground()JNI调用返回,当前线程将被删除? 因为后台类是在这个线程中创建的,所以这个类的方法(比如someMethod())不会在同一个线程上运行吗?如果不是,那么定义attachToThread()方法是否是获取当前线程并使用其JNIEnv*的正确方法

我基本上需要创建的背景对象在JNIManager的整个生命周期中都存在。然后偶尔调用后台对象的someMethod() (通过某个外部类)然后调用
JNIManager类(如代码中所示)

你做错了几件事

  • 在JNI方法返回后,不能存储对
    JNIEnv
    的引用
    JNIEnv
    s在JNI调用之间可能不会保持相同(特别是,两个不同的线程将具有不同的
    JNIEnv
    s,Java类终结器可能在单独的线程上运行)

  • 在JNI方法返回后,不能存储localref。从JNI函数返回时,将删除所有本地引用。如果需要保留对对象的引用,请使用全局引用(然后由您负责删除)。
    mArray
    mJObject
    都必须是全局引用

  • 是的,你漏了。您不会在任何地方
    删除
    ,事实上,您甚至没有将其地址存储在任何地方,因此它将泄漏。如果您打算将
    Background
    作为一个单例类,那么实际使用一个合适的单例模式来实现它。如果您希望
    Background
    的生存期与
    JNIManager
    的生存期相关联,那么您必须向
    JNIManager
    添加适当的终止代码,以销毁
    Background
    实例(并将
    Background
    实例存储在某处,例如
    JNIManager
    类实例上)


  • 我已经按照你的建议对原始代码做了一些更改。你能看一下并让我知道这是否是正确的方法吗?谢谢谁以及如何调用
    Background::someMethod()
    ?它基本上是我正在使用的后台类内部提供的侦听器方法。我只是扩展后台类并重写侦听器以执行一些自定义操作。因此,它由库代码调用。。
    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(){
        JNIManager jniManager = new JNIManager();
    
         //Do some stuff...
    
        jniManager.releaseBackground();
    }