ART中针对大内存(大于12K大小的内存且内存类型为基本类型数组和字符串对象)的申请会单独处理, 根据配置可分为FreeListSpace和LargeObjectMapSpace。
其中LargeObjectMapSpace是每次alloc和free都会调用系统的mmap和munmap,管理模块逻辑简单,但效率低。
FreeListSpace会一次性mmap一块512M内存, 是一个连续的大内存块,逻辑相对复杂但效率要高于前者, 目前Android T正是采用这种方法来对大内存块进行管理.
FreeListSpace
FreeListSpace在构造时会通过函数MemMap::MapAnonymous映射出512M的虚拟内存, 然后以4K(1页)为单元通过类AllocationInfo对连续内存块进行管理.
如果AllocationInfo是已申请的内存块且前面有空闲内存也就是GetPrevFreeBytes()不为0 则把这样的info通过FreeBlocks来进行管理(如上图中的1/2/3), 以便于内存申请时查找到合适的内存块.
在这里FreeBlocks 是std::set (通过红黑树实现) 的别名
using FreeBlocks = std::set<AllocationInfo*,
SortByPrevFree,
TrackingAllocator<AllocationInfo*, kAllocatorTagLOSFreeList>>;
FreeBlocks通过比较函数SortByPrevFree进行排序
inline bool FreeListSpace::SortByPrevFree::operator()(const AllocationInfo* a,
const AllocationInfo* b) const {
if (a->GetPrevFree() < b->GetPrevFree()) return true;
if (a->GetPrevFree() > b->GetPrevFree()) return false;
if (a->AlignSize() < b->AlignSize()) return true;
if (a->AlignSize() > b->AlignSize()) return false;
return reinterpret_cast<uintptr_t>(a) < reinterpret_cast<uintptr_t>(b);
}
决定排序的规则有三个:
- 当前AllocationInfo前面的空闲内存大小
- 当前AllocationInfo自身的内存大小
- 如果上面两个都相同, 则通过本身的地址来进行区分.
这里有一个非常大的疑问, 如果前方空闲内存相同时为什么使用自身内存大小来进行区分, 而不直接使用地址? , 这样做的好处是什么呢?
Alloc流程:
既然FreeBlocks管理着AllocationInfo 且又通过PrevFree来进行排序, 那就可以通过在FreeBlocks中查找符合的空闲内存块来完成内存申请.
- 先构造一个临时AllocationInfo以便于在FreeBlocks中进行查找
const size_t allocation_size = RoundUp(num_bytes, kAlignment); // 页内存对齐
AllocationInfo temp_info;
temp_info.SetPrevFreeBytes(allocation_size);
temp_info.SetByteSize(0, false);
然后通过lower_bound查找第一个满足temp_info要求的 info
- 如果查找成功:
// Find the smallest chunk at least num_bytes in size.
auto it = free_blocks_.lower_bound(&temp_info);
if (it != free_blocks_.end()) {
AllocationInfo* info = *it;
free_blocks_.erase(it);
// Fit our object in the previous allocation info free space.
new_info = info->GetPrevFreeInfo();
// Remove the newly allocated block from the info and update the prev_free_.
info->SetPrevFreeBytes(info->GetPrevFreeBytes() - allocation_size);
if (info->GetPrevFreeBytes() > 0) {
AllocationInfo* new_free = info - info->GetPrevFree();
new_free->SetPrevFreeBytes(0);
new_free->SetByteSize(info->GetPrevFreeBytes(), true);
// If there is remaining space, insert back into the free set.
free_blocks_.insert(info);
}
}
- 先把查找到的info 从free_blocks_中移除
- 然后info前面空闲区域大小就减小到了info->GetPrevFreeBytes() - allocation_size
- 如果前面仍然存在空闲区域就把改变大小后的info重新insert到free_blocks_中, 并更新new_free
- 上面获取到的new_info就是就是我们申请的内存块, 然后获取其地址返回就可以了.
mirror::Object* obj = reinterpret_cast<mirror::Object*>(GetAddressForAllocationInfo(new_info));
// We always put our object at the start of the free block, there cannot be another free block
// before it.
if (kIsDebugBuild) {
CheckedCall(mprotect, __FUNCTION__, obj, allocation_size, PROT_READ | PROT_WRITE);
}
new_info->SetPrevFreeBytes(0);
new_info->SetByteSize(allocation_size, false);
return obj;
4. 举例来说, 如果我们申请12k内存也就是3页大小, 那么将会在下面蓝色区域开辟这块内存, 且会把info3重新移除后插入到free_blocks_中, 顺便更新info7的信息.
Free 流程
总体来说就是先把物理内存释放掉, 然后把相邻的AllocationInfo进行合并.
- 通过地址获取info, 并通过madvise释放掉页表(这里并没有释放虚拟地址)
AllocationInfo* info = GetAllocationInfoForAddress(reinterpret_cast<uintptr_t>(obj));
const size_t allocation_size = info->ByteSize();
// madvise the pages without lock
madvise(obj, allocation_size, MADV_DONTNEED);
- 如果当前内存块前面有空闲块, 进行一次合并
size_t prev_free_bytes = info->GetPrevFreeBytes();
size_t new_free_size = allocation_size;
if (prev_free_bytes != 0) {
// Coalesce with previous free chunk.
new_free_size += prev_free_bytes;
RemoveFreePrev(info);
info = info->GetPrevFreeInfo();
// The previous allocation info must not be free since we are supposed to always coalesce.
DCHECK_EQ(info->GetPrevFreeBytes(), 0U) << "Previous allocation was free";
}
- 如果其next_info是free 的, 更新next_next_info否则更新next_info
AllocationInfo* new_free_info;
if (next_info->IsFree()) {
AllocationInfo* next_next_info = next_info->GetNextInfo();
// Next next info can't be free since we always coalesce.
DCHECK(!next_next_info->IsFree());
DCHECK_ALIGNED(next_next_info->ByteSize(), kAlignment);
new_free_info = next_next_info;
new_free_size += next_next_info->GetPrevFreeBytes();
RemoveFreePrev(next_next_info); //移出free_blocks_
} else {
new_free_info = next_info;
}
new_free_info->SetPrevFreeBytes(new_free_size);
free_blocks_.insert(new_free_info); //更新后重新插入
info->SetByteSize(new_free_size, true);
DCHECK_EQ(info->GetNextInfo(), new_free_info);