重要说明:这段代码不是从 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/