【问题标题】:How to structure JNI call to avoid memory leak?如何构造 JNI 调用以避免内存泄漏?
【发布时间】:2014-07-30 18:40:15
【问题描述】:

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

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

然后我用本机 (C) 代码实现了另一个类。我们称这个类为背景类。这个类做一些后台工作,并调用 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(){
    JNIManager jniManager = new JNIManager();

     //Do some stuff...

    jniManager.releaseBackground();
}

我不确定第一点。

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

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

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

【问题讨论】:

  • 谁以及如何打电话给Background::someMethod()
  • 它基本上是一个在我使用的背景类内部提供的侦听器方法。我只是扩展背景类并覆盖侦听器来做一些自定义的东西。因此,它由库代码调用..

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


【解决方案1】:

你做错了几件事。

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

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

  3. 是的,您有泄漏。你不会在任何地方deleteback,事实上你甚至不会在任何地方存储它的地址,所以它泄漏。如果您打算将Background 用作单例类,那么实际上使用适当的单例模式来实现它。如果您希望Background 的生命周期与JNIManager 的生命周期相关联,那么您必须向JNIManager 添加适当的终结代码以销毁Background 实例(并将Background 实例存储在某处,例如在JNIManager 类实例上)。

【讨论】:

  • 我已按照您的建议对原始代码进行了一些更改..您能看一下并告诉我这样做是否正确吗?谢谢!
猜你喜欢
  • 2010-12-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-13
  • 1970-01-01
相关资源
最近更新 更多