ByteBuffer не освобождает память

На 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() и b) приводит к ошибке

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

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

Нет утечки.

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

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

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

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

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


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

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

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

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

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


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

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

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

Я использовал решение TurqMage, пока не проверил его на эмуляторе Android 4.0.3 (Ice Cream Sandwich). По какой-либо причине вызов DeleteGlobalRef завершается с предупреждением jni: JNI WARNING: DeleteGlobalRef на неглобальном 0x41301ea8 (тип = 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); } 

Не уверен, что ваши последние комментарии старые или что 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; 

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

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

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

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