Android 的内存管理机制是一个多层次的复杂系统,旨在高效利用有限的物理内存(RAM),在保证前台应用流畅运行的同时,尽可能在后台保留更多应用以提高启动速度(多任务)。
它的核心机制结合了 Linux 内核的基础功能和 Android 特有的优化策略。
1 基于 Linux 内核的内存管理 (基础层)
1.1 分页与虚拟内存
与所有现代操作系统一样,Android 使用虚拟内存系统。每个进程运行在自己的私有虚拟地址空间中。
内核和 MMU 负责将虚拟地址映射到物理 RAM 或存储上的交换空间(尽管 Android 对传统交换的使用非常谨慎)。
1.2 内存分配
应用通过 malloc、new 等标准库调用请求内存。内核负责分配物理页帧。
1.3 内存不足终止机制
当系统内存严重不足时,Linux 内核的 OOM Killer 会被触发,根据某种策略选择并终止进程以释放内存。
2 Android 特有的优化层 (用户空间)
2.1 LMK(Low Memory Killer)
这个是 Android 对标准 Linux OOM Killer 的关键增强和替代。它更主动和可预测。
下面是LMK的一些机制:
- 内存压力等级: LMK 定义了一系列不断升高的内存阈值。
- 进程分类: 每个进程都有一个 oom_adj_score 或 oom_score_adj 值(范围通常从 -1000 到 1000+),数值越大表示优先级越低、越容易被杀。这个值由 ActivityManagerService 根据进程状态动态调整。
- 按优先级终止: 当可用内存低于某个阈值时,LMK 会查找当前阈值对应的 oom_adj 级别,并终止该级别或更高级别(数值更大,即优先级更低)的进程,直到可用内存回升到安全水平。
下面是一些非系统应用常见的oom_adj级别:
调整级别常量 | 数值范围 | 描述 |
---|---|---|
FOREGROUND_APP_ADJ | 0 | 前台应用(用户正在交互)。 |
VISIBLE_APP_ADJ | 100 | 可见应用(如弹出对话框或小部件)。 |
PERCEPTIBLE_APP_ADJ | 200 | 可感知应用(如后台播放音乐)。 |
BACKUP_APP_ADJ | 300 | 正在执行备份操作的进程。 |
HEAVY_WEIGHT_APP_ADJ | 400 | 重量级后台应用(较少使用)。 |
SERVICE_ADJ | 500 | 服务进程(后台服务)。 |
HOME_APP_ADJ | 600 | Home 应用(Launcher)。 |
PREVIOUS_APP_ADJ | 700 | 上一个应用(通常优先级略高于纯后台)。 |
CACHED_APP_MIN_ADJ | 800-999 | 空进程 / 缓存应用。最先被终止,数值越大越优先被杀。 |
NATIVE_ADJ | 1000+ | 系统原生进程(通常不会被杀)。 |
2.2 应用组件生命周期与内存管理
ActivityManagerService 是核心管理者,负责启动、停止、管理四大组件(Activity, Service, BroadcastReceiver, ContentProvider)的生命周期。
当系统内存不足时,AMS 会首先尝试终止空进程(Cached App。如果还不够,按 oom_adj 优先级从低到高终止包含 Service 或其他组件的后台进程。
最后才会考虑终止可见或前台进程(这是最坏情况)。
2.3 响应内存压力,onTrimMemory() 回调
这是应用响应内存压力的主要方式。系统会根据当前内存压力等级(TRIM_MEMORY_* 常量)调用此方法。
应用应根据等级释放相应资源,下面是几个等级常量:
- TRIM_MEMORY_RUNNING_MODERATE/CRITICAL: 应用正在前台运行,但系统开始感到压力(CRITICAL 表示可能很快被杀其他进程)。
- TRIM_MEMORY_UI_HIDDEN: 应用 UI 刚被隐藏(如按 Home 键),是释放仅 UI 使用资源的好时机。
- TRIM_MEMORY_BACKGROUND/MODERATE/COMPLETE: 应用在后台(LRU 列表位置不同)。COMPLETE 表示进程在列表末尾,可能很快被杀,应尽可能释放资源以争取不被杀或被杀后能快速重建。
正确响应这个回调可以显著降低应用被 LMK 终止的概率。
2.4 垃圾回收(GC)
Android 使用 分代垃圾收集器 ,通常是 ART 运行时中的 Concurrent Mark-Sweep 或其变种。
主要针对 Java/Kotlin 堆内存(对象实例)。
GC 是自动触发的(根据分配速率、堆使用情况等),我们通常不应手动调用 System.gc(),因为 ART 的 GC 策略更智能,手动调用可能打乱其节奏或造成不必要的卡顿。
整体来说,内存泄漏是导致应用内存占用过高甚至 OOM 的主要原因。
2.5 Native 内存管理
native内存还是需要通过 malloc/free、new/delete 手动管理。我们需自行负责分配和释放,否则会导致 Native 内存泄漏。
有个需要注意的点是,Bitmap 像素数据在 Android 8.0 之前分配在 Native 堆(通过 libandroid_runtime.so),之后主要分配在 Java 堆。
2.6 共享内存
Android 的共享内存机制是其高效进程间通信(IPC)和内存管理的核心基础之一,其底层实现结合了 Linux 内核原生机制 和 Android 特有的优化扩展。
2.6.1 基础层:Linux 共享内存机制
Android 基于 Linux 内核,因此继承了 Linux 的共享内存基础能力。
但是,Linux 共享内存的局限性:
缺乏精细的权限控制和生命周期管理。
未针对移动设备的小内存场景优化。
无法与 Android 的 Binder 等机制深度集成。
2.6.2 Android 的核心扩展Ashmem(Anonymous Shared Memory)
Android 在 Linux 基础上引入了 Ashmem,专门为移动场景优化,它的关键特性如下:
- 匿名共享内存,无需依赖文件系统路径或键值,通过文件描述符(fd)传递共享内存。
- 基于 mmap() 的零拷贝,进程通过 mmap() 直接映射同一块物理内存,避免数据复制。
- 动态内存回收(“Unpin” 机制),允许内核在内存不足时回收未被“钉住”(pinned)的内存页(类似交换分区,但更高效)。
- 精细化访问控制,通过 Binder 传递 fd 时,可附加权限限制(如只读)。
2.6.3 Android 的共享内存高级封装
MemoryFile,他的原理是基于 Ashmem 的 Java 封装,内部通过 JNI 调用 ashmem_create_region()。
使用场景:
适合在 Java 层共享较大数据块(如摄像头帧、传感器数据)。
MemoryFile memoryFile = new MemoryFile("my_shm", size);
memoryFile.getOutputStream().write(data); // 写入数据
// 通过 Binder 传递 MemoryFile 的 FileDescriptor
ParcelFileDescriptor pfd = memoryFile.getFileDescriptor();
SharedMemory(Java,API 27+),它替代 MemoryFile,支持更精细的控制(如只读共享)和 AIDL 直接传递。
SharedMemory sharedMem = SharedMemory.create("my_shm", size);
ByteBuffer buffer = sharedMem.mapReadWrite(); // 映射为 ByteBuffer
// 通过 Binder 传递 SharedMemory
bundle.putParcelable("shm", sharedMem);
2.6.4 共享内存的生命周期与同步
共享内存的生命周期管理,基于引用计数,通过文件描述符(fd)的传递和关闭控制内存释放。当所有进程关闭 fd 后,内存由内核回收。
Binder 传递 fd,Android 的 Binder 机制支持传递 fd,接收方会获得一个独立的 fd 指向同一块内存。
而关于共享内存的同步机制,本身是没有同步机制的,需要我们自己处理。
Q&A
需要频繁读写的大块内存,同时需要兼顾高效,比如需要实时读取usb摄像头的数据,有什么方案?
共享内存是个不错的选择。
持续更新中。。。