Search code examples
javaclinuxmemory-leaksjava-native-interface

Memory leak somewhere between Java and C


I have a java application that uses a C library to acquire frames from a camera. What I'm doing is storing the frame data in a c library and passing the meta data to Java. Then from Java it makes a call through JNI for that camera's latest frame data.

The problem that I'm having is that in one of these functions I seem to have a memory leak. After the processing starts it skyrockets up to several GB in a matter of seconds. However, if I run the C library to run independently (basically the exact same code, just without the JNI component) there's no memory leak.

Here's are the two C functions:

JNIEXPORT jbyteArray JNICALL
Java_com_c_C_1Wrapper_nGet_1Image (JNIEnv *env, jobject jobj, jint i_) {
    _jobj = jobj;
    jint rs = (*env)->GetJavaVM(env, &jvm);
    assert (rs == JNI_OK);

    jbyteArray img_data;
    int cam_id = i_;

    if (!frame_data[cam_id].lock) {
        frame_data[cam_id].lock = TRUE;
        img_data = (*env)->NewByteArray(env, frame_data[cam_id].data_size);
        (*env)->SetByteArrayRegion(env, img_data, 0,
                                   frame_data[cam_id].data_size,
                                   (jbyte*)frame_data[cam_id].img);
        frame_data[cam_id].lock = FALSE;
    }

    return img_data;
}


void
send_frame_data(guint camera_id, gint data_size, void *new_frame,
                gint width, gint height, const char *meta_data) {
    JNIEnv *env;
    jint rs = (*jvm)->AttachCurrentThread(jvm, &env, NULL);
    assert (rs == JNI_OK);

    if (!frame_data[camera_id].lock) {
        frame_data[camera_id].lock = TRUE;

        free(frame_data[camera_id].img);
        frame_data[camera_id].img = malloc(data_size);
        memcpy(frame_data[camera_id].img, new_frame, data_size);

        frame_data[camera_id].width = width;
        frame_data[camera_id].height = height;
        frame_data[camera_id].data_size = data_size;
        frame_data[camera_id].lock = FALSE;
    }

    jbyteArray jMetaData = (*env)->NewByteArray(env, strlen(meta_data));
    (*env)->SetByteArrayRegion(env, jMetaData, 0, strlen(meta_data), meta_data);
    (*env)->CallStaticVoidMethod(env, jSentryCore_class, jImagePreview, jMetaData);
    (*env)->DeleteLocalRef(env, jMetaData);
}

One thing to note, the *new_frame object is freed from the calling method.

The Java side is simple:

@Override
public void ImagePreview(String metaData) {

    // parse metaData

    byte[] image = C_Wrapper.Get_Image(cameraId);

    // do something with data
}

I've rearranged the logic several times with no luck. No matter what I do, whenever the JNI portion is in play, there's a severe memory leak.


Solution

  • It's not a memory leak, you're just allocating memory faster than the Java garbage collector can free it. Assuming 1080p@60 with 16bpp, that is 250 MB/s. C can handle it because very probably malloc will give you back the buffer you just freed if the sizes are equal.

    You should stop calling NewByteArray for every invocation of Get_Image. Instead, keep a fixed-size pool of byte[] objects around and change Get_Image to take a buffer from the pool. You will also need to return the buffer to the pool when your Java code is done with it. Depending on what your Java code does with the image, you could also investigate using direct ByteBuffers instead: you can write directly to these from C instead of calling memcpy.

    That will cap your memory roofline to your number of buffers. Keep in mind the possibility that no buffers are currently available when you call Get_Image and figure out a strategy for dealing with it.