ART学习笔记(二)RosAlloc

ART学习笔记

Posted by Cc1over on December 2, 2019

ART学习笔记(二)RosAlloc

前言

上一篇笔记记录了ART虚拟机中的各种Space,而MallocSpace这个虚类就是ART设计与实现如C语言般的内存分配及释放,而且ART中默认用的是RosAllocSpace,它的内存分配和释放算法是依赖于rosalloc实现的

# RosAlloc关键类及成员变量

  • FreePageRun: 在RosAlloc中通过AllocPages提供整数倍页大小内存分配的接口,而FreePageRun是RosAlloc中的内部类,用于帮助RosAlloc管理页内存的分配
  • Slot:代表内存分配的单元(有42种不同的粒度)
  • Run: RosAlloc的内部类,一个Run对象代表了一个内存分配的资源池(多个内存分配粒度一样的slot
  • SlotFreeList:管理Slot的空闲链表
  • RosAlloc:实际提供内存分配的关键类
// RosAlloc成员变量声明
// kNumOfSizeBrackets = 42
static size_t bracketSizes[kNumOfSizeBrackets];
static size_t numOfPages[kNumOfSizeBrackets];

  • bracketSizes: rosalloc设计了42种不同粒度的内存分配单元(Slot),bracketSizes用于描述每种slot所支持的内存分配粒度,比如:
    • barcketSizes[0] = 8
    • barcketSizes[1] = 16
    • barcketSizes[41] = 2KB
    • 内存分配时要选择一种粒度的slot,通常以向上取整的方式选择
  • numOfPages:记录内中slot对应的内存资源又多少(以4KB为单位)
    • 粒度为1KB的slot拥有2*4KB内存资源
    • 粒度为2Kb的slot拥有4*4KB内存资源
    • 其余粒度的slot拥有1*4KB内存资源

# Run & slot

  • Run对象代表一个内存分配的资源池,它将多个slot组织起来,通过size_bracket_idx_可以获取Run相关的各种信息:
    • bracketSizes[size_bracket_idx_] = Runslot内存分配的粒度
    • numOfPages[size_bracket_idx_] = Run中拥有的内存资源大小
    • headerSizes[size_bracket_idx_] = Run中整个头部空间的大小
    • numOfSlots[size_bracket_idx_] = Runslot的个数

# RosAlloc中的内存划分

// RosAlloc成员变量声明
// 代表rosalloc要管理的那块内存
uint8_t* base_;
// 这块内存资源目前的大小 <= max_capacity
size_t capacity_;
// 这块内存资源的最大尺寸
size_t max_capacity_;
// 用于保存base_内存分配情况以及一些状态
std::unique_ptr<MemMap> page_map_mem_map;
// 内存映射对象对应内存基地址
volatile unit8_t* page_map_;
size_t page_map_size_;
size_t max_page_map_size_;

  • 下半部分:base_代表rosalloc中所管理的内存资源:
    • 以 4KB/页 为单元进行划分
    • 内存资源最大为max_capacity
    • 内存页个数为page_map_size_
    • base_可强转成FreePageRun
  • 上半部分:用于记录内存页的状态,与下半部分的内存页一一对应,数组中每一个元素保存base_中每一页的信息

# RosAlloc-> Alloc

[rosalloc-inl.h-> RosAlloc::Alloc]

inline ALWAYS_INLINE void* RosAlloc::Alloc(Thread* self, size_t size, size_t* bytes_allocated, size_t* usable_size, size_t* bytes_tl_bulk_allocated) {
    if(size > 2KB) {
        return AllocLargeObject(self,size,bytes_allocated,usable_size,bytes_tl_bulk_allocated);
    }else{
        return AllocFromRun(self,size,bytes_allocated,usable_size,bytes_tl_bulk_allocated);
    }
}

[rosalloc-inl.h-> RosAlloc::AllocFromRun]

void* RosAlloc::AllocFromRun(Thread* self, size_t size, size_t* bytes_allocated, size_t* usable_size, size_t* bytes_tl_bulk_allocated) { 
    // 根据size向上取整决定应该采用哪种粒度的slot
    size idx = SizeToIndexAndBracketSize(size,...);
    if(idx < 16) {
        // bracketSizes[16] = 128, 如果分配的内存不超过128字节,则从线程本地资源池中分配内存
        // 线程本地资源池 ≠ TLAB,但与TLAB类似,是Thread对rosalloc单独支持
        // 与TLAB类似,本地资源池的信息存储在tlsPtr_中
        Run* thread_local_run = reinterpret_cast<Run*>(self->GetRosAllocRun(idx));
        slot_addr = thread_local_run->AllocSlot();
        // thread_local_run内存不足
        if(slot_addr == nullptr) {
           MutexLock mu(self,*size_bracket_lock_[idx]);
           // 空闲链表合并 
           if(thread_local_run->MergeThreadLocalFreeListToFreeList) {
               // ......
           } else {
               full_runs_[idx].insert(thread_local_run);
               thread_local_run = RefillRun(self,idx);
               thread_local_run->SetIsThreadLocal(true);
               self->SetRosAllocRun(idx, thread_local_run);
           }
           // 重新内存分配
           slot_addr = thread_locak_run->AllocSlot();
        }
    } else {
        // 当所需内存超过128字节时,将从RosAolloc内部的资源池进行分配,这个时候需要同步锁保护
        // 不同大小的资源池使用不同的同步锁保护
        MutexLock mu(self, *size_bracket_locks_[idx]);
        slot_addr = AllocFromCurrentRunUnlocked(self, idx);
        // 一些校验判空
    }
    return slot_addr;
}
  • 当分配内存不超过128字节,则从线程本地资源池中分配内存
  • 而当在线程本地资源池中内存不足的情况下:
    • 把thread_local_free_list_中的空闲资源合并到Run中free_list_中
    • 调用RefillRun重新分配一个Run对象
    • 把这个新Run对象设置为thread_local_run
    • 在这个新的Run中分配内存
    • 而这个已满的Run对象会被放到full_runs_数组保存起来
  • 当所需内存超过128字节,将从RosAlloc内部的资源池进行分配,不同大小的资源池使用不同的同步锁

[rosalloc.cc->RosAlloc::RefillRun]

RosAlloc::Run* RosAlloc::AllocRun(Thread* self,size_t idx) {
    RosAlloc::Run* new_run = nullptr;
    MutexLock mu(self,lock_);
    // 从base_所在的内存中分配一段内存空间,这段内存空间由一个Run对象进行管理
    // 这段空间的大小为numOfPage[idx]
    // kPageMapRun代表内存状态的一种
    new_run = reinterpret_cast<Run*>(AllocPages(self,numOfPage[idx],kPageMapRun));
    if(new_run!=nullptr) {
        // 初始化Run中的空闲链表
        new_run->InitFreeList();
    }
    return new_run;
}
  • 从base_中以页为单位分配Run,页数为numOfPage[idx]的值
  • 初始化Run中的Slot空闲链表

[rosalloc.h->RosAlloc::Run::InitFreeList]

void initFreeList() { 
   const uint8_t idx = size_bracket_idx_;
   const size_t bracket_size = brackerSizes[idx];
   Slot* first_slot = FirstSlot();
   for(Slot* slot = LastSlot();slot>=first_slot;slot=slot->Left(bracket_size)) {
      free_list_.Add(slot);     
   } 
}

[rosalloc-inl.h->RosAlloc::Run::AllocSlot]

inline void* RosAlloc::Run::AllocSlot(){
    Slot* slot = free_list_.Remove();
    return slot;
}
  • Run是从base_中以整页为单位分配过来的,而在Run的初始化过程中会初始Run中的Slot空闲链表
  • Slot空闲链表会按照Slot在Run中的内存布局的位置由前到后排列,而运行过程中考虑到内存释放,Slot空闲链表结构无法保持initFreeList时候的所创建的初始顺序,单不英雄内存分配

参考资料: 《深入理解Android虚拟机ART》