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

标签 java c++ c memory-leaks java-native-interface

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

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

然后我用 native (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?
   }

}

现在,在 native 方法 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(){
    JNIManager jniManager = new JNIManager();

     //Do some stuff...

    jniManager.releaseBackground();
}

我不确定第一点。

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

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

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

最佳答案

你做错了几件事。

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

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

  3. 是的,你漏水了。您不会在任何地方删除 返回,事实上您甚至不会在任何地方存储它的地址,所以它泄漏。如果您希望 Background 成为单例类,那么实际上使用适当的单例模式来实现它。如果您打算将 Background 的生命周期绑定(bind)到 JNIManager 的生命周期,那么您必须向 JNIManager 添加适当的终结代码以销毁Background 实例(并将 Background 实例存储在某处,例如在 JNIManager 类实例上)。

关于java - 如何构造 JNI 调用以避免内存泄漏?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24133570/

相关文章:

c - 从 CSV 中的行读取值时丢失字符串中的最后一个字符

java - 是否可以声明 Supplier<T> 需要抛出异常?

java - Android First App教程问题

c++ - 允许 C++ 开发的工具,没有单独的 .h .cpp 文件

c++ - 不区分大小写的操作

c - 开关语句 : must default be the last case?

java - 如何在调用自身的方法中使用 ArrayList

java - repaint() JFrame 和 JPanel

c++ - 是否有任何浏览器发送 multipart/form-data 子部分?

c - 在 C 中使用 getchar();每次我使用它时它都会移动到下一个字符吗?包括在分配操作中?