一、内存管理子系统概述
1.1 内存管理子系统的作用
Android Runtime(ART)的内存管理子系统是保障系统和应用稳定运行的关键组件,其核心作用在于高效地分配、回收和管理内存资源。在Android系统中,应用程序的运行、数据的存储与处理都依赖于内存。内存管理子系统需要确保不同应用之间的内存相互隔离,防止内存泄漏和非法访问,同时还要合理分配有限的内存资源,满足多个应用程序同时运行的需求。例如,当用户同时打开社交软件、浏览器和游戏应用时,内存管理子系统要为每个应用分配足够的内存空间,避免因内存不足导致应用崩溃,并且在应用切换或关闭时及时回收内存,以便其他应用使用。
1.2 与其他子系统的关系
内存管理子系统与ART中的其他子系统紧密协作。与垃圾回收子系统配合,共同完成内存的回收工作。垃圾回收子系统负责标记不再使用的对象,而内存管理子系统则回收这些对象占用的内存空间,将其重新纳入可用内存池。与线程管理子系统交互,为线程分配栈内存空间,确保线程的正常执行。同时,在多线程环境下,内存管理子系统还需要处理线程同步访问内存的问题,避免出现数据竞争和不一致的情况。此外,内存管理子系统还与类加载子系统相关,类加载过程中需要为加载的类和对象分配内存,内存管理子系统要提供合适的内存分配策略来满足类加载的需求。
1.3 内存管理的基本目标
内存管理子系统的基本目标包括提高内存利用率、降低内存分配和回收的开销、保证内存访问的安全性和稳定性。提高内存利用率意味着尽可能减少内存碎片,避免内存空间的浪费。通过合理的内存分配和回收算法,将空闲内存块有效地组织起来,以便后续的内存分配请求。降低内存分配和回收的开销,要求内存管理子系统在处理内存请求时,能够快速找到合适的内存块进行分配,并且在回收内存时高效地将其合并到空闲内存池中。保证内存访问的安全性和稳定性则需要防止应用程序越界访问内存、非法修改其他应用的内存数据,以及在内存不足时能够采取合理的策略来避免系统崩溃。
二、内存管理子系统启动前的准备工作
2.1 系统参数与配置的读取
在内存管理子系统启动之前,首先需要读取系统相关的参数与配置信息,这些信息将决定内存管理的具体策略和行为。系统参数包括可用内存大小、内存页大小、内存分配的阈值等。这些参数通常存储在系统配置文件中,如init.rc
或通过系统属性进行设置。
在Android系统中,可以通过property_get
函数来获取系统属性值。例如,获取可用内存大小的属性值:
cpp
#include <sys/system_properties.h>
#include <stdio.h>
int main() {
char prop_value[PROPERTY_VALUE_MAX];
// "ro.totalmem" 为表示总内存的系统属性名
if (property_get("ro.totalmem", prop_value, "")) {
int totalMemory = atoi(prop_value);
printf("Total memory: %d bytes\n", totalMemory);
}
return 0;
}
内存页大小通常是固定的,在Linux系统(Android基于Linux内核)中,常见的内存页大小为4KB。内存管理子系统会根据这些参数来初始化内存管理的数据结构,如内存页表、空闲内存链表等。
2.2 内存管理相关数据结构的初始化
内存管理子系统依赖多种数据结构来实现内存的有效管理,在启动前需要对这些数据结构进行初始化。常见的数据结构包括空闲内存链表、内存页表、内存分配记录等。
空闲内存链表用于记录系统中当前可用的内存块。初始化时,会将系统中未分配的内存空间划分为多个内存块,并将这些内存块按照一定的规则(如大小顺序)链接起来,形成空闲内存链表。
cpp
// 定义内存块结构体
struct MemoryBlock {
size_t size; // 内存块大小
struct MemoryBlock* next; // 指向下一个内存块的指针
};
// 初始化空闲内存链表
struct MemoryBlock* freeMemoryList = nullptr;
void initFreeMemoryList(size_t totalMemory) {
// 创建一个初始的内存块
struct MemoryBlock* initialBlock = new MemoryBlock();
initialBlock->size = totalMemory;
initialBlock->next = nullptr;
freeMemoryList = initialBlock;
}
内存页表用于记录内存页的使用状态,包括该页是否已分配、属于哪个进程等信息。初始化时,会根据内存页大小和总内存大小创建相应大小的内存页表,并将所有页的状态初始化为未分配。
cpp
// 定义内存页表项结构体
struct PageTableEntry {
bool isAllocated; // 是否已分配
int processId; // 所属进程ID(未分配时为-1)
};
// 假设内存页大小为4KB,总内存为1024KB
const int PAGE_SIZE = 4 * 1024;
const int TOTAL_MEMORY = 1024 * 1024;
const int NUM_PAGES = TOTAL_MEMORY / PAGE_SIZE;
PageTableEntry* pageTable = new PageTableEntry[NUM_PAGES];
void initPageTable() {
for (int i = 0; i < NUM_PAGES; ++i) {
pageTable[i].isAllocated = false;
pageTable[i].processId = -1;
}
}
内存分配记录用于跟踪每个进程的内存分配情况,方便内存的回收和统计。初始化时,会创建一个数据结构(如哈希表)来存储每个进程的内存分配信息。
2.3 与底层操作系统接口的连接准备
Android Runtime的内存管理子系统基于底层Linux操作系统提供的内存管理功能。在启动前,需要建立与底层操作系统的接口连接,以便调用相关的系统函数来实现内存的分配、回收和管理。
主要涉及的系统调用包括mmap
、munmap
、brk
等。mmap
用于将文件或设备映射到内存空间,或者分配一块匿名内存;munmap
用于解除内存映射,释放内存空间;brk
用于调整堆内存的大小。
在连接这些接口前,需要包含相应的头文件,并确保程序具备调用这些系统函数的权限。例如:
cpp
#include <sys/mman.h>
#include <unistd.h>
// 使用mmap分配内存示例
void* allocateMemory(size_t size) {
void* ptr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (ptr == MAP_FAILED) {
// 处理分配失败情况
return nullptr;
}
return ptr;
}
// 使用munmap释放内存示例
void freeMemory(void* ptr, size_t size) {
if (munmap(ptr, size) == -1) {
// 处理释放失败情况
}
}
通过这些准备工作,内存管理子系统在启动时能够顺利与底层操作系统进行交互,实现对内存资源的有效管理。
三、内存管理子系统的启动核心流程
3.1 内存管理模块的初始化
内存管理子系统包含多个功能模块,如内存分配模块、内存回收模块、内存保护模块等,在启动时需要依次对这些模块进行初始化。
内存分配模块初始化时,会设置内存分配的策略,常见的策略有首次适应算法、最佳适应算法、最坏适应算法等。以首次适应算法为例,其基本思想是从空闲内存链表的头部开始查找,找到第一个能够满足分配请求大小的内存块进行分配。
cpp
// 首次适应算法分配内存
void* firstFitAllocate(size_t size) {
struct MemoryBlock* current = freeMemoryList;
struct MemoryBlock* prev = nullptr;
while (current!= nullptr) {
if (current->size >= size) {
if (current->size == size) {
if (prev == nullptr) {
freeMemoryList = current->next;
} else {
prev->next = current->next;
}
delete current;
} else {
current->size -= size;
void* allocatedBlock = current + 1;
current->next = (struct MemoryBlock*)((char*)allocatedBlock + current->size);
return allocatedBlock;
}
return (void*)allocatedBlock;
}
prev = current;
current = current->next;
}
return nullptr; // 分配失败
}
内存回收模块初始化时,会建立回收内存的规则和机制,确定如何将释放的内存块合并到空闲内存链表中,以及如何处理内存碎片。例如,当一个内存块被释放时,需要检查其相邻的内存块是否也为空闲状态,如果是,则将它们合并成一个更大的内存块。
cpp
// 回收内存并合并相邻空闲块
void回收内存(void* ptr, size_t size) {
struct MemoryBlock* newBlock = (struct MemoryBlock*)((char*)ptr - sizeof(struct MemoryBlock));
newBlock->size = size;
struct MemoryBlock* current = freeMemoryList;
struct MemoryBlock* prev = nullptr;
while (current!= nullptr && current < newBlock) {
prev = current;
current = current->next;
}
if (prev!= nullptr && (char*)prev + prev->size + sizeof(struct MemoryBlock) == (char*)newBlock) {
prev->size += newBlock->size + sizeof(struct MemoryBlock);
newBlock = prev;
}
if (current!= nullptr && (char*)newBlock + newBlock->size + sizeof(struct MemoryBlock) == (char*)current) {
newBlock->size += current->size + sizeof(struct MemoryBlock);
newBlock->next = current->next;
} else {
newBlock->next = current;
}
if (prev == nullptr) {
freeMemoryList = newBlock;
} else {
prev->next = newBlock;
}
}
内存保护模块初始化时,会设置内存的访问权限,如只读、可读写、可执行等,以及建立内存访问的监控机制,防止非法内存访问。这通常通过调用底层操作系统的相关函数(如mprotect
)来实现。
cpp
// 设置内存访问权限示例
bool setMemoryProtection(void* ptr, size_t size, int prot) {
if (mprotect(ptr, size, prot) == -1) {
return false;
}
return true;
}
3.2 内存池的创建与初始化
内存池是内存管理子系统中用于存储和分配内存的重要数据结构,它将内存划分为多个固定大小或可变大小的内存块,以提高内存分配和回收的效率。
固定大小内存池的创建与初始化过程如下:首先确定内存池的大小和每个内存块的大小,然后将内存池划分为多个相同大小的内存块,并将这些内存块链接起来,形成一个空闲内存块链表。
cpp
// 定义固定大小内存块结构体
struct FixedSizeMemoryBlock {
bool isAllocated; // 是否已分配
struct FixedSizeMemoryBlock* next; // 指向下一个内存块的指针
};
// 定义固定大小内存池结构体
struct FixedSizeMemoryPool {
size_t blockSize; // 每个内存块大小
size_t numBlocks; // 内存块数量
FixedSizeMemoryBlock* freeList; // 空闲内存块链表头指针
};
// 创建并初始化固定大小内存池
FixedSizeMemoryPool* createFixedSizeMemoryPool(size_t totalSize, size_t blockSize) {
FixedSizeMemoryPool* pool = new FixedSizeMemoryPool();
pool->blockSize = blockSize;
pool->numBlocks = totalSize / blockSize;
pool->freeList = new FixedSizeMemoryBlock[pool->numBlocks];
for (int i = 0; i < pool->numBlocks - 1; ++i) {
pool->freeList[i].isAllocated = false;
pool->freeList[i].next = &pool->freeList[i + 1];
}
pool->freeList[pool->numBlocks - 1].isAllocated = false;
pool->freeList[pool->numBlocks - 1].next = nullptr;
return pool;
}
// 从固定大小内存池分配内存
void* allocateFromFixedSizePool(FixedSizeMemoryPool* pool) {
if (pool->freeList == nullptr) {
return nullptr; // 内存池已空
}
FixedSizeMemoryBlock* allocatedBlock = pool->freeList;
pool->freeList = pool->freeList->next;
allocatedBlock->isAllocated = true;
return (void*)allocatedBlock;
}
// 向固定大小内存池回收内存
void freeToFixedSizePool(FixedSizeMemoryPool* pool, void* ptr) {
FixedSizeMemoryBlock* freedBlock = (FixedSizeMemoryBlock*)ptr;
freedBlock->isAllocated = false;
freedBlock->next = pool->freeList;
pool->freeList = freedBlock;
}
可变大小内存池的创建相对复杂,它通常基于空闲内存链表进行管理,在分配和回收内存时需要根据实际的内存需求进行动态调整,以适应不同大小的内存分配请求。
3.3 内存管理系统服务的启动与注册
内存管理子系统启动后,需要将自身的功能以服务的形式提供给其他子系统和应用程序使用,这就涉及到内存管理系统服务的启动与注册。
在Android系统中,通过Binder机制实现进程间通信,内存管理服务也通过Binder接口对外提供服务。首先需要定义内存管理服务的接口,例如:
cpp
// 定义内存管理服务接口
class MemoryManagementService : public BinderService<MemoryManagementService> {
public:
virtual status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0);
void* allocateMemory(size_t size);
void freeMemory(void* ptr, size_t size);
// 其他内存管理相关接口方法
};
// 实现内存管理服务接口方法
status_t MemoryManagementService::onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) {
switch (code) {
case MEMORY_ALLOCATE: {
size_t size = data.readInt();
void* ptr = allocateMemory(size);
reply->writePointer(ptr);
return NO_ERROR;
}
case MEMORY_FREE: {
void* ptr = data.readPointer();
size_t size = data.readInt();
freeMemory(ptr, size);
return NO_ERROR;
}
// 其他接口方法的实现
default:
return BBinder::onTransact(code, data, reply, flags);
}
}
然后在系统启动过程中,将内存管理服务注册到系统服务管理器中,以便其他进程能够通过服务名称获取到该服务的引用,进而调用其提供的内存管理接口。
cpp
// 注册内存管理服务
void registerMemoryManagementService() {
sp<IServiceManager> sm = defaultServiceManager();
sp<MemoryManagementService> service = new MemoryManagementService();
sm->addService(String16("memory.management"), service);
}
通过内存管理系统服务的启动与注册,其他子系统和应用程序可以方便地请求内存分配、释放等操作,实现对内存资源的共享和管理。
四、内存分配策略与实现
4.1 不同类型内存分配请求的处理
在Android Runtime中,内存分配请求主要分为栈内存分配、堆内存分配和共享内存分配等类型,内存管理子系统针对不同类型的请求采用不同的处理方式。
栈内存分配主要用于函数调用过程中局部变量的存储。当一个函数被调用时,内存管理子系统会为该函数的栈帧分配一定大小的内存空间,用于存储局部变量、函数参数和返回地址等信息。栈内存的分配和回收是自动进行的,遵循后进先出(LIFO)的原则。在函数调用结束时,其对应的栈帧内存会被自动释放。在C++中,栈内存的分配由编译器自动处理,例如:
cpp
void function() {
int localVar = 10; // 局部变量存储在栈内存中
// 函数执行过程中, localVar占用栈内存空间
} // 函数结束, localVar占用的栈内存自动释放
堆内存分配则用于动态创建对象和数据结构,如通过new
操作符分配的内存。内存管理子系统在处理堆内存分配请求时,会根据内存分配策略从空闲内存池中查找合适的内存块进行分配。如果当前空闲内存池无法满足分配请求,可能会触发内存扩展操作,如调用brk
系统函数扩展堆内存大小。
cpp
int* allocateArray(int size) {
int* arr = new int[size]; // 从堆内存分配数组空间
return arr;
}
共享内存分配用于实现不同进程之间的数据共享。内存管理子系统会创建一块共享的内存区域,并将其映射到不同进程的地址空间中,使得这些进程可以直接访问和修改共享内存中的数据。这通常通过mmap
系统调用结合shm_open
等函数来实现。
cpp
// 创建共享内存并映射到当前进程地址空间
void* createAndMapSharedMemory(size_t size) {
在Android Runtime中,内存分配请求主要分为栈内存分配、堆内存分配和共享内存分配等类型,内存管理子系统针对不同类型的请求采用不同的处理方式。
栈内存分配主要用于函数调用过程中局部变量的存储。当一个函数被调用时,内存管理子系统会为该函数的栈帧分配一定大小的内存空间,用于存储局部变量、函数参数和返回地址等信息。栈内存的分配和回收是自动进行的,遵循后进先出(LIFO)的原则。在函数调用结束时,其对应的栈帧内存会被自动释放。在C++中,栈内存的分配由编译器自动处理,例如:
cpp
void function() {
int localVar = 10; // 局部变量存储在栈内存中
// 函数执行过程中,localVar占用栈内存空间
} // 函数结束,localVar占用的栈内存自动释放
堆内存分配则用于动态创建对象和数据结构,如通过new
操作符分配的内存。内存管理子系统在处理堆内存分配请求时,会根据内存分配策略从空闲内存池中查找合适的内存块进行分配。如果当前空闲内存池无法满足分配请求,可能会触发内存扩展操作,如调用brk
系统函数扩展堆内存大小。
cpp
int* allocateArray(int size) {
int* arr = new int[size]; // 从堆内存分配数组空间
return arr;
}
共享内存分配用于实现不同进程之间的数据共享。内存管理子系统会创建一块共享的内存区域,并将其映射到不同进程的地址空间中,使得这些进程可以直接访问和修改共享内存中的数据。这通常通过mmap
系统调用结合shm_open
等函数来实现。
cpp
// 创建共享内存并映射到当前进程地址空间
void* createAndMapSharedMemory(size_t size) {
int shm_fd = shm_open("/my_shared_memory", O_CREAT | O_RDWR, 0666);
if (shm_fd == -1) {
// 处理创建失败情况
return nullptr;
}
if (ftruncate(shm_fd, size) == -1) {
// 处理调整大小失败情况
close(shm_fd);
return nullptr;
}
void* ptr = mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
if (ptr == MAP_FAILED) {
// 处理映射失败情况
close(shm_fd);
return nullptr;
}
close(shm_fd);
return ptr;
}
4.2 常见内存分配算法的实现与应用
内存管理子系统采用多种内存分配算法来提高内存使用效率,常见的算法包括首次适应算法、最佳适应算法、最坏适应算法和伙伴系统算法等。
首次适应算法(First Fit)从空闲内存链表的头部开始查找,找到第一个能够满足分配请求大小的内存块进行分配。该算法实现简单,倾向于使用内存低地址部分,会导致低地址部分产生较多碎片。
cpp
// 首次适应算法分配内存
void* firstFitAllocate(size_t size, struct MemoryBlock** freeList) {
struct MemoryBlock* current = *freeList;
struct MemoryBlock* prev = nullptr;
while (current!= nullptr) {
if (current->size >= size) {
if (current->size == size) {
if (prev == nullptr) {
*freeList = current->next;
} else {
prev->next = current->next;
}
return current;
} else {
void* allocatedBlock = current + 1;
current->size -= size;
current = (struct MemoryBlock*)((char*)allocatedBlock + current->size);
return allocatedBlock;
}
}
prev = current;
current = current->next;
}
return nullptr; // 分配失败
}
最佳适应算法(Best Fit)遍历整个空闲内存链表,找到大小最接近分配请求且大于等于请求大小的内存块进行分配。该算法能减少内存碎片,但每次分配都需要遍历链表,开销较大。
cpp
// 最佳适应算法分配内存
void* bestFitAllocate(size_t size, struct MemoryBlock** freeList) {
struct MemoryBlock* bestFit = nullptr;
struct MemoryBlock* current = *freeList;
while (current!= nullptr) {
if (current->size >= size && (bestFit == nullptr || current->size < bestFit->size)) {
bestFit = current;
}
current = current->next;
}
if (bestFit == nullptr) {
return nullptr; // 无合适内存块
}
if (bestFit->size == size) {
if (bestFit == *freeList) {
*freeList = bestFit->next;
} else {
struct MemoryBlock* prev = *freeList;
while (prev->next!= bestFit) {
prev = prev->next;
}
prev->next = bestFit->next;
}
return bestFit;
} else {
void* allocatedBlock = bestFit + 1;
bestFit->size -= size;
bestFit = (struct MemoryBlock*)((char*)allocatedBlock + bestFit->size);
return allocatedBlock;
}
}
最坏适应算法(Worst Fit)与最佳适应算法相反,它选择空闲内存链表中最大的内存块进行分配。该算法可以减少内存碎片的产生,但可能导致大内存块快速耗尽。
cpp
// 最坏适应算法分配内存
void* worstFitAllocate(size_t size, struct MemoryBlock** freeList) {
struct MemoryBlock* worstFit = nullptr;
struct MemoryBlock* current = *freeList;
while (current!= nullptr) {
if (current->size >= size && (worstFit == nullptr || current->size > worstFit->size)) {
worstFit = current;
}
current = current->next;
}
if (worstFit == nullptr) {
return nullptr; // 无合适内存块
}
if (worstFit->size == size) {
if (worstFit == *freeList) {
*freeList = worstFit->next;
} else {
struct MemoryBlock* prev = *freeList;
while (prev->next!= worstFit) {
prev = prev->next;
}
prev->next = worstFit->next;
}
return worstFit;
} else {
void* allocatedBlock = worstFit + 1;
worstFit->size -= size;
worstFit = (struct MemoryBlock*)((char*)allocatedBlock + worstFit->size);
return allocatedBlock;
}
}
伙伴系统算法(Buddy System)将内存按2的幂次进行划分,分配和回收时以这些固定大小的块为单位,通过合并相邻的空闲块来减少内存碎片,在处理大量大小相近的内存分配请求时效率较高。
4.3 内存分配的性能优化措施
为了提高内存分配的性能,内存管理子系统采取了多种优化措施。其中之一是内存池技术,通过预先分配一块较大的内存作为内存池,将其划分为多个固定大小或可变大小的内存块。在处理内存分配请求时,优先从内存池中获取内存块,避免频繁调用系统级的内存分配函数(如malloc
),减少系统调用开销。例如,对于频繁创建和销毁的小对象,可以使用固定大小内存池,每次分配和回收操作只是在内存池内部的链表上进行指针操作,速度更快。
缓存机制也是优化内存分配性能的重要手段。内存管理子系统会维护一些缓存,如最近分配的内存块缓存、空闲内存块缓存等。当有新的内存分配请求时,首先检查缓存中是否有合适的内存块,若有则直接返回,减少查找空闲内存的时间。同时,对于回收的内存块,也会先放入缓存中,等待后续的分配请求,避免立即合并到空闲内存链表带来的额外开销。
此外,内存管理子系统还会对内存分配请求进行批处理,将多个小的内存分配请求合并为一个较大的请求,一次性从空闲内存中分配内存,然后再细分给各个请求。这样可以减少内存分配操作的次数,提高整体性能。并且通过动态调整内存分配策略,根据系统运行时的内存使用情况和分配模式,自动选择最合适的分配算法,进一步优化内存分配性能。
五、内存回收机制与流程
5.1 自动内存回收(垃圾回收)的原理与实现
在Android Runtime中,自动内存回收主要通过垃圾回收(Garbage Collection,GC)机制实现。垃圾回收的核心目标是识别并回收不再被使用的对象所占用的内存空间,防止内存泄漏,提高内存利用率。
垃圾回收机制基于可达性分析原理。系统从一系列被称为"根对象"的起始点开始,如栈上的局部变量、静态变量、JNI引用等,通过引用关系遍历整个对象图。所有从根对象可达的对象被认定为"存活对象",而不可达的对象则被判定为"垃圾对象",其占用的内存可以被回收。
在ART中,常见的垃圾回收算法包括标记 - 清除(Mark-Sweep)算法、标记 - 整理(Mark-Compact)算法和分代回收(Generational Collection)算法。标记 - 清除算法分为两个阶段:标记阶段,从根对象开始遍历,标记所有可达对象;清除阶段,遍历整个堆内存,回收所有未被标记的垃圾对象。
cpp
// 标记 - 清除算法简化实现
class Object {
public:
bool isMarked;
Object* next;
};
Object* heapStart; // 堆内存起始地址
void markPhase() {
// 假设根对象列表为rootObjects
std::vector<Object*> rootObjects;
std::stack<Object*> stack;
for (Object* root : rootObjects) {
stack.push(root);
}
while (!stack.empty()) {
Object* current = stack.top();
stack.pop();
if (current->isMarked) {
continue;
}
current->isMarked = true;
// 处理对象的引用关系,将可达对象入栈
Object* ref = current->next;
while (ref!= nullptr) {
stack.push(ref);
ref = ref->next;
}
}
}
void sweepPhase() {
Object* prev = nullptr;
Object* current = heapStart;
while (current!= nullptr) {
if (current->isMarked) {
current->isMarked = false;
prev = current;
current = current->next;
} else {
Object* next = current->next;
if (prev == nullptr) {
heapStart = next;
} else {
prev->next = next;
}
// 释放内存操作
delete current;
current = next;
}
}
}
标记 - 整理算法在标记 - 清除算法的基础上,增加了整理阶段,将存活对象向内存的一端移动,使得空闲内存连续,解决了标记 - 清除算法产生内存碎片的问题。
分代回收算法将对象根据存活时间划分为不同的代,如新生代(Young Generation)、老年代(Old Generation)和永久代(Permanent Generation,在Java中,Android已移除)。新生代存放新创建的对象,由于大多数对象生命周期较短,所以新生代采用复制算法(Copying Algorithm),将存活对象复制到另一块区域,快速回收垃圾对象。老年代存放存活时间较长的对象,采用标记 - 整理算法或标记 - 清除算法,减少内存碎片并提高回收效率。
5.2 手动内存回收的方式与应用场景
除了自动内存回收,Android Runtime也提供了手动内存回收的方式,主要用于开发者明确知道对象不再使用,希望立即释放内存的场景。在C++中,通过delete
操作符手动释放使用new
分配的内存,例如:
cpp
int* ptr = new int;
// 使用ptr
delete ptr; // 手动释放内存
对于数组类型的内存分配,需要使用delete[]
进行释放,否则会导致内存泄漏。
cpp
int* arr = new int[10];
// 使用arr
delete[] arr; // 释放数组内存
在Android的Java层,虽然主要依赖自动垃圾回收,但通过System.gc()
方法可以建议系统进行垃圾回收。不过需要注意的是,System.gc()
只是一个建议,系统不一定会立即执行垃圾回收操作。手动调用垃圾回收通常用于一些特殊场景,如在应用进入后台前,主动释放一些占用大量内存的临时对象,以减少应用在后台的内存占用,避免被系统杀死。
此外,在使用JNI(Java Native Interface)与C/C++交互时,开发者需要特别注意内存的手动管理。当Java层传递对象引用给C/C++代码时,C/C++代码对该对象的操作可能会影响其生命周期,需要正确处理内存的分配和释放,防止内存泄漏或悬空指针等问题。
5.3 内存回收过程中的碎片整理与合并
内存回收过程中会产生内存碎片,即空闲内存块不连续,导致无法满足较大的内存分配请求。为了解决这个问题,内存管理子系统需要进行碎片整理与合并操作。
在内存回收时,当一个内存块被释放,系统会检查其相邻的内存块是否也为空闲状态。如果是,则将它们合并成一个更大的内存块。例如,在基于链表的内存管理中,当释放一个内存块后,会遍历链表,找到相邻的空闲块并进行合并。
cpp
// 合并相邻空闲内存块
void mergeFreeBlocks(struct MemoryBlock** freeList) {
struct MemoryBlock* current = *freeList;
while (current!= nullptr && current->next!= nullptr) {
if ((char*)current + current->size + sizeof(struct MemoryBlock) == (char*)current->next) {
current->size += current->next->size + sizeof(struct MemoryBlock);
current->next = current->next->next;
} else {
current = current->next;
}
}
}
对于更复杂的内存碎片问题,如外部碎片(空闲内存总量足够但没有连续的大内存块),可以采用内存紧缩(Memory Compaction)技术,将所有存活对象移动到内存的一端,使空闲内存连续。但内存紧缩操作开销较大,因为需要修改所有指向存活对象的指针,所以通常在内存碎片严重影响到内存分配效率时才会采用。
此外,通过采用更合理的内存分配算法,如伙伴系统算法,从分配阶段就尽量减少内存碎片的产生;以及使用内存池技术,固定大小的内存池不会产生碎片,可变大小的内存池也能通过合理的管理减少碎片,从而在整体上提高内存回收的效率和内存的使用效率。
六、内存保护与安全机制
6.1 内存访问权限的设置与管理
内存管理子系统通过设置内存访问权限来保护内存数据的安全性和完整性,防止非法访问和修改。在Linux系统(Android基于Linux内核)中,通过mprotect
系统调用来设置内存区域的访问权限,常见的权限包括可读(PROT_READ)、可写(PROT_WRITE)和可执行(PROT_EXEC)。
在Android Runtime中,对于不同类型的内存区域,会设置不同的访问权限。例如,代码段(存储可执行代码)通常设置为可读和可执行权限(PROT_READ | PROT_EXEC),防止代码被意外修改;数据段(存储全局变量、静态变量等)设置为可读和可写权限(PROT_READ | PROT_WRITE),允许程序读取和修改数据;栈内存区域设置为可读和可写权限,用于存储函数调用时的局部变量和返回地址等信息。
cpp
// 设置内存区域访问权限示例
bool setMemoryPermissions(void* address, size_t size, int permissions) {
if (mprotect(address, size, permissions) == -1) {
// 处理设置权限失败情况
return false;
}
return true;
}
对于共享内存区域,根据其使用场景和安全需求,也会设置相应的访问权限。如果多个进程需要同时读写共享内存,则会设置为可读可写权限;如果共享内存仅用于数据共享而不允许修改,则设置为只读权限。
此外,内存管理子系统还会对内存访问权限进行动态管理。例如,在进行某些安全操作时,临时提升或降低内存区域的访问权限,操作完成后再恢复原状,以增强内存的安全性。
6.2 防止内存泄漏与越界访问的措施
内存泄漏是指程序中已动态分配的内存由于某种原因未释放或无法释放,导致系统内存资源浪费。越界访问则是指程序访问了不属于自己的内存空间,可能导致数据损坏或程序崩溃。内存管理子系统采取多种措施来防止这些问题。
为防止内存泄漏,在自动内存回收方面,垃圾回收机制通过可达性分析准确识别不再使用的对象并回收其内存。在手动内存管理方面,开发者需要遵循严格的内存分配和释放规则,确保每一个new
都有对应的delete
,每一个malloc
都有对应的free
。同时,使用智能指针(如C++中的std::unique_ptr
、std::shared_ptr
)可以自动管理内存的生命周期,避免忘记释放内存导致的泄漏问题。