ByteBuffer 不释放内存 [英] ByteBuffer not releasing memory

查看:59
本文介绍了ByteBuffer 不释放内存的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在 Android 上,一个直接的 ByteBuffer 似乎永远不会释放它的内存,即使在调用 System.gc() 时也不会.

On Android, a direct ByteBuffer does not ever seem to release its memory, not even when calling 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.

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

我如何摆脱这种泄漏?

添加:

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

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

然后我在 JAVA 端获取我的 ByteBuffer

I then get my ByteBuffer on the JAVA side with

ByteBuffer myBuf = allocNative(LARGE_NUMBER);

并释放它

freeNative(myBuf);

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

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)

我现在彻底糊涂了,我想我至少了解了 C++ 方面的东西...为什么 free() 不返回内存?我对 DeleteGlobalRef() 做错了什么?

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()?

推荐答案

没有泄漏.

ByteBuffer.allocateDirect() 从本机堆/空闲存储(想想 malloc())分配内存,然后将其包装到 ByteBuffer 实例.

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

ByteBuffer 实例被垃圾回收时,本机内存被回收(否则你会泄漏本机内存).

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

您正在调用 System.gc(),希望能够立即回收本机内存.但是,调用 System.gc() 只是一个请求,这解释了为什么您的第二条日志语句没有告诉您内存已被释放:这是因为它还没有!

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!

在您的情况下,Java 堆中显然有足够的可用内存,垃圾收集器决定什么都不做:因此,尚未收集无法访问的 ByteBuffer 实例,它们的终结器未运行并且未释放本机内存.

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.

另外,请记住 JVM 中的这个 bug(不确定它如何应用于Dalvik),其中直接缓冲区的大量分配导致不可恢复的OutOfMemoryError.

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.

您评论了从 JNI 执行控制操作.这实际上是可能的,您可以实现以下内容:

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

  1. 发布一个 native ByteBuffer allocationNative(long size) 入口点:

  • 调用void* buffer = malloc(size)来分配本地内存
  • 通过调用(*env)->NewDirectByteBuffer(env, buffer, size);
  • 将新分配的数组包装到ByteBuffer实例中
  • 使用(*env)->NewGlobalRef(env, directBuffer);ByteBuffer本地引用转换为全局引用莉>
  • 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);

发布一个native void disposeNative(ByteBuffer buffer)入口点:

  • *(env)->GetDirectBufferAddress(env, directBuffer);
  • 返回的直接缓冲区地址上调用free()
  • 使用 (*env)->DeleteGlobalRef(env, directBuffer);
  • 删除全局引用

一旦您在缓冲区上调用 disposeNative,您就不应再使用该引用,因此很容易出错.重新考虑您是否真的需要对分配模式进行这种显式控制.

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.

忘记我所说的全局引用.实际上,全局引用是一种在本机代码中存储引用的方法(如在全局变量中),以便进一步调用 JNI 方法可以使用该引用.例如,您可以:

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

抱歉造成混乱.

这篇关于ByteBuffer 不释放内存的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆