如何构建JNI调用以避免内存泄漏

How to structure JNI call to avoid memory leak?

本文关键字:内存 泄漏 调用 JNI 何构建 构建      更新时间:2023-10-16

所以我在Java中有以下JNIManager类。如您所见,在这个类中,我定义了一个名为setUpBackGround();

的本地方法。
public class JNIManager{
   public native void setUpBackground();
   public void messageMe(byte[] byteArray);
}
然后我有另一个用本机(C)代码实现的类。我们把这个类命名为Background class。这个类做一些后台工作,并调用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()。

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

我有两个问题。

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

我不确定第一点。

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

这是否意味着一旦setupBackground() JNI调用返回,当前线程被删除?因为后台类是在这个线程中创建的,这个类的方法(比如someemethod())不会在同一个线程上运行吗?如果不是,是不是attachToThread()方法定义了正确的方式来获得当前线程和使用它的JNIEnv*?

我需要创建的Background对象在JNIManager的整个生命周期中都存在。然后偶尔调用后台对象的someemethod ()类中的messageMe()方法进行调用JNIManager类(如代码中所示)。

你做错了几件事。

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

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

  3. 是的,你有一个泄漏。你没有在任何地方delete back,事实上你甚至没有在任何地方存储它的地址,所以它泄漏。如果您打算将Background作为一个单例类,那么实际上应该使用适当的单例模式来实现它。如果你打算将Background的生命周期与JNIManager的生命周期捆绑在一起,那么你必须在JNIManager中添加适当的终结代码来销毁Background实例(并将Background实例存储在某处,例如在JNIManager类实例上)。