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

9073次阅读  |  发布于5年以前

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

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

从前面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_spacecapacity:Non-Moving Space的大小,可以通过ART运行时启动选项-XX:NonMovingSpaceCapacity来指定,默认大小为kDefaultNonMovingSpaceCapacity(64MB)。

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

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

options->min_interval_homogeneous_space_compaction_byoom: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_collectortype)和Background GC(background_collectortype)是否是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_collectortype)被指定为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_movingspace)与Generational Semi-Space GC的Promote Space(mainspace)指向的是一个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_collectortype) != IsMovingGc(foreground_collectortype)就是用来判断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

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8