Утечка памяти где-то между Java и C

#java #c #linux #memory-leaks #java-native-interface

#java #c #linux #утечки памяти #java-native-interface

Вопрос:

У меня есть Java-приложение, которое использует библиотеку C для получения кадров с камеры. Что я делаю, так это сохраняю данные фрейма в библиотеке c и передаю метаданные в Java. Затем из Java он выполняет вызов через JNI для получения последних данных кадра этой камеры.

Проблема, с которой я сталкиваюсь, заключается в том, что в одной из этих функций у меня, похоже, утечка памяти. После запуска обработки она увеличивается до нескольких ГБ за считанные секунды. Однако, если я запускаю библиотеку C для независимого запуска (в основном точно такой же код, только без компонента JNI), утечки памяти нет.

Вот две функции C:

 JNIEXPORT jbyteArray JNICALL
Java_com_c_C_1Wrapper_nGet_1Image (JNIEnv *env, jobject jobj, jint i_) {
    _jobj = jobj;
    jint rs = (*env)->GetJavaVM(env, amp;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, amp;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);
}
  

Следует отметить одну вещь: объект *new_frame освобождается от вызывающего метода.

Сторона Java проста:

 @Override
public void ImagePreview(String metaData) {

    // parse metaData

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

    // do something with data
}
  

Я несколько раз переставлял логику, но безуспешно. Что бы я ни делал, всякий раз, когда задействована часть JNI, происходит серьезная утечка памяти.

Комментарии:

1. Вы пробовали использовать инструмент для поиска утечек, такой как AddressSanitizer?

2. У меня нет, но я должен упомянуть, что утечка памяти не является большой проблемой в моей среде разработки с 32 ГБ памяти. Проблема действительно очевидна (и является проблемой) на меньшем ARM-устройстве с гораздо меньшим объемом памяти, и похоже, что AddressSanitizer не поддерживает ARM.

3. Хорошая особенность таких инструментов в том, что они обнаруживают утечки, когда они происходят, а не только тогда, когда утечки становятся заметными для пользователя. Поэтому используйте инструмент на любой системе, которая у вас есть, которая его поддерживает.

Ответ №1:

Это не утечка памяти, вы просто выделяете память быстрее, чем сборщик мусора Java может ее освободить. Предполагая 1080p @ 60 с 16 бит / с, это 250 МБ / с. C может справиться с этим, потому что, скорее malloc всего, вернет вам буфер, который вы только free что создали, если размеры равны.

Вы должны прекратить вызывать NewByteArray для каждого вызова Get_Image . Вместо этого сохраните пул byte[] объектов фиксированного размера и измените Get_Image его, чтобы использовать буфер из пула. Вам также нужно будет вернуть буфер в пул, когда ваш Java-код будет выполнен с ним. В зависимости от того, что ваш Java-код делает с изображением, вы также можете исследовать, используя ByteBuffer вместо этого direct s: вы можете напрямую записывать в них из C вместо вызова memcpy .

Это ограничит ваш объем памяти до вашего количества буферов. Имейте в виду возможность того, что при вызове Get_Image буферы в настоящее время недоступны, и определите стратегию борьбы с этим.

Комментарии:

1. Я действительно нашел, откуда шло увеличение памяти. Вы частично правы в том, что что-то увеличивается быстрее, чем оно может быть обработано, но этого не было в этом разделе кода. Я действительно в какой-то степени реализовал ваше предложение, но потом подумал, что имею дело с утечкой памяти, и отказался от этой идеи, пока не выяснил проблему. Спасибо за предложение.