开发者

Art 虚拟机系列Heap内存模型分配策略详解

开发者 https://www.devze.com 2023-03-28 10:20 出处:网络 作者: Pika
目录背景内存分配第一层第二层第三层第四层第五、六层内存分配策略BumpPointerSpaceZygoteSpaceRegionSpaceDlmallocSpace 与 RosAllocSpaceLargeObjectSpace总结背景
目录
  • 背景
  • 内存分配
    • 第一层
    • 第二层
    • 第三层
    • 第四层
    • 第五、六层
  • 内存分配策略
    • BumpPointerSpace
    • ZygoteSpace
    • RegionSpace
    • DlmallocSpace 与 RosAllocSpace
    • LargeObjectSpace
  • 总结

    背景

    随着性能优化逐步进入深水区,我们发现很多大厂的方案,其实都建立在对正常虚拟机运行过程中Hook的基础上,建立自己的优化方案。因此,只有了解了art虚拟机的运行机制,才能真正去实施一些特殊的“魔改”方案。因为art代码非常庞大,我们会从很多的方面去了解他。本片是Heap 模型的第一篇。

    内存分配

    当我们在Java层 new 一个对象的时候,由于有虚拟机的存在,我们开发者不用了解任何相关的内存分配细节,就能够完成一次内存的分配。那么,art虚拟机究竟是怎么帮助我们进行内存分配的,其实得益于art虚拟机抽象的以下分配类

    Art 虚拟机系列Heap内存模型分配策略详解

    我们从上图可以看到,art把内存分配相关的模型策略,分为了6层

    第一层

    分为了Space与AllocSpace两个类,他们都是一个虚类,即需要子类去实现virtual修饰的方法,比如我们看到Space类定义

    class Space {
     public:
      // Dump space. Also key method for C++ vtables.
      virtual void Dump(std::ostream& os) const;
    

    Space代表一块内存空间,AllocSpace代表一块可用于内存分配的空间,AllocSpace则提供了内存分配与释放相关的虚函数,Alloc,Free,由特定的子类去实现对应的策略。

    class AllocSpace {
     public:
      virtual mirror::Object* Alloc(Thread* self, size_t num_bytes, size_t* bytes_allocated,
                                    size_t* usable_size, size_t* bytes_tl_bulk_allocated) = 0;
      virtual size_t Free(Thread* self, mirror::Object* ptr) = 0;
    

    再回过头我们看到上面的类图,我们就会发现子类中的ImageSpace是没有继承于AllocSpace,这就意味着,它其实是没有实现对应的Alloc与Free相关的函数,因此,我们常说的GC机制,其实是不会作用在这块内存空间上面的!非常有趣!我们下文会继续讲到,这跟art划分的内存模型有很大关系

    第二层

    第二层是ContinuousSpace与DiscontinuousSpace两个类,这两个类的职责非常简单,前者就是代表了该内存空间是连续的,后者是非连续。

    划分为连续与非连续,其实更大的作用是在GC过程中相关的内存整理,非连续的空间有更简单的分配方式

    第三层

    MemMapSpace 与 LargeObjectSpace是第三层,在这里,已经开始根据外部所需要的空间的大小,进行了不同的空间划分。比如当分配的内存属于java对象String或者是基础类型的数组,且内存超过3个内存页(Heap 构造时我们会讲到,一般是12kb)的时候,就会把资源分配到LargeObject内存空间里面,其他的就分配在MemMapSpace

    第四层

    到了第四层,我们出现了第一个非常重要的内存分配空间,ImageSpcace,ImageSpace用于.art文件的加载,我们知道应用可以在预编译的时候,生成.art文件,还有就是一些系统类,比如boot.art,这些art文件在内存中的表现,其实就放在了ImageSpace中。同时我们也讲到,ImageSpace没有继承AllocSpace,这也就意味着,它不会对外提供分配与释放策略,因此该内存中,也不会被GC所影响!(可以把部分启动类放在这里,达到GC抑制的效果)而另外的ContinuousMemMapAllocSpace,将由它的子类去实现常见的内存分配策略。

    第五、六层

    五六层就是具体的art内存分配策略了,包括了BumpPointerSpace,ZygoteSpace,RegionSpace,RosAllocSpace,DlMallocSpace,它们都有自己特有的内存分配策略,这些策略被应用在不同的各个地方(主要由GC策略选择不同的内存分配策略)。下面我们按装顺序介绍一下这几种内存分配策略

    内存分配策略

    BumpPointerSpace

    BumpPointerSpace其实分配思想很简单,就是顺序分配,比如我第一次分配分配10kb内存,下一次再分配xxkb

    Art 虚拟机系列Heap内存模型分配策略详解

    BumpPointerSpace::BumpPointerSpace(const std::string& name, uint8_t* begin, uint8_t* limit)
        : ContinuousMemMapAllocSpace(name,
                                     MemMap::Invalid(),
                                     begin,
                                     begin,
                                     limit,
                                     kGcRetentionPolicyAlwaysCollect),
          growth_end_(limit), 资源的尾部,
          objects_allocated_(0), 当前有多少个mirrorobject
          bytes_allocated_(0), 分配的内存大小
          block_lock_("Block lock"),
          main_block_size_(0) {
      // This constructor gets called only from Heap::PreZygoteFork(), which
      // doesn't require a mark_bitmap.
    }
    

    因为是顺序分配,因此我们只需记录一个尾部end地址(已分配的最后一个地址尾部),就能在下次分配直接找到分配的开始地址,非常简单,这里就不详细说分配过程,可以参考分配源码

    ZygoteSpace

    ZygoteSpace本身没有创建相关的内存资源,而是通过外部传入的MemMap对象,作为内存资源,自身只是起到了一个管理作用

    ZygoteSpace* ZygoteSpace::Create(const std::string& name,
                                     MemMap&& mem_map,
                                     accounting::ContinuousSpaceBitmap&& live_bitmap,
                                     accounting::ContinuousSpaceBitmap&& mark_bitmap) {
      DCHECK(live_bitmap.IsValid());
      DCHECK(mark_bitmap.IsValid());
      size_t objects_allocated = 0;
      CountObjectsAllocated visitor(&objects_allocated);
      ReaderMutexLock mu(Thread::Current(), *Locks::heap_bitmap_lock_);
      live_bitmap.VisitMarkedRange(reinterpret_cast<uintptr_t>(mem_map.Begin()),
                                   reinterpret_cast<uintptr_t>(mem_map.End()), visitor);
      ZygoteSpace* zygote_space = new ZygoteSpace(name, std::move(mem_map), objects_allocated);
      zygote_space->live_bitmap_ = std::move(live_bitmap);
      zygote_space->mark_bitmap_ = std::move(mark_bitmap);
      return zygote_space;
    }
    

    ZygoteSpace在art有特殊的含义,就是该内存空间是继承自Zygote进程的资源存放地,虽然它继承自AllocSpace,但是它的分配实现Alloc/Free,是不起作用的

    mirror::Object* ZygoteSpace::Alloc(Thread*, size_t, size_t*, size_t*, size_t*) {
      UNIMPLEMENTED(FATAL);
      UNREACHABLE();
    }
    

    它作为一块带有“含义”的内存资源而存在,同时它不同于ImageSpace,ImageSpace存放的是系统资源内存,不会发生GC操作。而ZygoteSpace会在FullGC的时候,进行GC的操作。因为来自Zygote进程的内存,还是有回收的空间

    RegionSpace

    RegionSpace,其实是把内存资源通过划分的思想,把内存划分成一个个固定大小的Region而得名,相当于内存空间被划分多个内存块,进行内存分配时,在内存块分配即可。

    RegionSpace::RegionSpace(const std::string& name, MemMap&& mem_map, bool use_generational_cc)
        : ContinuousMemMapAllocSpace(name,
                                     std::move(mem_map),
                                     mem_map.Begin(),
                                     mem_map.End(),
                                     mem_map.End(),
                                     kGcRetentionPolicyAlwaysCollect),
          region_lock_("Region lock", kRegionSpaceRegionLock),
          use_generational_编程客栈cc_(use_generational_cc),
          time_(1U),
          num_regions_(mem_map_.Size() / kRegionSize),
          madvise_time_(0U),
          num_non_free_regions_(0U),
          num_evac_regions_(0U),
          max_peak_num_non_free_regions_(0U),
       编程   non_free_region_index_limit_(0U),
          current_region_(&full_region_),
          evac_region_(nullptr),
          cyclic_alloc_region_index_(0U) {
      CHECK_ALIGNED(mem_map_.Size(), kRegionSize);
      CHECK_ALIGNED(mem_map_.Begin(), kRegionSize);
      DCHECK_GT(num_regions_, 0U);
      num_regions_ 个Region空间
      regions_.reset(new Region[num_regions_]);
      uint8_t* region_addr = mem_map_.Begin();
      for (size_t i = 0; i < num_regions_; ++i, region_addr += kRegionSize) {
        regions_[i].Init(i, region_addr, region_addr + kRegionSize);
      }
      mark_bitmap_ =
          accounting::ContinuousSpaceBitmap::Create("region space live bitmap", Begin(), Capacity());
      if (kIsDebugBuild) {
        CHECK_EQ(regions_[0].Begin(), Begin());
        for (size_t i = 0; i < num_regions_; ++i) {
          CHECK(regions_[i].IsFree());
          CHECK_EQ(static_cast<size_t>(regions_[i].End() - regions_[i].Begin()), kRegionSize);
          if (i + 1 < num_regions_) {
            CHECK_EQ(rwww.devze.comegions_[i].End(), regions_[i + 1].Begin());
          }
        }
        CHECK_EQ(regions_[num_regions_ - 1].End(), Limit());
      }
      DCHECK(!full_region_.IsFree());
      DCHECK(full_region_.IsAllocated());
      size_t ignored;
      DCHECK(full_region_.Alloc(kAlignment, &ignored, nullptr, &ignored) == nullptr);
      // Protect the whole region space from the start.
      Protect();
    }
    

    其中分配会采取内存对其,由属性kRegionSize决定(每个Region默认1m)

    因为抽象出来一个个Region,我们能够随心的去分配想要的内存,不局限内存本身是否是连续的,同时因为每个内存块当前的状态不同(比如,有的已经分配了,有的没有),因此每个Region本身还对应一个状态RegionState

     enum class RegionState : uint8_t {
        kRegionStateFree,            该Region没有内存分配
        kRegionStateAllocated,       内存块已分配
        kRegionStateLarge,           大对象Region,比如4m的大对象(那么就需要4个Region),其中第一块Region状态为kRegionStateLarge,其他就是kRegionStateLargeTail
        kRegionStateLargeTail,       如上
      };
    

    RegionSpace本身还定义着很多toSpace,fromSpace的操作,其实它就是Copying Collection(拷贝垃圾回收机制)的内存分配模型。然后android默认的垃圾回收机制,前台是cms,后台是HSC,因此,RegionSpace能被发挥作用的地方其实还算是有点局限,除非你指定了Copying Collection垃圾回收机制

    DlmallocSpace 与 RosAllocSpace

    我们从上文的类图可以看到DlmallocSpace与RosAllocSpace ,都是MallocSpace的子类,而我们的Heap里面,其实只有MallocSpace对象,具体选择实现的子类是DlmallocSpace还是Ro开发者_Js入门sAllocSpace,其实由kUseRosAlloc决定,默认是使用RosAllocSpace。

     space::MallocSpace* Heap::CreateMallocSpaceFromMemMap(MemMap&& mem_map,
                                                          size_t initial_size,
                                                          size_t growth_limit,
                                                          size_t capacity,
                                                          const char* name,
                                                          bool can_move_objects) {
      space::MallocSpace* malloc_space = nullptr;
      if (kUseRosAlloc) {
        // Create rosalloc space.
        malloc_space = space::RosAllocSpace::CreateFromMemMap(std::move(mem_map),
                                                              name,
                                                              kDefaultStartingSize,
                                                              initial_size,
                                                              growth_limit,
                                                              capacity,
                                                              low_memory_mode_,
                                                              can_move_objects);
      } else {
        malloc_space = space::DlMallocSpace::CreateFromMemMap(std::move(mem_map),
                                 python                             name,
                                                              kDefaultStartingSize,
                                                              initial_size,
                                                              growth_limit,
                                                              capacity,
                                                              can_move_objects);
      }
      if (collector::SemiSpace::kUseRememberedSet) {
        accounting::RememberedSet* rem_set  =
            new accounting::RememberedSet(std::string(name) + " remembered set", this, malloc_space);
        CHECK(rem_set != nullptr) << "Failed to create main space remembered set";
        AddRememberedSet(rem_set);
      }
      CHECK(malloc_space != nullptr) << "Failed to create " << name;
      malloc_space->SetFootprintLimit(malloc_space->Capacity());
      return malloc_space;
    }
    

    DlmallocSpace与RosAllocSpace两者不同点就在于,实现的机制不一样,DlmallocSpace采用的是dlmalloc内存分配管理模型,它是一个开源库,也是c语音malloc调用的具体实现。dlmalloc内存机制如下(其实就是malloc的机制)

    Art 虚拟机系列Heap内存模型分配策略详解

    而RosAllocSpace,采用的是谷歌自己的内存分配rosalloc完成,具体实现机制如下

    Art 虚拟机系列Heap内存模型分配策略详解

    rosalloc是一种动态分配内存算法,专门为了art虚拟机做了适配,其实它是一种多粒度内存分配算法,ros的意思就是run of slot,可以理解为一个run是RosAllocSpace中内存分配的单元,每个Run有自己的内存分配粒度(slot)

    Art 虚拟机系列Heap内存模型分配策略详解

    比如Run1 中代表可分配10kb为单位的内存,Run2则代表可分配1M为单位的内存,具体的内存由slot决定。

    更加具体的分配细节,可以在这里看到,我们就不深入细节,有个概念就好。

    LargeObjectSpace

    它的创建过程很简单,就是初始化num_bytes_allocated_等分配的参数

    LargeObjectSpace::LargeObjectSpace(const std::string& name, uint8_t* begin, uint8_t* end,
                                       const char* lock_name)
        : DiscontinuousSpace(name, kGcRetentionPolicyAlwaysCollect),
          lock_(lock_name, kAllocSpaceLock),
          num_bytes_allocated_(0), num_objects_allocated_(0), total_bytes_allocated_(0),
          total_objects_allocated_(0), begin_(begin), end_(end) {
    }
    

    不知道大家有没有发现,LargeObjectSpace跟其他Space不一样,它继承于DiscontinuousSpace,意味着它的分配是不连续的,我们看一下它的分配过程就理解了

    mirror::Object* LargeObjectMapSpace::Alloc(Thread* self, size_t num_bytes,
                                               size_t* bytes_allocated, size_t* usable_size,
                                               size_t* bytes_tl_bulk_allocated) {
      std::string error_msg;
      每次都调用MapAnonymous,其实它最终调用的就是mmap
      MemMap mem_map = MemMap::MapAnonymous("large object space allocation",
                                            num_bytes,
                                            PROT_READ | PROT_WRITE,
                                            /*low_4gb=*/ true,
                                            &error_msg);
      if (UNLphpIKELY(!mem_map.IsValid())) {
        LOG(WARNING) << "Large object allocation failed: " << error_msg;
        return nullptr;
      }
      mirror::Object* const obj = reinterpret_cast<mirror::Object*>(mem_map.Begin());
      const size_t allocation_size = mem_map.BaseSize();
      MutexLock mu(self, lock_);
      large_objects_.Put(obj, LargeObject {std::move(mem_map), false /* not zygote */});
      DCHECK(bytes_allocated != nullptr);
      if (begin_ == nullptr || begin_ > reinterpret_cast<uint8_t*>(obj)) {
        begin_ = reinterpret_cast<uint8_t*>(obj);
      }
      end_ = std::max(end_, reinterpret_cast<uint8_t*>(obj) + allocation_size);
      *bytes_allocated = allocation_size;
      if (usable_size != nullptr) {
        *usable_size = allocation_size;
      }
      DCHECK(bytes_tl_bulk_allocated != nullptr);
      *bytes_tl_bulk_allocated = allocation_size;
      num_bytes_allocated_ += allocation_size;
      total_bytes_allocated_ += allocation_size;
      ++num_objects_allocated_;
      ++total_objects_allocated_;
      return obj;
    }
    

    可以看到,分配的时候,其实每次都是采用mmap去分配内存空间,而LargeObjectMapSpace就是将mmap返回的空间进行了一次管理,所以它才是不连续的,很好理解,这也意味指这块内存的增长,不会影响属于连续内存分配的其他Space(这里能做很多黑科技)。

    那么我们分配的内存什么时候会被划分进入LargeObjectSpace呢?其实我们直接看Heap分配条件即可,被定义为大对象要满足以下条件

    inline bool Heap::ShouldAllocLargeObject(ObjPtr<mirror::Class> c, size_t byte_count) const {
      return byte_count >= large_object_threshold_ && (c->IsPrimitiveArray() || c->IsStringClass());
    }
    

    large_object_threshold_默认为12kb

    总结

    我们通过本文,初步了解了art内存分配的几个模型,为什么要写这个系列呢,其实就是因为最近大厂很多针对内存优化的方案,都离不开对虚拟机内存分配的知识的了解!希望能够通过这一系列的文章,能够对自己有帮助!

    以上就是Art 虚拟机系列Heap内存模型分配策略详解的详细内容,更多关于Art 虚拟机Heap内存分配的资料请关注我们其它相关文章!

    0

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    关注公众号