ART运行时Compacting GC堆创建过程分析

       引进了Compacting GC之后,ART运行时的堆空间结构就发生了变化。这是由于Compacting GC和Mark-Sweep GC的算法不同,要求底层的堆具有不同的空间结构。同时,即使是原来的Mark-Sweep GC,由于需要支持新的同构空间压缩特性(Homogeneous Space Compact),也使得它们要具有与原来不一样的堆空间结构。本文就对这些堆空间创建过程进行详细的分析。

老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!

《Android系统源代码情景分析》一书正在进击的程序员网(http://0xcc0xcd.com)中连载,点击进入!

       从前面ART运行时Java堆创建过程分析一文可以知道,在没有Compacting GC之前,Mark-Sweep GC的堆由Image Space、Zygote Space、Allocation Space和Large Object Space四种Space组成。其中,Allocation Space是从Zygote Space中分离出来的,它们都是一种DlMallocSpace。引入Compacting GC之后,Image Space和Large Object Space没有发生根本性的变化,但是Zygote Space和Allocation Space就发生了很大的变化。因此,接下来我们就结合Compacting GC以及其它的一些新特性来分析Zygote Space和Allocation Space都发生了哪些变化。

       从前面ART运行时Compacting GC简要介绍和学习计划一文可以知道,用来分配对象的空间可以是一种DlMallocSpace,也可以是一种RosAllocSpace,因此,堆空间发生的第一个变化是用来分配对象的空间有可能是一个DlMallocSpace,也有可能是一个RosAllocSpace。

       从前面ART运行时Compacting GC简要介绍和学习计划一文还可以知道,Semi-Space GC需要两个Bump Pointer Space,Generational Semi-Space GC需要两个Bump Pointer Space和一个Promote Space,Mark-Compact GC需要一个Bump Pointer Space。因此,我们需要增加一种类型为Bump Pointer的Space,以及一个Promote Space。

       此外,我们还需要一个Non-Moving Space。由于在Compacting GC中,涉及到对象的移动,但是有些对象,例如类对象(Class)、类方法对象(ArtMethod)和类成员变量对象(ArtField),它们一经加载后,基本上就会一直存在。因此,频繁对此类对象进行移动是无益的,我们需要将它们分配在一个不能移动的Space中,以减少在Compacting GC需要处理的对象的数量。

      所谓的同构空间压缩特性(Homogeneous Space Compact),是针对Mark-Sweep GC而言的。一个Space需要有Main和Backup之分。执行同构空间压缩时,将Main Space的对象移动至Backup Space中去,再将Main Space和Backup Space进行交换,这样就达到压缩空间,即减少内存碎片的作用。

      综合前面的分析,我们就列出ART运行时支持的各种GC的堆空间结构,如下三个图所示:


图1 Mark-Sweep GC的堆空间结构


图2 Semi-Space GC和Mark-Compact GC的堆空间结构


图3 Generational Semi-Space GC的堆空间结构

      接下来,我们将结构源代码来详细分析上述三个图各个Space的创建过程,这样就可以更好理解这三个图所表达的意思。

      从前面ART运行时Java堆创建过程分析一文可以知道,堆的创建是从在ART运行时内部创建一个Heap对象开始的,如下所示:

bool Runtime::Init(const RuntimeOptions& raw_options, bool ignore_unrecognized) {
  ......

  heap_ = new gc::Heap(options->heap_initial_size_,
                       options->heap_growth_limit_,
                       options->heap_min_free_,
                       options->heap_max_free_,
                       options->heap_target_utilization_,
                       options->foreground_heap_growth_multiplier_,
                       options->heap_maximum_size_,
                       options->heap_non_moving_space_capacity_,
                       options->image_,
                       options->image_isa_,
                       options->collector_type_,
                       options->background_collector_type_,
                       options->parallel_gc_threads_,
                       options->conc_gc_threads_,
                       options->low_memory_mode_,
                       options->long_pause_log_threshold_,
                       options->long_gc_log_threshold_,
                       options->ignore_max_footprint_,
                       options->use_tlab_,
                       options->verify_pre_gc_heap_,
                       options->verify_pre_sweeping_heap_,
                       options->verify_post_gc_heap_,
                       options->verify_pre_gc_rosalloc_,
                       options->verify_pre_sweeping_rosalloc_,
                       options->verify_post_gc_rosalloc_,
                       options->use_homogeneous_space_compaction_for_oom_,
                       options->min_interval_homogeneous_space_compaction_by_oom_);


  ......
}
      这个函数定义在文件art/runtime/runtime.cc中。

      创建堆所需要的一般性参数的含义可以参考前面ART运行时Java堆创建过程分析一文,这里我们只解释几个与Compacting GC相关的参数:

      options->heap_non_moving_space_capacity_:Non-Moving Space的大小,可以通过ART运行时启动选项-XX:NonMovingSpaceCapacity来指定,默认大小为kDefaultNonMovingSpaceCapacity(64MB)。

      options->collector_type_:Foreground GC的类型,可以通过ART运行时启动选项-Xgc指定。如果没有指定,在编译ART运行时时,可以通过ART_DEFAULT_GC_TYPE_IS_CMS、ART_DEFAULT_GC_TYPE_IS_SS和ART_DEFAULT_GC_TYPE_IS_GSS这三个宏分别默认为Concurrent Mark-Sweep GC、Semi-Space GC或者Generational Semi-Space GC。

      options->background_collector_type_:Background GC的类型,可以通过ART运行时启动选项-XX:BackgroundGC指定。如果没有指定,在编译ART运行时时,可以通过ART_USE_HSPACE_COMPACT宏指定为Homogeneous-Space-Compact。如果没有指定ART_USE_HSPACE_COMPACT宏,默认就与Foreground GC一样。

      options->use_homogeneous_space_compaction_for_oom_:是否在OOM时执行Homogeneous-Space-Compact,可以通过ART运行时启动选项-XX:EnableHSpaceCompactForOOM和-XX:DisableHSpaceCompactForOOM来设置为支持和不支持。如果没有指定,默认不支持。

      options->min_interval_homogeneous_space_compaction_by_oom_:OOM时执行Homogeneous-Space-Compact的最小时间间隔,可以在OOM时频繁地执行Homogeneous-Space-Compact,固定为100秒。

      Heap对象的创建和初始化过程如下所示:

Heap::Heap(size_t initial_size, size_t growth_limit, size_t min_free, size_t max_free,
           double target_utilization, double foreground_heap_growth_multiplier,
           size_t capacity, size_t non_moving_space_capacity, const std::string& image_file_name,
           const InstructionSet image_instruction_set, CollectorType foreground_collector_type,
           CollectorType background_collector_type, size_t parallel_gc_threads,
           size_t conc_gc_threads, bool low_memory_mode,
           size_t long_pause_log_threshold, size_t long_gc_log_threshold,
           bool ignore_max_footprint, bool use_tlab,
           bool verify_pre_gc_heap, bool verify_pre_sweeping_heap, bool verify_post_gc_heap,
           bool verify_pre_gc_rosalloc, bool verify_pre_sweeping_rosalloc,
           bool verify_post_gc_rosalloc, bool use_homogeneous_space_compaction_for_oom,
           uint64_t min_interval_homogeneous_space_compaction_by_oom)
  ......

  byte* requested_alloc_space_begin = nullptr;
  if (!image_file_name.empty()) {
    std::string error_msg;
    space::ImageSpace* image_space = space::ImageSpace::Create(image_file_name.c_str(),
                                                               image_instruction_set,
                                                               &error_msg);
    if (image_space != nullptr) {
      AddSpace(image_space);
      // Oat files referenced by image files immediately follow them in memory, ensure alloc space
      // isn't going to get in the middle
      byte* oat_file_end_addr = image_space->GetImageHeader().GetOatFileEnd();
      ......
      requested_alloc_space_begin = AlignUp(oat_file_end_addr, kPageSize);
    } 
    ......
  }

  ......

  bool support_homogeneous_space_compaction =
      background_collector_type_ == gc::kCollectorTypeHomogeneousSpaceCompact ||
      use_homogeneous_space_compaction_for_oom;
  // We may use the same space the main space for the non moving space if we don't need to compact
  // from the main space.
  // This is not the case if we support homogeneous compaction or have a moving background
  // collector type.
  bool separate_non_moving_space = is_zygote ||
      support_homogeneous_space_compaction || IsMovingGc(foreground_collector_type_) ||
      IsMovingGc(background_collector_type_);
  if (foreground_collector_type == kCollectorTypeGSS) {
    separate_non_moving_space = false;
  }
  std::unique_ptr<MemMap> main_mem_map_1;
  std::unique_ptr<MemMap> main_mem_map_2;
  byte* request_begin = requested_alloc_space_begin;
  if (request_begin != nullptr && separate_non_moving_space) {
    request_begin += non_moving_space_capacity;
  }
  ......
  std::unique_ptr<MemMap> non_moving_space_mem_map;
  if (separate_non_moving_space) {
    // Reserve the non moving mem map before the other two since it needs to be at a specific
    // address.
    non_moving_space_mem_map.reset(
        MemMap::MapAnonymous("non moving space", requested_alloc_space_begin,
                             non_moving_space_capacity, PROT_READ | PROT_WRITE, true, &error_str));
    ......
    // Try to reserve virtual memory at a lower address if we have a separate non moving space.
    request_begin = reinterpret_cast<byte*>(300 * MB);
  }
  // Attempt to create 2 mem maps at or after the requested begin.
  main_mem_map_1.reset(MapAnonymousPreferredAddress(kMemMapSpaceName[0], request_begin, capacity_,
                                                    PROT_READ | PROT_WRITE, &error_str));
  ......
  if (support_homogeneous_space_compaction ||
      background_collector_type_ == kCollectorTypeSS ||
      foreground_collector_type_ == kCollectorTypeSS) {
    main_mem_map_2.reset(MapAnonymousPreferredAddress(kMemMapSpaceName[1], main_mem_map_1->End(),
                                                      capacity_, PROT_READ | PROT_WRITE,
                                                      &error_str));
    ......
  }
  // Create the non moving space first so that bitmaps don't take up the address range.
  if (separate_non_moving_space) {
    // Non moving space is always dlmalloc since we currently don't have support for multiple
    // active rosalloc spaces.
    const size_t size = non_moving_space_mem_map->Size();
    non_moving_space_ = space::DlMallocSpace::CreateFromMemMap(
        non_moving_space_mem_map.release(), "zygote / non moving space", kDefaultStartingSize,
        initial_size, size, size, false);
    non_moving_space_->SetFootprintLimit(non_moving_space_->Capacity());
    ......
    AddSpace(non_moving_space_);
  }
  // Create other spaces based on whether or not we have a moving GC.
  if (IsMovingGc(foreground_collector_type_) && foreground_collector_type_ != kCollectorTypeGSS) {
    // Create bump pointer spaces.
    // We only to create the bump pointer if the foreground collector is a compacting GC.
    // TODO: Place bump-pointer spaces somewhere to minimize size of card table.
    bump_pointer_space_ = space::BumpPointerSpace::CreateFromMemMap("Bump pointer space 1",
                                                                    main_mem_map_1.release());
    ......
    AddSpace(bump_pointer_space_);
    temp_space_ = space::BumpPointerSpace::CreateFromMemMap("Bump pointer space 2",
                                                            main_mem_map_2.release());
    ......
    AddSpace(temp_space_);
    ......
  } else {
    CreateMainMallocSpace(main_mem_map_1.release(), initial_size, growth_limit_, capacity_);
    ......
    AddSpace(main_space_);
    if (!separate_non_moving_space) {
      non_moving_space_ = main_space_;
      ......
    }
    if (foreground_collector_type_ == kCollectorTypeGSS) {
      ......
      // Create bump pointer spaces instead of a backup space.
      main_mem_map_2.release();
      bump_pointer_space_ = space::BumpPointerSpace::Create("Bump pointer space 1",
                                                            kGSSBumpPointerSpaceCapacity, nullptr);
      ......
      AddSpace(bump_pointer_space_);
      temp_space_ = space::BumpPointerSpace::Create("Bump pointer space 2",
                                                    kGSSBumpPointerSpaceCapacity, nullptr);
      ......
      AddSpace(temp_space_);
    } else if (main_mem_map_2.get() != nullptr) {
      const char* name = kUseRosAlloc ? kRosAllocSpaceName[1] : kDlMallocSpaceName[1];
      main_space_backup_.reset(CreateMallocSpaceFromMemMap(main_mem_map_2.release(), initial_size,
                                                           growth_limit_, capacity_, name, true));
      ......
      // Add the space so its accounted for in the heap_begin and heap_end.
      AddSpace(main_space_backup_.get());
    }
  }

  ......
}

      这个函数定义在文件art/runtime/gc/heap.cc中。

      由于底层堆的空间结构要兼顾到上层的各种GC,因此堆创建过程中涉及到逻辑是比较复杂的,我们将上述函数涉及到的代码分段来解读。

      第一段代码是关于Image Space的创建的,如下所示:

  byte* requested_alloc_space_begin = nullptr;
  if (!image_file_name.empty()) {
    std::string error_msg;
    space::ImageSpace* image_space = space::ImageSpace::Create(image_file_name.c_str(),
                                                               image_instruction_set,
                                                               &error_msg);
    if (image_space != nullptr) {
      AddSpace(image_space);
      // Oat files referenced by image files immediately follow them in memory, ensure alloc space
      // isn't going to get in the middle
      byte* oat_file_end_addr = image_space->GetImageHeader().GetOatFileEnd();
      ......
      requested_alloc_space_begin = AlignUp(oat_file_end_addr, kPageSize);
    } 
    ......
  }
      关于Image Space的创建过程,可以参考前面 ART运行时Java堆创建过程分析一文。从前面 ART运行时Java堆创建过程分析一文可以知道,紧跟在Image Space后面的是一个boot.art@classes.oat文件。而紧跟在boot.art@classes.oat文件末尾的Zygote Space,这个地址记录在本地变量requested_alloc_space_begin中。

      第二段代码是关于Non-Moving Space的,如下所示:

  bool support_homogeneous_space_compaction =
      background_collector_type_ == gc::kCollectorTypeHomogeneousSpaceCompact ||
      use_homogeneous_space_compaction_for_oom;
  // We may use the same space the main space for the non moving space if we don't need to compact
  // from the main space.
  // This is not the case if we support homogeneous compaction or have a moving background
  // collector type.
  bool separate_non_moving_space = is_zygote ||
      support_homogeneous_space_compaction || IsMovingGc(foreground_collector_type_) ||
      IsMovingGc(background_collector_type_);
  if (foreground_collector_type == kCollectorTypeGSS) {
    separate_non_moving_space = false;
  }
  std::unique_ptr<MemMap> main_mem_map_1;
  std::unique_ptr<MemMap> main_mem_map_2;
  byte* request_begin = requested_alloc_space_begin;
  if (request_begin != nullptr && separate_non_moving_space) {
    request_begin += non_moving_space_capacity;
  }
  ......
  std::unique_ptr<MemMap> non_moving_space_mem_map;
  if (separate_non_moving_space) {
    // Reserve the non moving mem map before the other two since it needs to be at a specific
    // address.
    non_moving_space_mem_map.reset(
        MemMap::MapAnonymous("non moving space", requested_alloc_space_begin,
                             non_moving_space_capacity, PROT_READ | PROT_WRITE, true, &error_str));
    ......
    // Try to reserve virtual memory at a lower address if we have a separate non moving space.
    request_begin = reinterpret_cast<byte*>(300 * MB);
  }

       这段代码的逻辑是判断是否需要给Non-Moving Space一个独立的地址空间。Non-Moving Space总是存在的,现在需要判断的是要给它一个独立的地址空间,还是要与其它Space共享同一个地址空间,主要是考虑到Generational Semi-Space GC。

       从前面ART运行时Compacting GC简要介绍和学习计划一文可以知道,Generational Semi-Space GC需要一个Promote Space来保存那些经过若干轮GC后仍然存活下来的对象,而且这些对象在以后的Generational Semi-Space GC中不需要进行移动。这个Promote Space就是一个DlMallocSpace或者RosAllocSpace。Promote Space起到的作用与Non-Moving Space类似,因为保存在它们里面的对象都是不可以移动的。因此,在Generational Semi-Space GC的情况下,将Promote Space和Non-Moving Space合在一起共享同一个地址空间。

       Non-Moving Space是相对Moving Space而言的,也就是说,只要存在Moving Space,就需要给Non-Moving Space一个独立的地址空间,使得在Non-Moving Space和Moving Space的对象在GC中可以区别对待处理。

       那么,在什么情况下存在Moving Space呢?最直觉地,只要我们使用到了Compacting GC,那么就需要Moving Space,因为Compacting GC需要移动对象。因此,上述代码段会调用Heap类的成员函数IsMovingGc判断指定的Foreground GC(foreground_collector_type_)和Background GC(background_collector_type_)是否是Compacting GC,也就是是否是Semi-Space GC、Generational Semi-Space GC和Mark-Compact GC之一。如果是的话,那么就将本地变量separate_non_moving_space设置为true,表示需要给Non-Moving Space一个独立的地址空间。

       除了Compacting GC的情况,还有两种情况也是涉及到Moving Space的。

       第一种情况是应用程序运行在Zygote模式中,即本地变量is_zygote等于true的情况下。应用程序运行在Zygote模式时,它们的进程都是由Zygote进程fork出来的,这样做的目的是为了让Zygote进程和应用程序进程共享内存。Zygote进程在fork第一个应用程序进程之前,为了有效地和应用程序进程共享内存,会对堆空间进行一次压缩处理。这个压缩处理实际上就是执行一次Semi-Space GC。因此,在这种情况下,即本地变量is_zygote等于true时,也需要将本地变量separate_non_moving_space设置为true,表示需要给Non-Moving Space一个独立的地址空间。

       第二种情况ART运行时支持Homogeneous-Space-Compact特性。Homogeneous-Space-Compact特性意味我们要将Main Space上的对象移动到Backup Space上去。这个移动过程实际上也是通过执行一次Semi-Space GC来完成的。因此,在这种情况下,即本地变量support_homogeneous_space_compaction等于true时,也需要将本地变量separate_non_moving_space设置为true,表示需要给Non-Moving Space一个独立的地址空间。

       那么,什么情况下ART运行时需要支持Homogeneous-Space-Compact特性呢?有两种情况需要支持。

       第一种情况是Background GC(background_collector_type_)被指定为Homogeneous-Space-Compact GC,这可以通过ART运行时启动选项-XX:BackgroundGC进行指定。

       第二种情况是在分配对象遇到OOM时,需要将Main Space上的对象移动到Backup Space上去,然后再将这两个Space进行交换,并且再次尝试在Main Space上进行分配,以便可以解决由内存碎片引发的OOM问题。我们可以通过ART运行时启动选项-XX:EnableHSpaceCompactForOOM和-XX:DisableHSpaceCompactForOOM来启用和禁用这种行为,体现在这里就是参数use_homogeneous_space_compaction_for_oom的值是等于true还是false。

       一旦决定给Non-Moving Space一个独立的地址空间,那么就会调用MemMap类的静态成员函数MapAnonymous创建一块匿名共享内存non_moving_space_mem_map,以便接下来可以用来创建Non-Moving Space。注意,这块匿名共享内存的起始地址紧接着在boot.art@classes.oat的末尾。同时,其它的Space的起始地址request_begin被修改为300MB地址处,即它们不再是紧跟着Non-Moving Space的末尾。

       第三段代码用来创建另外两块匿名共享内存,如下所示:

  // Attempt to create 2 mem maps at or after the requested begin.
  main_mem_map_1.reset(MapAnonymousPreferredAddress(kMemMapSpaceName[0], request_begin, capacity_,
                                                    PROT_READ | PROT_WRITE, &error_str));
  ......
  if (support_homogeneous_space_compaction ||
      background_collector_type_ == kCollectorTypeSS ||
      foreground_collector_type_ == kCollectorTypeSS) {
    main_mem_map_2.reset(MapAnonymousPreferredAddress(kMemMapSpaceName[1], main_mem_map_1->End(),
                                                      capacity_, PROT_READ | PROT_WRITE,
                                                      &error_str));
    ......
  }
       第一块匿名共享内存main_mem_map_1用来创建Compacting GC的From Bump Pointer Space或者Mark-Sweep GC的Main Space。第二块匿名共享内存main_mem_map_2用来创建Semi-Space GC的To Bump Pointer Space或者Mark-Sweep GC的Backup Space。注意,第二块匿名共享内存main_mem_map_2紧跟在第一块匿名共享内存main_mem_map_1的末尾。

       第四段代码用来创建Non-Moving Space,如下所示:

  // Create the non moving space first so that bitmaps don't take up the address range.
  if (separate_non_moving_space) {
    // Non moving space is always dlmalloc since we currently don't have support for multiple
    // active rosalloc spaces.
    const size_t size = non_moving_space_mem_map->Size();
    non_moving_space_ = space::DlMallocSpace::CreateFromMemMap(
        non_moving_space_mem_map.release(), "zygote / non moving space", kDefaultStartingSize,
        initial_size, size, size, false);
    non_moving_space_->SetFootprintLimit(non_moving_space_->Capacity());
    ......
    AddSpace(non_moving_space_);
  }
       只有在本地变量separate_non_moving_space等于true的情况下,也就是要给Non-Moving Space一块独立的地址空间的情况下,这里才会将前面创建的匿名共享内存non_moving_space_mem_map封装成一个DlMallocSpace,作为一块独立的Non-Moving Space使用。

       第五段代码用来为Compacting GC创建Bump Pointer Space或者为Mark-Sweep GC创建Main Space和Backup Space,如下所示:

  // Create other spaces based on whether or not we have a moving GC.
  if (IsMovingGc(foreground_collector_type_) && foreground_collector_type_ != kCollectorTypeGSS) {
    // Create bump pointer spaces.
    // We only to create the bump pointer if the foreground collector is a compacting GC.
    // TODO: Place bump-pointer spaces somewhere to minimize size of card table.
    bump_pointer_space_ = space::BumpPointerSpace::CreateFromMemMap("Bump pointer space 1",
                                                                    main_mem_map_1.release());
    ......
    AddSpace(bump_pointer_space_);
    temp_space_ = space::BumpPointerSpace::CreateFromMemMap("Bump pointer space 2",
                                                            main_mem_map_2.release());
    ......
    AddSpace(temp_space_);
    ......
  } else {
    CreateMainMallocSpace(main_mem_map_1.release(), initial_size, growth_limit_, capacity_);
    ......
    AddSpace(main_space_);
    if (!separate_non_moving_space) {
      non_moving_space_ = main_space_;
      ......
    }
    if (foreground_collector_type_ == kCollectorTypeGSS) {
      ......
      // Create bump pointer spaces instead of a backup space.
      main_mem_map_2.release();
      bump_pointer_space_ = space::BumpPointerSpace::Create("Bump pointer space 1",
                                                            kGSSBumpPointerSpaceCapacity, nullptr);
      ......
      AddSpace(bump_pointer_space_);
      temp_space_ = space::BumpPointerSpace::Create("Bump pointer space 2",
                                                    kGSSBumpPointerSpaceCapacity, nullptr);
      ......
      AddSpace(temp_space_);
    } else if (main_mem_map_2.get() != nullptr) {
      const char* name = kUseRosAlloc ? kRosAllocSpaceName[1] : kDlMallocSpaceName[1];
      main_space_backup_.reset(CreateMallocSpaceFromMemMap(main_mem_map_2.release(), initial_size,
                                                           growth_limit_, capacity_, name, true));
      ......
      // Add the space so its accounted for in the heap_begin and heap_end.
      AddSpace(main_space_backup_.get());
    }
  }
      当Foreground GC是Compacting GC,但是不是Generational Semi-Space GC时,分别是用前面创建的匿名共享内存main_mem_map_1和main_mem_map_2创建两个Bump Pointer Space,并且保存在Heap类的成员变量bump_pointer_space_和temp_space_中。

      当Foreground GC是Mark-Sweep GC或者Generational Semi-Space GC时,首先是调用Heap类的成员函数CreateMainMallocSpace创建一个Main Space,这个Main Space是一块DlMallocSpace或者RosAllocSpace,并且由Heap类的成员变量main_space_指向。

      如果Foreground GC是Generational Semi-Space GC,上面创建的Main Space实际上是作为Promote Space来使用的。同时由前面的分析可以知道,本地变量separate_non_moving_space的值这时候等于false,这意味着Non-Moving Space与上述Promote Space共享的是同一个地址空间。也就是此时ART运行时的Non-Moving Space(non_moving_space_)与Generational Semi-Space GC的Promote Space(main_space_)指向的是一个Space。接下来,上述代码还会继续为Generational Semi-Space GC创建一个From Bump Pointer Space和一个To Bump Pointer Space。这两个Bump Pointer Space是通过封装两块新创建的匿名共享内存得到的。

       如果Foreground GC是Mark-Sweep GC,则它们所需要的Main Space前面已经创建完毕,现在只需要再创建一个Backup Space即可。通过调用Heap类的成员函数CreateMallocSpaceFromMemMap即可创建一个DlMallocSpace或者RosAllocSpace,以作为Backup Space使用,并且由Heap类的成员变量main_space_backup_指向。

       接下来我们继续分析Heap类的成员函数CreateMainMallocSpace的实现,以便可以了解Main Space的创建过程,而且从中也可以看到用来创建Backup Space的Heap类的成员函数CreateMallocSpaceFromMemMap的实现,如下所示:

void Heap::CreateMainMallocSpace(MemMap* mem_map, size_t initial_size, size_t growth_limit,
                                 size_t capacity) {
  // Is background compaction is enabled?
  bool can_move_objects = IsMovingGc(background_collector_type_) !=
      IsMovingGc(foreground_collector_type_) || use_homogeneous_space_compaction_for_oom_;
  // If we are the zygote and don't yet have a zygote space, it means that the zygote fork will
  // happen in the future. If this happens and we have kCompactZygote enabled we wish to compact
  // from the main space to the zygote space. If background compaction is enabled, always pass in
  // that we can move objets.
  if (kCompactZygote && Runtime::Current()->IsZygote() && !can_move_objects) {
    // After the zygote we want this to be false if we don't have background compaction enabled so
    // that getting primitive array elements is faster.
    // We never have homogeneous compaction with GSS and don't need a space with movable objects.
    can_move_objects = !have_zygote_space_ && foreground_collector_type_ != kCollectorTypeGSS;
  }
  if (collector::SemiSpace::kUseRememberedSet && main_space_ != nullptr) {
    RemoveRememberedSet(main_space_);
  }
  const char* name = kUseRosAlloc ? kRosAllocSpaceName[0] : kDlMallocSpaceName[0];
  main_space_ = CreateMallocSpaceFromMemMap(mem_map, initial_size, growth_limit, capacity, name,
                                            can_move_objects);
  SetSpaceAsDefault(main_space_);
  VLOG(heap) << "Created main space " << main_space_;
}
       这个函数定义在文件art/runtime/runtime.cc中。

       一般来说,在以下两种情况下,Main Space的对象可以移动:

       1. Foreground GC和Background GC不同时为Compacting GC或者Mark-Sweep GC。这是因为当发生Foreground GC和Background GC切换时,如果Foreground GC和Background GC不同时为Compacting GC或者Mark-Sweep GC时,需要将对象从Main Space移动到Bump Pointer Space,或者从Bump Pointer Space移动到Main Space。

       2. ART运行时分配对象发生OOM时支持Homogeneous-Space-Compact特性。这时候需要将Main Space的对象移动到Backup Space。

       还有一种特殊情况,要求Main Space上的对象是可以移动的。前面提到,Zygote进程在fork第一个应用程序进程之前,会对堆进行一次Semi-Space GC。取决于当前的Foreground GC是Compacting GC还是Mark-Sweep GC,这次Semi-Space GC的From Space即为Compacting GC当前使用的Bump Pointer Space或者Mark-Sweep GC的Main Space。不过这样的Semi-Space GC是要在常量kCompactZygote设置为true的情况下才会执行。

       根据前面的分析,在Foreground GC是Generational Semi-Space GC的情况下,这里创建的Main Space同时也作为Generational Semi-Space GC的Promote Space,这就要求Main Space是不能移动对象的。 

       有了这些背景知识后,就可以很容易理解Heap类的成员函数CreateMallocSpaceFromMemMap的实现了。首先,语句IsMovingGc(background_collector_type_) != IsMovingGc(foreground_collector_type_)就是用来判断Foreground GC和Background GC不同时为Compacting GC或者Mark-Sweep GC的。其次,use_homogeneous_space_compaction_for_oom_代表ART运行时分配对象发生OOM时支持Homogeneous-Space-Compact特性。

       如果经过上面的处理之后,本地变量can_move_objects的值仍然为false,并且当前是运行在Zygote模式中(Runtime::Current()->IsZygote()等于true)、常量kCompactZygote为true,那么就会接着判断当前是否处于Zygote进程fork第一个应用程序进程之前,即Heap类的成员变量have_zygote_space_等于false。如果是的话,那么就会在当前的Foreground GC不是Generational Semi-Space GC的情况下,将本地变量can_move_objects修改为true,以便接下来调用Heap类的成员函数CreateMallocSpaceFromMemMap创建一个DlMallocSpace或者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(mem_map, name, kDefaultStartingSize,
                                                          initial_size, growth_limit, capacity,
                                                          low_memory_mode_, can_move_objects);
  } else {
    malloc_space = space::DlMallocSpace::CreateFromMemMap(mem_map, 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;
}
       这个函数定义在文件art/runtime/runtime.cc中。

       如果常量kUseRosAlloc的值等于true,那么就Heap类的成员函数CreateMallocSpaceFromMemMap创建的是一个RosAllocSpace;否则的话,创建的是一个DlMallocSpace。同时,如果常量collector::SemiSpace::kUseRememberedSet的值等于true,那么就为前面创建的RosAllocSpace或者DlMallocSpace创建一个RememberedSet。RememberedSet与在前面ART运行时垃圾收集机制简要介绍和学习计划这个系列文章提到的ModUnionTable的作用类似,都是用来记录被修改对象对指定目标空间的对象的引用情况的。

       回到Heap类的成员函数CreateMainMallocSpace中,调用Heap类的成员函数CreateMallocSpaceFromMemMap创建完成Main Space之后,还会调用另外一个成员函数SetSpaceAsDefault将该Main Space设置为当前Mark-Sweep GC使用的Main Space或者当前Generational Semi-Space GC使用的Promote Space,如下所示:

void Heap::SetSpaceAsDefault(space::ContinuousSpace* continuous_space) {
  WriterMutexLock mu(Thread::Current(), *Locks::heap_bitmap_lock_);
  if (continuous_space->IsDlMallocSpace()) {
    dlmalloc_space_ = continuous_space->AsDlMallocSpace();
  } else if (continuous_space->IsRosAllocSpace()) {
    rosalloc_space_ = continuous_space->AsRosAllocSpace();
  }
}
       这个函数定义在文件art/runtime/runtime.cc中。

       如果前面创建的Main Space是一个DlMallocSpace,那么就将它保存在Heap类的成员变量dlmalloc_space_中;否则的话,如果是一个RosAllocSpace,就保存在Heap类的成员变量rosalloc_space_中。

       设置好Heap类的成员变量dlmalloc_space_和rosalloc_space_之后,以后在分配对象时,就可以通过kAllocatorTypeDlMalloc或者kAllocatorTypeRosAlloc常量指定是要DlMallocSpace中分配对象,还是在RosAllocSpace中分配对象,以及Generational Semi-Space GC可以通过它们获得对应的Promote Space来保存那些经过若干轮GC仍然存活下来的对象。

       代码分析到这里,我们就基本上把图1、图2和图3涉及到的知识都解释完毕,大家可以对照着重新理解一下。不过,在图1、图2和图3中,我们还没有解释到的一个点就是Zygote Space是怎么来的。接下来我们就继续分析Zygote Space的创建过程。

       Zygote Space是从Non-Moving Space分割而来的。具体来说,就是在Zygote进程fork第一个应用程序进程之前,会调用Heap类的成员函数PreZygoteFork创建一个Zygote Space,它的实现如下所示:

void Heap::PreZygoteFork() {
  CollectGarbageInternal(collector::kGcTypeFull, kGcCauseBackground, false);
  Thread* self = Thread::Current();
  MutexLock mu(self, zygote_creation_lock_);
  // Try to see if we have any Zygote spaces.
  if (have_zygote_space_) {
    return;
  }
  ......
  // Trim the pages at the end of the non moving space.
  non_moving_space_->Trim();
  // The end of the non-moving space may be protected, unprotect it so that we can copy the zygote
  // there.
  non_moving_space_->GetMemMap()->Protect(PROT_READ | PROT_WRITE);
  const bool same_space = non_moving_space_ == main_space_;
  if (kCompactZygote) {
    ......
    // Temporarily disable rosalloc verification because the zygote
    // compaction will mess up the rosalloc internal metadata.
    ScopedDisableRosAllocVerification disable_rosalloc_verif(this);
    ZygoteCompactingCollector zygote_collector(this);
    zygote_collector.BuildBins(non_moving_space_);
    // Create a new bump pointer space which we will compact into.
    space::BumpPointerSpace target_space("zygote bump space", non_moving_space_->End(),
                                         non_moving_space_->Limit());
    // Compact the bump pointer space to a new zygote bump pointer space.
    bool reset_main_space = false;
    if (IsMovingGc(collector_type_)) {
      zygote_collector.SetFromSpace(bump_pointer_space_);
    } else {
      ......
      // Copy from the main space.
      zygote_collector.SetFromSpace(main_space_);
      reset_main_space = true;
    }
    zygote_collector.SetToSpace(&target_space);
    zygote_collector.SetSwapSemiSpaces(false);
    zygote_collector.Run(kGcCauseCollectorTransition, false);
    if (reset_main_space) {
      main_space_->GetMemMap()->Protect(PROT_READ | PROT_WRITE);
      madvise(main_space_->Begin(), main_space_->Capacity(), MADV_DONTNEED);
      MemMap* mem_map = main_space_->ReleaseMemMap();
      RemoveSpace(main_space_);
      space::Space* old_main_space = main_space_;
      CreateMainMallocSpace(mem_map, kDefaultInitialSize, mem_map->Size(), mem_map->Size());
      delete old_main_space;
      AddSpace(main_space_);
    } 
    ......
  }
  ......
  space::MallocSpace* old_alloc_space = non_moving_space_;
  ......
  space::ZygoteSpace* zygote_space = old_alloc_space->CreateZygoteSpace("alloc space",
                                                                        low_memory_mode_,
                                                                        &non_moving_space_);
  ......
  if (same_space) {
    main_space_ = non_moving_space_;
    SetSpaceAsDefault(main_space_);
  }
  delete old_alloc_space;
  ......
  AddSpace(zygote_space);
  non_moving_space_->SetFootprintLimit(non_moving_space_->Capacity());
  AddSpace(non_moving_space_);
  have_zygote_space_ = true;
  ......
  accounting::ModUnionTable* mod_union_table =
      new accounting::ModUnionTableCardCache("zygote space mod-union table", this, zygote_space);
  ......
  AddModUnionTable(mod_union_table);
  if (collector::SemiSpace::kUseRememberedSet) {
    // Add a new remembered set for the post-zygote non-moving space.
    accounting::RememberedSet* post_zygote_non_moving_space_rem_set =
        new accounting::RememberedSet("Post-zygote non-moving space remembered set", this,
                                      non_moving_space_);
    ......
    AddRememberedSet(post_zygote_non_moving_space_rem_set);
  }
}
       这个函数定义在文件art/runtime/runtime.cc中。

       Heap类的成员函数PreZygoteFork用来从Non-Moving Space中分割出一个Zygote Space来。在分割之前,如果需要的话,还会对Main Space或者Bump Pointer Space的对象进行压缩处理。我们分成三小段代码来阅读这个函数。

       第一段代码是对堆进行一次GC和裁剪处理,如下所示:

  CollectGarbageInternal(collector::kGcTypeFull, kGcCauseBackground, false);
  Thread* self = Thread::Current();
  MutexLock mu(self, zygote_creation_lock_);
  // Try to see if we have any Zygote spaces.
  if (have_zygote_space_) {
    return;
  }
  ......
  // Trim the pages at the end of the non moving space.
  non_moving_space_->Trim();
  // The end of the non-moving space may be protected, unprotect it so that we can copy the zygote
  // there.
  non_moving_space_->GetMemMap()->Protect(PROT_READ | PROT_WRITE);
  const bool same_space = non_moving_space_ == main_space_;
       对堆进行GC处理是通过调用Heap类的成员函数CollectGarbageInternal来实现的。接下来,判断Heap类的成员变量have_zygote_space_的值是否等于true。如果等于的话,就说明Zygote Space已经创建过了,因此就不用再往下处理。否则的话,再继续调用Heap类的成员变量non_moving_space_指向的一个DlMallocSpace或者RosAllocSpace对象的成员函数Trim对它未使用的内存进行裁剪,即将这些未使用的内存归还给内核。最后,将Non-Moving Space内部使用的匿名共享内存块设置为可读可写,并且记录好Non-Moving Space和Main Space是否共用同一块匿名共享内存。这里之所以要将Non-Moving Space内部使用的匿名共享内存块设置为可读可写,是因为接下来我们要将Main Space或者Bump Pointer Space上的对象移动到Non-Moving Space上去。

      第二段代码是将Main Space或者Bump Pointer Space上的对象移动到Non-Moving Space上去,如下所示:

  if (kCompactZygote) {
    ......
    // Temporarily disable rosalloc verification because the zygote
    // compaction will mess up the rosalloc internal metadata.
    ScopedDisableRosAllocVerification disable_rosalloc_verif(this);
    ZygoteCompactingCollector zygote_collector(this);
    zygote_collector.BuildBins(non_moving_space_);
    // Create a new bump pointer space which we will compact into.
    space::BumpPointerSpace target_space("zygote bump space", non_moving_space_->End(),
                                         non_moving_space_->Limit());
    // Compact the bump pointer space to a new zygote bump pointer space.
    bool reset_main_space = false;
    if (IsMovingGc(collector_type_)) {
      zygote_collector.SetFromSpace(bump_pointer_space_);
    } else {
      ......
      // Copy from the main space.
      zygote_collector.SetFromSpace(main_space_);
      reset_main_space = true;
    }
    zygote_collector.SetToSpace(&target_space);
    zygote_collector.SetSwapSemiSpaces(false);
    zygote_collector.Run(kGcCauseCollectorTransition, false);
    if (reset_main_space) {
      main_space_->GetMemMap()->Protect(PROT_READ | PROT_WRITE);
      madvise(main_space_->Begin(), main_space_->Capacity(), MADV_DONTNEED);
      MemMap* mem_map = main_space_->ReleaseMemMap();
      RemoveSpace(main_space_);
      space::Space* old_main_space = main_space_;
      CreateMainMallocSpace(mem_map, kDefaultInitialSize, mem_map->Size(), mem_map->Size());
      delete old_main_space;
      AddSpace(main_space_);
    } 
    ......
  }
       当常量kCompactZygote的值等于true的情况下,就需要将当前Main Space或者Bump Pointer Space上的对象移动到Non-Moving Space上去。

       这个移动的过程是通过一个类型为ZygoteCompactingCollector的垃圾收集器来完成的。ZygoteCompactingCollector是从SemiSpace继承下来的,这意味着它是通过执行一次Semi-Space GC来完成对象的移动过程的。

       实际上,ZygoteCompactingCollector执行的是一次特殊的Semi-Space GC。通常我们执行Semi-Space GC时,涉及到From和To两个Space。其中,From Space包含有对象,而To Space完全没有包含对象。这样当我们将对象从From Space移动到To Space时,就从To Space的起始位置开始保存对象。但是对于ZygoteCompactingCollector来说,它需要将Main Space或者Bump Pointer Space的对象移动到Non-Moving Space上去,但是Non-Moving Space这时候可能不是空的,也就是说,在上面已经存在一些对象,而且这些对象在地址空间上可能不是连续地存在的。

       在移动对象之前,ZygoteCompactingCollector将Non-Moving Space分为两部分。第一部分是前面包含有对象的空间,这部分空间可能存在一些空闲内存,因此就调用ZygoteCompactingCollector类的成员函数BuildBins将这些空闲内存块的起始地址和大小记录起来。第二部分是后面完全没有包含对象的空间,这部分空间被封装为一个BumpPointerSpace,作为ZygoteCompactingCollector的To Space。于是在移动对象到Non-Moving Space上的时候,就会优先考虑前面的空闲内存块是否合适用来保存一个被移动对象。如果合适的话,就使用它;否则的话,再将被移动对象保存在To Space中。通过这种方式,就可以最有效地利用Non-Moving Space,尽最大限度减小内存碎片。

       设置好ZygoteCompactingCollector的To Space之后,接下来再设置它的From Space。如果当前的Foreground GC是一个Compacting GC,那么就意味着当前使用的Space是一个Bump Pointer Space,该Bump Pointer Space由Heap类的成员变量bump_pointer_space_指向。否则的话,当前的Foreground GC就是一个Mark-Sweep GC,这意味着当前使用的Space是一个Main Space,该Main Space由Heap类的成员变量main_space_指向。在后一种情况下,还需要将本地变量reset_main_space的设置为true,表示在移动对象完成之后,需要重置Main Space。

       设置好ZygoteCompactingCollector的From Space和To Space之后,就可以调用它的成员函数Run进行Semi-Space GC了。Semi-Space GC执行完毕,如果前面将本地变量reset_main_space的设置为true,就说明我们是将Main Space上的对象移动到了Non-Moving Space中。这时候Main Space就没有什么作用了,这时候就可以将Main Space之前占用的所有内存都可以归还给内核。但是,我们还是需要有一个Main Space的,因此,就再重新调用我们前面分析过的Heap类的成员函数CreateMainMallocSpace创建一个Main Space,并且将新创建的Main Space最初始占用的内存大小设置为kDefaultInitialSize。通过这个重置操作,就可以减少Main Space占用的内存。

       第三段代码执行从Non-Moving Space分离出Zygote Space的操作,如下所示:

  space::MallocSpace* old_alloc_space = non_moving_space_;
  ......
  space::ZygoteSpace* zygote_space = old_alloc_space->CreateZygoteSpace("alloc space",
                                                                        low_memory_mode_,
                                                                        &non_moving_space_);
  ......
  if (same_space) {
    main_space_ = non_moving_space_;
    SetSpaceAsDefault(main_space_);
  }
  delete old_alloc_space;
  ......
  AddSpace(zygote_space);
  non_moving_space_->SetFootprintLimit(non_moving_space_->Capacity());
  AddSpace(non_moving_space_);
  have_zygote_space_ = true;
  ......
  accounting::ModUnionTable* mod_union_table =
      new accounting::ModUnionTableCardCache("zygote space mod-union table", this, zygote_space);
  ......
  AddModUnionTable(mod_union_table);
  if (collector::SemiSpace::kUseRememberedSet) {
    // Add a new remembered set for the post-zygote non-moving space.
    accounting::RememberedSet* post_zygote_non_moving_space_rem_set =
        new accounting::RememberedSet("Post-zygote non-moving space remembered set", this,
                                      non_moving_space_);
    ......
    AddRememberedSet(post_zygote_non_moving_space_rem_set);
  }

       本地变量old_alloc_space指向旧的Non-Moving Space,通过调用它的成员函数CreateZygoteSpace可以从里面分割出一个Zygote Space出来,保存在本地变量zygote_space中,并且新的Non-Moving Space仍然保存在Heap类的成员变量non_moving_space_中。从Non-Moving Space分割出一个Zygote Space可以参考前面ART运行时Java堆创建过程分析一文分析的从Allocation Space分割出Zygote Space的方法。

       从旧的Non-Moving Space分割出Zygote Space和新的Non-Moving Space之后,如果前面记录了Main Space和Non-Moving Space共享的是同一块地址空间,那么同时也需要修改Heap类的成员变量main_space_的值,使得它与新的Non-Moving Space指向的是同一块地址空间,并且调用Heap类的成员函数SetSpaceAsDefault将新的Main Space设置为当前使用的DlMallocSpace或者RosAllocSpace。

       接下来还需要将Heap类的成员变量have_zygote_space_设置为true,表示Zygote Space已经从Non-Moving Space分割出来了。最后,还要为新创建的Zygote Space创建一个ModUnionTable,用来记录该Space的对象被修改时对其它Space的引用情况。同时,在常量collector::SemiSpace::kUseRememberedSet为true的情况下,为新的Non-Moving Space创建一个RememberedSet,同样是用来记录该Space的对象被修改时对其它Space的引用情况。

       这样,从Non-Moving Space中分割出Zygote Space的总体过程就分析完成了。由于具体过程涉及到Semi-Space GC,这里就没有进一步展开来说,不过接下来我们会有专门的文章分析Semi-Space GC的执行过程,到时候再回过头分析从Non-Moving Space中分割出Zygote Space的具体过程就会容易很多了。

       至此,图1、图2和图3涉及到的所有知识点就分析完成了,从中我们就可以了解到ART运行时引进了Compacting GC之后,内部的堆空间结构组成。这对我们后面理解ART运行时的对象分配过程以及Compacting GC的执行过程都是非常重要的。在接下来的一篇文章中,我们就对引进了Compacting GC之后的ART运行时的对象分配过程进行分析,敬请关注!更多信息也可以关注老罗的新浪微博:http://weibo.com/shengyangluo

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值