-
-
Notifications
You must be signed in to change notification settings - Fork 596
Description
Hello! I'm investigating a memory leak at generated code caused by the VectorAdapter<P> template.
In my case, the memory leak is several megabytes per second, so it visible. Heap and others JVM memory areas remains the same size. Valgrind reports memory leak at the JNI code.
The root cause is:
Allocated raw void* memory area at operator VectorAdapter<P>::P*() will be freed at VectorAdapter::deallocate(void*), but constructed-in-place P objects by std::uninitialized_copy never destructed, which cause a memory leaks at some situations.
To achive that, P object should contain some dynamically allocated object (std::vector<..> in my case).
Example:
// Some C++ API interface which I want to use
class SomeClass
{
...
public:
// Generated by JavaCPP: public native @StdVector LeakedClass getResults();
std::vector<LeakedClass> getResults();
};
// Class which destructor never called
class LeakedClass
{
public:
// Non-default constructor
LeakedClass(const uint64_t a, const uint64_t b) : a(a), b(b) {};
// Default destructor
virtual ~LeakedClass() = default;
// Objects which will be automatically destroyed
uint64_t a;
uint64_t b;
// It will cause the memory leak if non-empty
std::vector<SomeStruct> someStructs;
};
struct SomeStruct
{
int k;
float fd;
}Generated JavaCPP JNI code:
JNIEXPORT jobject JNICALL Java_com_example_getResults(JNIEnv* env, jobject obj) {
SomeClass* ptr = (SomeClass*)jlong_to_ptr(env->GetLongField(obj, JavaCPP_addressFID));
if (ptr == NULL) {
env->ThrowNew(JavaCPP_getClass(env, 9), "This pointer address is NULL.");
return 0;
}
jlong position = env->GetLongField(obj, JavaCPP_positionFID);
ptr += position;
jobject rarg = NULL;
LeakedClass* rptr;
jthrowable exc = NULL;
try {
VectorAdapter< LeakedClass > radapter(ptr->getResults());
rptr = radapter;
jlong rcapacity = (jlong)radapter.size;
void* rowner = radapter.owner;
void (*deallocator)(void*) = rowner != NULL ? &VectorAdapter< LeakedClass >::deallocate : 0;
if (rptr != NULL) {
rarg = JavaCPP_createPointer(env, 28);
if (rarg != NULL) {
JavaCPP_initPointer(env, rarg, rptr, rcapacity, rowner, deallocator);
}
}
} catch (...) {
exc = JavaCPP_handleException(env, 8);
}
if (exc != NULL) {
env->Throw(exc);
}
return rarg;
}Thus, the recommended way to destroy objects is to call their destructors with std::destroy (for example). See:
- http://en.cppreference.com/w/cpp/memory/uninitialized_copy.html
- http://en.cppreference.com/w/cpp/memory/destroy.html
But to do that you need to:
- know the original size of the array which holds at the java object at the
capacityfield. - the
Ptype.
There is any workarounds such as custom deallocators or anything else? Or any way to pass to deallocator function additional parameter?
#include <vector>
template<typename P, typename T = P, typename A = std::allocator<T> > class JavaCPP_hidden VectorAdapter {
public:
...
// something like that
static void deallocate(void* owner, typename std::vector<T,A>::size_type size) {
// aware brain-compiled
std::destroy((P*)owner, (P*)(owner) + size);
operator delete(owner);
}
...
};