使用 JNI 的内存泄漏:我们是否释放对象属性?

Per*_*-lk 2 c++ java java-native-interface

重要的提示:这段代码不是native从 Java 调用的函数。我们的流程是用 C++ 编写的,我们实例化一个 Java VM,并从 C++ 调用 Java 函数,但绝不会反过来:Java 方法从不调用本机函数,但本机函数会实例化 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.
}
Run Code Online (Sandbox Code Playgroud)

其中取决于必须设置为 的字段fill_obj的类型。例如,如果 Java 类包含,将包含,例如,. 所以重载将如下所示:cpp_dataobjclsArrayListcpp_datastd::vectorfill_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);
}
Run Code Online (Sandbox Code Playgroud)

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

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

我们有一个方法,在调用时会创建数千个这样的对象,并且每次调用该方法时,进程在 RAM 中占用的内存(常驻内存)都会增加超过 200 MiB,并且该内存永远不会被释放。

我们添加了一个显式调用DeleteLocalRef但结果是相同的。

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

Rem*_*eau 5

既然你说你的some_fun()函数不是由Java代码调用的,它只是在内部调用JNI的纯C++代码,那么是的,你将需要调用DeleteLocalRef()你持有的不传回的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
}
Run Code Online (Sandbox Code Playgroud)

或者,您可以(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);
}
Run Code Online (Sandbox Code Playgroud)