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?