'Memory leaks using JNI: are we releasing objects property?
IMPORTANT NOTE: This snippet of code is not a native function called from Java. Our process is written in C++, we instantiate a Java VM, and we call Java functions from C++, but never the other way around: Java methods never calls native functions, but native functions instantiate Java objects and call Java functions on them.
We are doing something like this:
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.
}
Where fill_obj depends on the kind of cpp_data that must be set as fields of obj. For example, if the Java class cls contains an ArrayList, cpp_data will contain, for example, a std::vector. So the fill_obj overload will look like this:
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);
}
According to our understanding on the JNI docs, that code should not have any memory leaks since local objects are destroyed when the native method ends. So, when the first fill_obj ends, at C++ side there's no more references to list_obj or any of its childs, and when some_fun ends, any reference of obj at C++ side is gone as well.
My understanding is that jobject contains some sort of reference counter and when that reference counter reachs 0, then there's no more references to the Java object at C++ side. If there's no more references to the object at Java side either, then the Java's garbage collector is free to release the resources occupied by the object.
We have a method that, when called, creates thousand of those objects, and each time that method is called, the memory that the process occupies in RAM (the resident memory) grows by more than 200 MiB, and that memory is never released.
We have added an explicit call to DeleteLocalRef but the result is the same.
What is going on? Are we doing something wrong?
Solution 1:[1]
Since you say your some_fun() function is not being called by Java code, it is just pure C++ code that is internally calling into JNI, then yes, you will need to call DeleteLocalRef() on any local references you hold to Java objects that you don't pass back to Java. You will need to do this inside of your for loop as well.
Try this:
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
}
Alternatively, you can use (Push|Pop)LocalFrame() instead, so that JNI can keep track of all the local references you create and then release them for you in one go, eg:
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);
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
