Android Bitmap在虚拟内存与物理内存空间分配特性
摘要:Android Bitmap内存分配需要虚拟地址空间连续,但通常不要求物理内存连续。普通Bitmap(如ARGB_8888)的像素数据来自Java堆或native堆,只需虚拟地址连续,物理页可分散。硬件相关Buffer(如GraphicBuffer)才可能需要物理连续内存。LruCache仅管理引用不影响内存连续性。内存不足时,可能因虚拟地址空间碎片化或进程内存限制导致OOM,与CMA通常无关。不同Android版本对Bitmap内存位置的策略差异会影响OOM表现。
Bitmap 的"像素内存"通常需要在进程虚拟地址空间里是一段连续的内存;但不一定要求在物理内存上连续。
这是理解 Bitmap、CMA、OOM 的关键区别。
1. 先区分两个"连续"
内存连续有两种含义:
1)虚拟地址连续
也就是在 App 进程看到的地址空间里是一段连续区域。
比如 Bitmap 像素区可能像这样:
0x70000000 ~ 0x70A00000
长度约 10MB。
对于普通 Bitmap 像素 buffer 来说,通常需要一段连续的 虚拟地址空间。
2)物理地址连续
也就是 RAM 里面真实的物理页也必须挨在一起:
物理页 A、A+1、A+2、A+3...
这个要求更高,通常只有 DMA、Camera、Video、Display、部分 GraphicBuffer/HardwareBuffer 才需要。
普通 Java/Native 分配一般不要求物理连续。
2. 普通 Bitmap 是否需要物理连续?
一般不需要。
比如普通软件 Bitmap:
Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
BitmapFactory.decodeResource(...)
BitmapFactory.decodeFile(...)
它的像素内存通常来自:
-
Java heap;
-
native heap;
-
malloc;
-
mmap;
-
ashmem;
-
Android Runtime/Skia 管理的 native allocation。
这些分配通常只需要 虚拟地址连续,底层可以映射到离散的物理页。
也就是说,10MB Bitmap 在虚拟地址上可能连续,但物理内存上可能是很多分散的 4KB page:
虚拟地址连续:
[ page ][ page ][ page ][ page ][ page ] ...
物理地址可能分散:
PFN 1000, PFN 3500, PFN 900, PFN 12000 ...
所以普通 Bitmap 通常不依赖 CmaFree。
3. 那为什么 Bitmap 还可能 OOM?
即使不需要物理连续,也可能失败,因为它需要满足这些条件:
1)进程可用内存够不够
例如 ARGB_8888:
宽 × 高 × 4 bytes
一张 10MB Bitmap 大约就是:
10 × 1024 × 1024 bytes
如果 App heap 或 native heap 空间不足,就可能 OOM。
2)虚拟地址空间是否够连续
在 64 位 App 中通常问题不大。
但在 32 位进程中,虚拟地址空间有限,且可能碎片化。
这时候即使系统总内存还有,也可能找不到一段足够大的连续虚拟地址空间,导致分配失败。
不过 10MB 通常不算特别大,除非进程内存已经非常碎片化或接近上限。
3)Android 版本差异
Bitmap 像素内存位置历史上有变化:
-
早期 Android:Bitmap 像素可能在 native heap;
-
Android 3.0 ~ 7.x:更多计入 Dalvik/ART heap;
-
Android 8.0 以后:Bitmap pixel data 又更多放到 native heap,但会通过 native allocation 影响 GC。
所以 OOM 类型可能不同,但本质还是分配内存失败或触发进程限制。
4. 硬件 Bitmap 呢?
如果是:
Bitmap.Config.HARDWARE
或者图形相关 buffer:
-
HardwareBuffer
-
GraphicBuffer
-
Surface buffer
-
Texture
-
Camera preview buffer
-
Video decoder buffer
那就不同了。
这些可能由 GPU/display/camera 等硬件使用,底层可能走:
-
dma-buf
-
ION
-
DMA heap
-
gralloc
-
CMA
-
carveout memory
这类 buffer 有时会要求:
-
物理连续;
-
或 IOMMU 可映射;
-
或特定内存区域;
-
或满足 stride/alignment;
-
或符合硬件 DMA 约束。
这种场景下,CmaFree=0 就可能相关。
5. 放在 LruCache 里的 Bitmap 连续性有变化吗?
不会。
LruCache 只是保存 Bitmap 对象引用。
它不会改变 Bitmap 像素内存的分配方式。
也就是说:
memoryCache.put(key, bitmap);
只是让 cache 强引用这个 Bitmap。
Bitmap 的像素内存早在创建/解码时就已经分配好了。
LruCache 影响的是:
-
是否继续持有引用;
-
何时按 LRU 淘汰;
-
是否触发 GC 后释放。
它不影响底层像素 buffer 是不是物理连续。
6. 举个例子
假设创建一张普通 ARGB_8888 Bitmap:
Bitmap bitmap = Bitmap.createBitmap(1600, 1600, Bitmap.Config.ARGB_8888);
占用约:
1600 × 1600 × 4 = 10,240,000 bytes ≈ 9.77MB
它通常需要:
一段约 10MB 的连续虚拟地址空间
但物理页可以分散。
然后放入:
memoryCache.put("image", bitmap);
此时 LruCache 只是引用它。
如果 cache 超过 maxSize,会淘汰旧对象;如果没人引用旧 Bitmap,之后 GC/native free 才可能释放对应内存。
7. 回到问题
Bitmap 在内存空间里面是连续分配的吗?
结论是:
普通 Bitmap 的像素数据通常在虚拟地址空间中是连续的一块 buffer,但不要求物理内存连续。
所以:
-
普通 Bitmap:一般与
CmaFree没有直接关系; -
硬件 Bitmap / GraphicBuffer / Camera buffer:可能需要特殊连续内存或 IOMMU 映射,可能与 CMA 有关系;
-
LruCache:只负责持有引用和按容量淘汰,不决定内存连续性。
一句话概括:
Bitmap 创建时需要能分配出一块足够大的像素 buffer;对普通 Bitmap 来说,这块 buffer 主要是虚拟地址连续,不是物理地址连续。