You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)

我有两个问题:

  1. 为什么DirectByteBuffer会将数据存储在JVM堆中?我原本以为HeapByteBuffer用于堆存储,DirectByteBuffer用于本地内存存储。
  2. 如果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,提问作者Денис Рязанцев

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 12:04:58