Android中DirectByteBuffer为何存数据于JVM堆而非本地内存?
Android中DirectByteBuffer内存分配异常问题
问题背景
我原本希望通过ByteBuffer.allocateDirect将数据存储在本地内存中,但实际发现数据被存储在JVM堆中。我通过以下代码验证DirectByteBuffer的数据存储位置:
import android.util.Log import androidx.appcompat.app.AppCompatActivity import java.nio.ByteBuffer class MainActivity : AppCompatActivity() { val tenMb = 10 * 1024 * 1024 val byteBuffer = ByteBuffer.allocateDirect(tenMb) init { if (byteBuffer.hasArray()) { Log.i(TAG, "Byte buffer has heap array ${byteBuffer.array().size / 1024f / 1024f} MB") } else { Log.i(TAG, "Byte buffer doesn't have heap array") } } companion object { private const val TAG = "BYTE_BUFFER_TEST" } }
生成堆快照后,我看到大小为10MB的字节数组被存储在堆中。此外,由于Android应用的标准堆大小为256MB,我尝试分配300MB以触发OOM,结果成功触发,报错信息如下:
FATAL EXCEPTION: main Process: com.android.myapplication, PID: 14976 java.lang.OutOfMemoryError: Failed to allocate a 314572819 byte allocation with 25165824 free bytes and 253MB until OOM, target footprint 27560064, growth limit 268435456 at dalvik.system.VMRuntime.newNonMovableArray(Native Method) at java.nio.DirectByteBuffer$MemoryRef.<init>(DirectByteBuffer.java:71) at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:265) at net.idrnd.test.myapplication.MainActivity.<init>(MainActivity.kt:10) at java.lang.Class.newInstance(Native Method) at android.app.AppComponentFactory.instantiateActivity(AppComponentFactory.java:95) at androidx.core.app.CoreComponentFactory.instantiateActivity(CoreComponentFactory.java:44) at android.app.Instrumentation.newActivity(Instrumentation.java:1441) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3839) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:4126) at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:108) at android.app.servertransaction.TransactionExecutor.executeNonLifecycleItem(TransactionExecutor.java:195) at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:157) at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:90) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2544) at android.os.Handler.dispatchMessage(Handler.java:107) at android.os.Looper.loopOnce(Looper.java:232) at android.os.Looper.loop(Looper.java:317) at android.app.ActivityThread.main(ActivityThread.java:8501) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:878)
我有两个问题:
- 为什么
DirectByteBuffer会将数据存储在JVM堆中?我原本以为HeapByteBuffer用于堆存储,DirectByteBuffer用于本地内存存储。 - 如果
DirectByteBuffer无法正常工作且我不想使用NDK,该如何将数据存储在本地内存中?
问题解答
1. DirectByteBuffer存入JVM堆的原因
Android的ART虚拟机对DirectByteBuffer的实现与标准JVM存在差异:
- 标准JVM中,
DirectByteBuffer的内存确实分配在堆外本地内存,但ART为优化内存管理,在部分场景下会将DirectByteBuffer的内存分配到非移动堆区——这部分内存属于JVM堆的范畴,不会被GC移动,但会被计入应用堆大小限制。 - 从报错信息中的
dalvik.system.VMRuntime.newNonMovableArray可以看出,此处分配的是不可移动数组,这类内存会被统计到堆快照中,因此你会看到堆里的10MB数组。 - 另外,
hasArray()方法仅判断ByteBuffer是否持有堆数组引用,DirectByteBuffer本身不会返回true,但ART的底层实现让其内存被纳入堆统计。
2. 不使用NDK的本地内存存储方案
如果不想依赖NDK,可以使用Android提供的以下API实现本地内存存储:
方案一:使用MemoryFile
android.os.MemoryFile基于共享内存实现,内存分配在本地内存,不计入应用堆大小限制:
import android.os.MemoryFile // 分配10MB本地内存 val memoryFile = MemoryFile("local-memory", 10 * 1024 * 1024) // 写入数据示例 val writeBuffer = ByteArray(1024).apply { fill(0x1) } memoryFile.writeBytes(writeBuffer, 0, 0, writeBuffer.size) // 读取数据示例 val readBuffer = ByteArray(1024) memoryFile.readBytes(readBuffer, 0, 0, readBuffer.size) // 使用完毕后关闭资源 memoryFile.close()
方案二:使用SharedMemory(API 26+)
android.os.SharedMemory是更现代的共享内存API,支持跨进程共享,同样分配在本地内存:
import android.os.SharedMemory // 分配10MB共享内存 val sharedMemory = SharedMemory.create("shared-local-memory", 10 * 1024 * 1024) // 将内存映射为可读写的ByteBuffer val byteBuffer = sharedMemory.mapReadWrite() // 操作ByteBuffer示例 byteBuffer.putInt(0, 12345) val value = byteBuffer.getInt(0) // 使用完毕后关闭资源 sharedMemory.close()
内容的提问来源于stack exchange,提问作者Денис Рязанцев
相关产品推荐
相关产品推荐

