Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
322 views
in Technique[技术] by (71.8m points)

android - ByteBuffer not releasing memory

On Android, a direct ByteBuffer does not ever seem to release its memory, not even when calling System.gc().

Example: doing

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

gives two numbers in the log, the second one being at least LARGE_NUMBER larger than the first.

How do I get rid of this leak?


Added:

Following the suggestion by Gregory to handle alloc/free on the C++ side, I then defined

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);
    }

I then get my ByteBuffer on the JAVA side with

ByteBuffer myBuf = allocNative(LARGE_NUMBER);

and free it with

freeNative(myBuf);

Unfortunately, while it does allocate fine, it a) still keeps the memory allocated according to Debug.getNativeHeapAllocatedSize() and b) leads to an error

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

I am now thoroughly confused, I thought I at least understood the C++ side of things... Why is free() not returning the memory? And what am I doing wrong with the DeleteGlobalRef()?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

There is no leak.

ByteBuffer.allocateDirect() allocates memory from the native heap / free store (think malloc()) which is in turn wrapped in to a ByteBuffer instance.

When the ByteBuffer instance gets garbage collected, the native memory is reclaimed (otherwise you would leak native memory).

You're calling System.gc() in hope the native memory is reclaimed immediately. However, calling System.gc() is only a request which explains why your second log statement doesn't tell you memory has been released: it's because it hasn't yet!

In your situation, there is apparently enough free memory in the Java heap and the garbage collector decides to do nothing: as a consequence, unreachable ByteBuffer instances are not collected yet, their finalizer is not run and native memory is not released.

Also, keep in mind this bug in the JVM (not sure how it applies to Dalvik though) where heavy allocation of direct buffers leads to unrecoverable OutOfMemoryError.


You commented about doing controlling things from JNI. This is actually possible, you could implement the following:

  1. publish a native ByteBuffer allocateNative(long size) entry point that:

    • calls void* buffer = malloc(size) to allocate native memory
    • wraps the newly allocated array into a ByteBuffer instance with a call to (*env)->NewDirectByteBuffer(env, buffer, size);
    • converts the ByteBuffer local reference to a global one with (*env)->NewGlobalRef(env, directBuffer);
  2. publish a native void disposeNative(ByteBuffer buffer) entry point that:

    • calls free() on the direct buffer address returned by *(env)->GetDirectBufferAddress(env, directBuffer);
    • deletes the global ref with (*env)->DeleteGlobalRef(env, directBuffer);

Once you call disposeNative on the buffer, you're not supposed to use the reference anymore, so it could be very error prone. Reconsider whether you really need such explicit control over the allocation pattern.


Forget what I said about global references. Actually global references are a way to store a reference in native code (like in a global variable) so that a further call to JNI methods can use that reference. So you would have for instance:

  • from Java, call native method foo() which creates a global reference out of a local reference (obtained by creating an object from native side) and stores it in a native global variable (as a jobject)
  • once back, from Java again, call native method bar() which gets the jobject stored by foo() and further processes it
  • finally, still from Java, a last call to native baz() deletes the global reference

Sorry for the confusion.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...