ART学习笔记(一)Space
# 前言
隔了有一段时间没有去记录下学习的内容,心里感觉怪怪的,前段时间一直在忙着项目的开发,最近终于交接了,有时间来回顾一些学习的内容
# Space关键类
-
第一层Space & AllocSpace: Space代表一块内存空间,而AllocSpace代表一块可用于内存分配的空间,提供了和内存分配及释放有关的虚函数
-
第二层ContinuousSpace & DiscontinuousSpace: ContinuousSpace表示连续的内存空间 ,DiscontinuousSpace表示非连续的内存空间
-
第三层MemMapSpace & LargeObjectSpace: MemMapSpace表示内存空间里的内存是通过内存映射技术来提供的,而当一个java对象(该对象的类型必须java String或基础数据类型,比如int数组)所需的内存超过3个内存页时,将使用LargeObjectSpace来提供内存资源
-
第四层ImageSpace & ContinuousMemMapAllocSpace: ImageSpace用于.art文件的加载,ContinuousMemMapAllocSpace代表一个可以对外提供连续内存空间,其内存资源由内存映射技术提供
-
第五层:只有MallocSpace是虚类,其他三个类都可以直接用于内存分配,但所使用的内存分配算法各不相同
-
第六层:DlMallocSpace & RosAllocSpace 派生自MallocSpace,用于内存分配,但是使用了不同的算法
# ZygoteSpace
- 外界传入mem_map指针,表示对应的内存资源
- mirror Object对应于java中的Obect,8字节对齐
- 不能分配内存及释放内存
# BumpPointerSpace
- 采用顺序分配的分配方式,内存分配逻辑为第N次内存分配的起始位置是第N-1次内存分配的重点位置,因此需要记录下最后最后一次分配的终点位置 === Bump Pointer
- 不能释放某一次所分配的内存,只支持一次性释放所有已分配的内存
- 适合用于TLAB的分配
- heap中有成员bump_pointer_space,指向一个BumpPointerSpace对象,可以被任意一个线程作为TLAB使用
- 第一个分配TLAB的线程将创建一个Main Block,位于内存资源的头部,尾随main_block_size_指明
- 后续线程的TLAB都会有一个BlockHeader描述
[bump_pointer_space-inl.h-> BumpPointerSpace::Alloc]
inline mirror::Object* BumpPointerSpace::Alloc(Thread* , size_t num_bytes, size_t* bytes_allocated, size_t* usable_size, size_t* bytes_tl_bulk_allocated) {
//......
// 8字节向上对齐
nums_bytes = RoundUp(num_bytes, 8);
// 分配内存,返回值类型为mirror Object*
mirror::Object* ret = AllocNonvirtual(nums_bytes);
// 设置返回参数,各种size的判读和设置
// ......
return ret;
}
[bump_pointer_space-inl.h-> BumpPointerSpace::AllocNonvirtual]
inline mirror::Object* BumpPointerSpace::AllocNonvirtual(size_t num_bytes) {
// 交由AllocNonvirtualWithoutAccoutint进行内存分配
mirror::Object* ret = AllocNonvirtualWithoutAccoutint(num_bytes);
if(ret != nullptr){
// object_allocated与bytes_allocated都是AtomicInteger类型,若对象分配成功,则添加两个计数器的计数值
object_allocated_.FercgAbdAddSequentiallyConsistent(1);
bytes_allocated_.FercgAbdAddSequentiallyConsistent(num_bytes)
}
return ret;
}
[bump_pointer_space-inl.h-> AllocNonvirtualWithoutAccoutint]
inline mirror::Object* BumpPointerSpace::AllocNonvirtualWithoutAccoutint(size_t num_bytes) {
uint8_t* old_end;
uint8_t* new_end;
do{
// end_为ContinousSpace的成员变量,表示上一次内存分配的末尾位置,就是Bump Pointer位置,为Atomic<uint_8*>
// 获取当前space的末尾位置
old_end = end_.LoadRelaxed();
// 计算新的末尾位置
new_end = old_end + num_byes;
// 如果超过内存资源的大小,则返回空指针
if(UNLIKELY(new_end > growth_end_)){
return nullptr;
}
// 将end_指向新的末尾的位置
while(!end_.CompareExchangeWeakSequentiallyConsistent(old_end,new_end));
}
return reinterpret_cast<mirror::Object*>(old_end);
}
[bump_pointer_space.cc-> BumpPointerSpace::AllocNewTlab]
bool BumpPointerSpace::AllocNewTlab(Thread* self, size_t bytes) {
MutexLock mu(Thread::Current(),block_lock_);
// 先释放self线程原来的TLAB
RevokeThreadLocalBuffersLocked(self);
uint8_t* start = AllocBlock(bytes);
if(start == nullptr){
return false;
}
self->setTlab(start,start+bytes);
return true;
}
[Thread tlsPtr_]
struct PACKED(sizeof(void *)) tls_ptr_sized_values {
// 表示 TLAB 中分配了多少个对象
size_t thread_local_objects;
// TLAB 的起始位置
uint8_t* thread_local_start;
// TLAB 当前所分配的内存位置,位于 thread_local_start 与 thread_local_end 之间
uint8_t* thread_local_pos;
// TLAB 的末尾位置
uint8_t* thread_local_end;
} tlsPtr_;
- tlsPtr_是Thread中的一个结构体,记录了一些与TLAB相关的信息
- setTlab函数执行的操作就是更新tlsPtr_指向的数据
[bump_pointer_space.cc-> BumpPointerSpace::AllocBlock]
uint8_t* BumpPointerSpace::AllocBlock(size_t btyes) {
// 8字节向上对齐
btyes = RoundUp(btyes,8);
struct BlockHeader {
// 内存块总大小
size_t size_;
// 剩余内存空间
size_t unused_;
}
// 第一次分配,设置main_block_size_的值
if(!num_blocks_) {
main_block_size_ = Size();
}
// 分配内存,原来所需内存大小 + BlockHeader结构的大小
uint8_t* storage = reinterpret_cast<uint8_t*>
(AllocNonvirtualWithoutAccoutint(bytes+sizeof(BlockHeader)))
// 设置BlockHeader的信息
// 跳过header的部分返回
storage += sizeof(BlockHeader);
// blocks计数 + 1
return storgae;
}
# RegionSpace
- 先将内存资源划分成一个个固定的大小(默认为1MB)的内存块,每一个内存块由一个Region对象表示,进行内存分配时,先找到满足要求的Region,然后从Region中分配资源,Region内的分配方式与BumpPointerSpace的分配方式一致
- RegionSpace的用法和拷贝垃圾回收算法的使用有关,拷贝垃圾回收算法把内存资源划分为两个semispace,新对象分配时,所需的内存来自其中一个semispace(tospace),而另一个semispace(fromspace)作为空闲空间,当tospace不够用的时候就进行垃圾回收,对两个space进行变量交换,然后将新fromspace中存活对象拷贝到新tospace中,最后释放fromspace的空间,而引入的Region的概念之后,对于内存资源的划分就可以不再局限于semispace,而是region,并且可以根据region的内存占用情况,动态设置region达到fromspace的条件
[region_space-inl.h-> RegionSpace::Alloc]
inline mirror::Object* RegionSpace::Alloc(Thread* , size_t num_bytes, size_t* byte_allocated, size_t* usbale_size, size_t* bytes_tl_bulk_allocated) {
// 8字节向上对齐
num_bytes = RoundUp(num_bytes,8);
return AllocNonvirtual<false>(num_bytes, bytes_allocated, usable_size, bytes_tl_bulk_allocated);
}
[region_space-inl.h-> RegionSpace::AllocNonvirtual]
inline mirror::Object* RegionSpace::AllocNonvirtual(size_t num_bytes, size_t* bytes_allocated, size_t* usable_size, size_t* bytes_tl_bulk_allocated) {
mirror::Object obj;
// num_bytes与regionSize比较,过滤分配空间大于regionSize的情况
if(num_bytes < = kRegionSize){
// 不加锁根据kForEvac参数从当前region或者evac_region 分配内存
// evac_regio代表还没分配过内存的region
if(!kForEvac){
obj = current_region->Alloc(num_bytes, bytes_allocated, usable_size, bytes_tl_bulk_allocated);
}else{
obj = evac_region->Alloc(num_bytes, bytes_allocated, usable_size, bytes_tl_bulk_allocated);
}
// 分配成功
if(obj != nullptr) return obj;
// 留一半内存块作为fromspace
if(num_non_free_regions_ + 1) * 2 > num_regions_) {
return nullptr;
}
// 分配失败可能有两种原因:
// 1) 并发问题:可能其他线程设置了current_region_
// 在这种情况下就加锁重试
// 2) 非并发问题,遍历region数组找到空闲的region进行分配
// 然后找到空闲的region之后便进行内存分配
} else {
obj = AllocLarge<kForEvac>(num_bytes,bytes_allocated,usable_size,bytes_tl_bulk_allocated);
if(obj != nullptr){
return obj;
}
}
return nullptr;
}
- RegionSpace Alloc的任务就是确认好目标的内存块,确认好目标的内存块后,真正的内存分配工作就交给Region的Alloc函数
- 而Region的Alloc函数和BumpPointer使用的算法完全一样
[region_space.cc-> RegionSpace::AllocNewTlab]
RegionSpace也可用作线程的TLAB,regionSpace的AllocNewTlab的逻辑就是找到一个空闲的Region就可以了
# DlMallocSpace & RosAllocSpace
BumpPointerSpace & RegionSpace都采用了比较简单的内存分配算法,但是并不能满足类似在C语音中malloc和free这样自由分配和释放内存的要求,而MallocSpace这个虚类,提供了DlMallocSpace和RosAllocSpace两个实现类提供类似C语音中mallc/free的内存分配和释放功能
- DlMallocSpace:使用开源的dlmalloc提供具体的内存分配和释放算法
- RosAllocSpace:使用了谷歌开发的rosalloc内存分配器,而且需要ART虚拟机的其他模块配合使用,分配效果比dlmalloc更好
[heap.cc-> Heap::CreateMallocSpaceFromMemMap]
space::MallocSpace* Heap::CreateMallocSpaceFormMemMap(MemMap* mem_map, size_t initial_size, size_t growth_limit, size_t capacity, const char* name, bool can_move_objects) {
// kUseRosAlloc默认为true
if(kUseRosAlloc) {
// 采用RosAllocSpace
}else {
// 采用DlMallocSpace
}
// 创建RememberSet, 主要用于存储跨Space引用关系,解决跨Space问题
}
- 可见其实,ART虚拟机中的设计其实默认会使用RosAllocSpace,而定义虚类就是为了实现多态
# LargeObjectMapSpace
当一个对象所需的内存空间大小超过设定的阀值(默认为3个内存页),同时,该对象的类型必须是Java基础类型的数组或者Java对象的类型是java.lang.String时就会使用LargeObjectSpace,而且在Android高版本中,Fresco也会把Bitmap对象存放在LargeObjectMapSpace中
LargeObjectMapSapce的常见并不需要和其他Space一样需要在构造函数传入mem_map,然后在mem_map进一步内存分配,而是调用Alloc函数的时候直接在操作系统中映射一块内存空间
[large_object_space.cc-> LargeObjectMapSpace::Alloc]
mirror::Object* LargeObjectMapSpace::Alloc(Thread* self, size_t num_bytes, size_t* bytes_allocated, size_t* usable_size, size_t* bytes_tl_bulk_allocated) {
// 直接创建一个MemMap对象
MemMap* mem_map = MemMap::MapAnonymous(num_bytes);
// 将这块内存映射空间的基地址转换为返回值类型
mirror::Object* const obj = reinterpret_cast<mirror::Object*>(mem_map->Begin());
// 用一个map保存下内存映射空间的MemMap对象
MutexLock mu(self, lock_);
large_objects_.Put(obj, LargeObject {mem_map, false});
return obj;
}
参考资料: 《深入理解Android虚拟机ART》