Search code examples
c++memory-managementmemory-leaksandroid-ndkjava-native-interface

C++ NDK library memory management for jobjectarray return method


// ***** New question. *****

There is a memory leak below when the struct is being passed to a thread. Can not understand why, as the code inside the thread if called directly in the main thread does not leak memory.

class PeopleCounting{

  // Class variables
  Ptr<cv::BackgroundSubtractorMOG2> pMOG2 = cv::createBackgroundSubtractorMOG2(500, 16);
  Mat maskBackgroundSubtracted = Mat(resizeDimension.height, resizeDimension.width, CV_8UC1);

  // Thread creation code below, code called from main.

    //Create thread
    pthread_t threads;
    pthread_attr_t attr;
    void *status;

    // Initialize and set thread joinable
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

    // Creating thread data and initializing it
    BackgroundSubstractionThreadData threadData = {CamImage, maskBackgroundSubtracted, pMOG2};
    int rc;
    rc = pthread_create(&threads, NULL, performBackgroundSubstraction, (void *)&threadData);
    if (rc)
    {
        __android_log_print(ANDROID_LOG_ERROR, APPNAME, "Error: peopleCountingMainMono unable to create thread  - %d",rc);
    }
    // free attribute and wait for the other threads
    pthread_attr_destroy(&attr);

    // ************** Do something else in main thread **************

    // Join thread i.e. wait till completion of thread
    rc = pthread_join(threads, &status);
    if (rc)
    {
        __android_log_print(ANDROID_LOG_ERROR, APPNAME, "Error: peopleCountingMainMono unable to join - %d",rc);
    }

    // Using class variable **maskBackgroundSubtracted** and **pMOG2** for later use. **CamImage** (opencv mat) usually gets released automatically in general due to smart pointer implementation, not sure if it is the source of leak

}

// Note: Outside class
void *performBackgroundSubstraction(void *threadarg)
  {
  struct BackgroundSubstractionThreadData *my_data;
  my_data = (struct BackgroundSubstractionThreadData *)threadarg;

  Mat fgMask;
  my_data->pMOG2F->apply(my_data->leftCamImage, fgMask, 0.002);

  morphologyEx(fgMask, fgMask, MORPH_OPEN, getStructuringElement(MORPH_RECT, Size(3, 3)),Point(-1,-1),1);
  morphologyEx(fgMask, fgMask, MORPH_CLOSE, getStructuringElement(MORPH_RECT, Size(11, 11)),Point(-1,-1),1);
  threshold(fgMask, my_data->dst, 128, 255, THRESH_BINARY);
  pthread_exit(NULL);
  };

// ***** End of question ****

I have a NDK library having a JNI function which returns jobjectArray

In below code, I am using a static global jPeopleCountArray which is filled with jobject via a loop and returned to Java calling method. This JNI function is called again and again via a loop from my Java code but only one instance at a time, thus allowing for a global return object. I perform memory cleanup at the end of library use by looping through the jobject array and deleting local ref of jobjects and finally deleting global ref of jPeopleCountArray. The memory cleanup is performed only at the very end as the iterative use (but only single instance) allows reuse of return object.

The question is when I am assigning the global jobjectArray through NewObjectArray. Does the all jobjects previously held inside the jobjectArray due to the previous call gets freed from memory?

class PeopleCounting{
  public:
    static inline jobjectArray jPeopleCountArray = NULL;
    static inline JNI_PEOPLECOUNT * jniPeopleCount = NULL;
  // .... Rest of Code ...
}


// JNI function
    PeopleCounting *obj = (PeopleCounting *) hEngineHandle;

    obj->LoadJniPeopleCount(env);
    Mat *pMatCGray = (Mat *) addrCamGray;

    vector<PeopleSegment> peopleCountingFromContourRes = obj->peopleCountingMainMono(
            *pMatCGray);

    // ******** IMPORTANT BELOW *********        
    obj->jPeopleCountArray = env->NewObjectArray(peopleCountingFromContourRes.size(),
                                            obj->jniPeopleCount->cls, NULL);
    for (size_t i = 0; i < peopleCountingFromContourRes.size(); i++) {
        jobject jPeopleCount = env->NewObject(obj->jniPeopleCount->cls,
                                              obj->jniPeopleCount->constructortorID);
        obj->FillPeopleCountValuesToJni(env, jPeopleCount, peopleCountingFromContourRes[i]);
        env->SetObjectArrayElement(obj->jPeopleCountArray, i, jPeopleCount);
    }
    return obj->jPeopleCountArray;



// Memory cleanup at the end of library use.
PeopleCounting *obj = (PeopleCounting *) hEngineHandle;
if (obj->jPeopleCountArray != NULL){
    __android_log_print(ANDROID_LOG_VERBOSE, APPNAME,
                        "Freeing memory of jobject array");
    //https://www.ibm.com/developerworks/library/j-jni/index.html
    int size = env->GetArrayLength(obj->jPeopleCountArray);
    for(int i = 0; i < size; i++)
    {
        jobject row = env->GetObjectArrayElement(obj->jPeopleCountArray, i);
        if(env->ExceptionOccurred()) {
            break;
        }
        env->DeleteLocalRef(row);
    }
    env->DeleteGlobalRef(obj->jPeopleCountArray);
}

delete (PeopleCounting *)(hEngineHandle);

Solution

  • Your code could exhaust the very limited local references table (its size is implementation-dependent, but could be as low as 256).

    You may delete the local reference to jPeopleCount inside the loop where it's created, right after SetObjectArrayElement(…, jPeopleCount). On the other hand, all these local references will be released automatically after the JNI function returns obj->jPeopleCountArray.

    Similarly, the loop that deletes local references to elements of obj->jPeopleCountArray is redundant. There exist no local references to deal with before you create these with GetObjectArrayElement().

    This demonstrates the difference of behavior between local references and global references. You don't need to create global references to each element of a jobjectArray. But if you were storing the jPeopleCount objects in a C++ collection (e.g. array), you would need global references for each. In this case, the cleanup code would loop over the collection and release these global references, similar to your code.