Skip to content

Memory leak at VectorAdapter<P> because P destructor never called #823

@fearnad52

Description

@fearnad52

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:

But to do that you need to:

  • know the original size of the array which holds at the java object at the capacity field.
  • the P type.

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);
    }
...
};

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions