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?
Note the section Implementing Local References from the JNI specification:
To implement local references, the Java VM creates a registry for each transition of control from Java to a native method. A registry maps nonmovable local references to Java objects, and keeps the objects from being garbage collected. All Java objects passed to the native method (including those that are returned as the results of JNI function calls) are automatically added to the registry. The registry is deleted after the native method returns, allowing all of its entries to be garbage collected.
The term “after the native method returns” means the reversal of the “transition of control from Java to a native method”, in other words, the method declared inside a Java class using the keyword native has been called and returns to the caller.
Once this is understood, it’s also clear how to read the Global and Local References section
Local references are valid for the duration of a native method call, and are automatically freed after the native method returns.
So when you have code that runs for a long time before returning to the Java caller or has no Java caller at all, there is no way around deleting references manually using DeleteLocalRef.
As Remy Lebeau mentioned, you can also use PushLocalFrame and PopLocalFrame to perform bulk destruction of references created between two points.
Mind that the destruction of the reference does not imply the destruction of the referenced object, but just not to prevent the garbage collection of the object. So after calling env -> CallBooleanMethod(list_obj, add_method, child_obj);, you can destroy the child_obj reference, because the object is still referenced by the list you’re referencing via list_obj, so the added object will be kept as long as it is still in use.
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);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With