【ART】大内存管理

52 阅读4分钟

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);
}

决定排序的规则有三个:

  1. 当前AllocationInfo前面的空闲内存大小
  2. 当前AllocationInfo自身的内存大小
  3. 如果上面两个都相同, 则通过本身的地址来进行区分.

这里有一个非常大的疑问, 如果前方空闲内存相同时为什么使用自身内存大小来进行区分, 而不直接使用地址? , 这样做的好处是什么呢?

Alloc流程:

既然FreeBlocks管理着AllocationInfo 且又通过PrevFree来进行排序, 那就可以通过在FreeBlocks中查找符合的空闲内存块来完成内存申请.

  1. 先构造一个临时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

  1. 如果查找成功:
  // 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
  1. 上面获取到的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);