ByteBuffer не освобождает память - PullRequest
9 голосов
/ 21 февраля 2011

На Android прямой ByteBuffer, кажется, никогда не освобождает свою память, даже при вызове System.gc ().

Пример: выполнение

Log.v("?", Long.toString(Debug.getNativeHeapAllocatedSize()));
ByteBuffer buffer = allocateDirect(LARGE_NUMBER);
buffer=null;
System.gc();
Log.v("?", Long.toString(Debug.getNativeHeapAllocatedSize()));

дает два числа в журнале, второе по крайней мере на LARGE_NUMBER больше первого.

Как мне избавиться от этогоутечка?


Добавлено:

Следуя предложению Грегори обрабатывать alloc / free на стороне C ++, я определил

JNIEXPORT jobject JNICALL Java_com_foo_bar_allocNative(JNIEnv* env, jlong size)
    {
    void* buffer = malloc(size);
    jobject directBuffer = env->NewDirectByteBuffer(buffer, size);
    jobject globalRef = env->NewGlobalRef(directBuffer);
    return globalRef;
    }

JNIEXPORT void JNICALL Java_com_foo_bar_freeNative(JNIEnv* env, jobject globalRef)
    {
    void *buffer = env->GetDirectBufferAddress(globalRef);
    free(buffer);
    env->DeleteGlobalRef(globalRef);
    }

Затем я получаю свой ByteBuffer на стороне JAVA с помощью

ByteBuffer myBuf = allocNative(LARGE_NUMBER);

и освобождаю его с помощью

freeNative(myBuf);

К сожалению, хотя он выделяется нормально, он а) по-прежнему сохраняет выделенную памятьсогласно Debug.getNativeHeapAllocatedSize() и б) приводит к ошибке

W/dalvikvm(26733): JNI: DeleteGlobalRef(0x462b05a0) failed to find entry (valid=1)

Теперь я полностью сбит с толку, мне показалось, что я по крайней мере понял сторону C ++ ... Почему free () не возвращает память?И что я делаю не так с DeleteGlobalRef()?

Ответы [ 4 ]

20 голосов
/ 21 февраля 2011

Нет утечки.

ByteBuffer.allocateDirect() выделяет память из собственной кучи / свободного хранилища (думаю, malloc()), которое, в свою очередь, помещается в экземпляр ByteBuffer.

Когда экземпляр ByteBuffer получает сборщик мусора, собственная память освобождается ( в противном случае вы бы потеряли собственную память ).

Вы вызываете System.gc() в надежде, что собственная памятьисправлено немедленно .Однако вызов System.gc() - это всего лишь запрос , который объясняет, почему ваш второй оператор журнала не сообщает вам, что память была освобождена: это потому, что она еще не была!

В вашей ситуацииочевидно, в куче Java достаточно свободной памяти, и сборщик мусора решает ничего не делать: вследствие этого недоступные экземпляры ByteBuffer еще не собраны, их финализатор не запущен и собственная память не освобождена.

Кроме того, имейте в виду эту ошибку в JVM (хотя не знаю точно, как она применима к Dalvik), когда большое распределение прямых буферов приводит к неисправимым OutOfMemoryError.


Вы прокомментировали управление вещами из JNI.Это действительно возможно, вы можете реализовать следующее:

  1. опубликовать native ByteBuffer allocateNative(long size) точку входа, которая:

    • вызывает void* buffer = malloc(size) для выделения собственной памяти
    • упаковывает вновь выделенный массив в экземпляр ByteBuffer с вызовом (*env)->NewDirectByteBuffer(env, buffer, size);
    • преобразует локальную ссылку ByteBuffer в глобальную с (*env)->NewGlobalRef(env, directBuffer);
  2. опубликует native void disposeNative(ByteBuffer buffer) точку входа, которая:

    • вызывает free() по адресу прямого буфера, возвращаемого *(env)->GetDirectBufferAddress(env, directBuffer);
    • удаляет глобальную ссылку с помощью (*env)->DeleteGlobalRef(env, directBuffer);

Как только вы вызываете disposeNative в буфере, вы не должны использоватьссылка больше, так что это может быть очень подвержено ошибкам.Подумайте, нужен ли вам такой явный контроль над шаблоном распределения.


Забудьте, что я сказал о глобальных ссылках.На самом деле глобальные ссылки - это способ хранения ссылки в собственном коде (как в глобальной переменной), так что дальнейший вызов методов JNI может использовать эту ссылку.Таким образом, вы должны иметь, например:

  • из Java, вызвать собственный метод foo(), который создает глобальную ссылку из локальной ссылки (полученной путем создания объекта с собственной стороны) и сохраняет его в собственной глобальной переменной (как jobject)
  • один раз назад, снова из Java, вызовите собственный метод bar(), который получает jobject, сохраненный foo() и далее обрабатывает его
  • наконец, все еще из Java, последний вызов нативного baz() удаляет глобальную ссылку

Извините запутаница.

1 голос
/ 27 марта 2012

Я использовал решение TurqMage, пока не протестировал его на эмуляторе Android 4.0.3 (Ice Cream Sandwich). По некоторым причинам вызов DeleteGlobalRef завершается неудачно с предупреждением jni: JNI WARNING: DeleteGlobalRef для неглобального 0x41301ea8 (type = 1), за которым следует ошибка сегментации.

Я убрал вызовы для создания NewGlobalRef и DeleteGlobalRef (см. Ниже), и он, кажется, работает нормально на эмуляторе Android 4.0.3. Как оказалось, я использую только созданный байтовый буфер на Java сторона, которая в любом случае должна содержать ссылку на java, так что я думаю, что вызов NewGlobalRef () не был необходим в первую очередь ..

JNIEXPORT jobject JNICALL Java_com_foo_allocNativeBuffer(JNIEnv* env, jobject thiz, jlong size)
{
    void* buffer = malloc(size);
    jobject directBuffer = env->NewDirectByteBuffer(buffer, size);
    return directBuffer;
}

JNIEXPORT void JNICALL Java_comfoo_freeNativeBuffer(JNIEnv* env, jobject thiz, jobject bufferRef)
{
    void *buffer = env->GetDirectBufferAddress(bufferRef);

    free(buffer);
}
0 голосов
/ 15 сентября 2015

Используйте отражение для вызова java.nio.DirectByteBuffer.free () .Напоминаю, что Android DVM вдохновлен Apache Harmony, который поддерживает описанный выше метод.

Прямые буферы NIO размещаются в собственной куче, а не в куче Java, управляемой сборкой мусора.Это зависит от разработчика, чтобы освободить их родную память.С OpenJDK и Oracle Java это немного отличается, потому что они пытаются вызвать сборщик мусора, когда создание прямого буфера NIO не удается, но нет гарантии, что это поможет.

NB: Вам придется повозитьсянемного больше, если вы используете asFloatBuffer (), asIntBuffer (), ... потому что только прямой байтовый буфер может быть "освобожден".

0 голосов
/ 15 марта 2011

Не уверен, если ваши последние комментарии старые или какие Kasper.Я сделал следующее ...

JNIEXPORT jobject JNICALL Java_com_foo_allocNativeBuffer(JNIEnv* env, jobject thiz, jlong size)
{
    void* buffer = malloc(size);
    jobject directBuffer = env->NewDirectByteBuffer(buffer, size);
    jobject globalRef = env->NewGlobalRef(directBuffer);

    return globalRef;
}

JNIEXPORT void JNICALL Java_comfoo_freeNativeBuffer(JNIEnv* env, jobject thiz, jobject globalRef)
{
    void *buffer = env->GetDirectBufferAddress(globalRef);

    env->DeleteGlobalRef(globalRef);
    free(buffer);
}

Затем в Java ...

mImageData = (ByteBuffer)allocNativeBuffer( mResX * mResY * mBPP );

и

freeNativeBuffer(mImageData);
mImageData = null;

и все, кажется, работает нормально для меня,Большое спасибо Грегори за эту идею.Ссылка на указанную ошибку в JVM испортилась.

...