ByteBuffer不释放内存

Kas*_*ers 9 java-native-interface android memory-leaks 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()));
Run Code Online (Sandbox Code Playgroud)

在日志中给出两个数字,第二个数字至少比第一个大LARGE_NUMBER.

我如何摆脱这种泄漏?


添加:

按照Gregory的建议来处理C++方面的alloc/free,然后我定义了

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);
    }
Run Code Online (Sandbox Code Playgroud)

然后我在JAVA端获得我的ByteBuffer

ByteBuffer myBuf = allocNative(LARGE_NUMBER);
Run Code Online (Sandbox Code Playgroud)

并释放它

freeNative(myBuf);
Run Code Online (Sandbox Code Playgroud)

不幸的是,虽然它确实分配好了,但它仍然保持根据Debug.getNativeHeapAllocatedSize()和b)分配的内存导致错误

W/dalvikvm(26733): JNI: DeleteGlobalRef(0x462b05a0) failed to find entry (valid=1)
Run Code Online (Sandbox Code Playgroud)

我现在彻底搞糊涂了,我以为我至少理解了C++方面的事情......为什么free()没有返回内存?我做错了DeleteGlobalRef()什么?

Gre*_*osz 21

没有泄漏.

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(),该方法从本地引用(通过从本机端创建对象获得)创建全局引用,并将其存储在本机全局变量中(作为a jobject)
  • 一旦回来,再次从Java中调用本机方法bar(),该方法获取jobject存储foo()并进一步处理它
  • 最后,仍然来自Java,对native的最后一次调用baz()删除了全局引用

对困惑感到抱歉.