java - 使用 JNI : are we releasing objects property? 内存泄漏

标签 java c++ java-native-interface

重要说明:这段代码不是从 Java 调用的本地函数。我们的过程是用 C++ 编写的,我们实例化一个 Java VM,我们从 C++ 调用 Java 函数,但绝不会反过来:Java 方法从不调用 native 函数,但 native 函数实例化 Java 对象并在其上调用 Java 函数。

我们正在做这样的事情:

void some_fun()
{
    // env is a JNIEnv*, cls (and cls2) a jclass, and init (and mid) a jmethodID
    jobject obj = env->NewObject(cls, init);
    fill_obj(obj, cpp_data);
    env->callStaticVoidMethod(cls2, mid, obj);
    // env->DeleteLocalRef(obj); // Added out of desperation.
}

fill_obj 取决于必须设置为obj 字段的cpp_data 的种类。例如,如果 Java 类 cls 包含一个 ArrayList,则 cpp_data 将包含一个 std::vector。所以 fill_obj 重载看起来像这样:

void fill_obj(jobject obj, SomeType const& cpp_data)
{
   std::vector<SomeSubType> const& v = cpp_data.inner_data;
   jobject list_obj = env->NewObject(array_list_class_global_ref, its_init_method);

   for (auto it = v.begin(); it != v.end(); ++it) {
      jobject child_obj = env->NewObject(SomeSubType_class_global_ref, its_init_method);
      fill_obj(child_obj , *it);
      env->CallBooleanMethod(list_obj, add_method, child_obj);
   }

   env->SetObjectField(obj, field_id, list_obj);
}

根据我们对 JNI 文档的理解,该代码不应该有任何内存泄漏,因为本地对象在本地方法结束时被销毁。因此,当第一个 fill_obj 结束时,在 C++ 端不再有对 list_obj 或其任何子项的引用,并且当 some_fun 结束时,任何C++ 端对 obj 的引用也消失了。

我的理解是 jobject 包含某种引用计数器,当该引用计数器达到 0 时,C++ 端将不再引用 Java 对象。如果在 Java 端也不再有对该对象的引用,那么 Java 的垃圾收集器可以自由释放该对象占用的资源。

我们有一个方法,在调用时会创建数千个这样的对象,每次调用该方法时,进程在 RAM(常驻内存)中占用的内存会增加 200 MiB 以上,并且该内存是从未发布。

我们添加了对 DeleteLocalRef 的显式调用,但结果是一样的。

这是怎么回事?我们做错了什么吗?

最佳答案

既然你说你的 some_fun() 函数没有被 Java 代码调用,它只是在内部调用 JNI 的纯 C++ 代码,那么是的,你需要调用 DeleteLocalRef() 对您持有的对 Java 对象的任何本地引用,您不传递回 Java。您还需要在 for 循环中执行此操作。

试试这个:

void some_fun()
{
    // env is a JNIEnv*, cls (and cls2) a jclass, and init (and mid) a jmethodID
    jobject obj = env->NewObject(cls, init); // <-- local ref created
    fill_obj(obj, cpp_data);
    env->callStaticVoidMethod(cls2, mid, obj);
    env->DeleteLocalRef(obj); // <-- local ref released
}

void fill_obj(jobject obj, SomeType const& cpp_data)
{
   std::vector<SomeSubType> const& v = cpp_data.inner_data;

   jobject list_obj = env->NewObject(array_list_class_global_ref, its_init_method); // <-- local ref created

   for (auto it = v.begin(); it != v.end(); ++it) {
      jobject child_obj = env->NewObject(SomeSubType_class_global_ref, its_init_method); // <-- local ref created
      fill_obj(child_obj, *it);
      env->CallBooleanMethod(list_obj, add_method, child_obj);
      env->DeleteLocalRef(child_obj); // <-- local ref released
   }

   env->SetObjectField(obj, field_id, list_obj);
   env->DeleteLocalRef(list_obj); // <-- local ref released
}

或者,您可以改用 (Push|Pop)LocalFrame(),这样 JNI 可以跟踪您创建的所有本地引用,然后一次性为您释放它们,例如:

void some_fun()
{
    // env is a JNIEnv*, cls (and cls2) a jclass, and init (and mid) a jmethodID

    env->PushLocalFrame(1);

    jobject obj = env->NewObject(cls, init);
    fill_obj(obj, cpp_data);
    env->callStaticVoidMethod(cls2, mid, obj);

    env->PopLocalFrame(NULL);
}

void fill_obj(jobject obj, SomeType const& cpp_data)
{
   std::vector<SomeSubType> const& v = cpp_data.inner_data;

   env->PushLocalFrame(1+v.size());

   jobject list_obj = env->NewObject(array_list_class_global_ref, its_init_method);

   for (auto it = v.begin(); it != v.end(); ++it) {
      jobject child_obj = env->NewObject(SomeSubType_class_global_ref, its_init_method);
      fill_obj(child_obj, *it);
      env->CallBooleanMethod(list_obj, add_method, child_obj);
   }

   env->SetObjectField(obj, field_id, list_obj);

   env->PopLocalFrame(NULL);
}

关于java - 使用 JNI : are we releasing objects property? 内存泄漏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70944859/

相关文章:

c++ - NULL检查qt中的整数

eclipse - 当 Eclipse 插件使用 JNI 时,如何将 JNI 控制台输出重定向到 Eclipse 控制台 View ?

java - Unity 中的回调监听器 - 如何从 Android 中的 UnityPlayerActivity 调用脚本文件方法

Linux,取消阻塞read()

java - OAuth中获取的RequestToken的有效性如何?

java - 如何从给定的两个字符串数组形成第三个java数组

java - JSP页面显示数据时出现java.lang.NumberFormatException

java - 使用凯撒密码和给定方法进行加密..Java

c++ - 如何定义指向函数指针的指针以及如何在 C++ 中使用它?

c++ - gluLookAt 没有出现