打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

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

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

《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对象开始的,如下所示:

  1. bool Runtime::Init(const RuntimeOptions& raw_options, bool ignore_unrecognized) {
  2. ......
  3. heap_ = new gc::Heap(options->heap_initial_size_,
  4. options->heap_growth_limit_,
  5. options->heap_min_free_,
  6. options->heap_max_free_,
  7. options->heap_target_utilization_,
  8. options->foreground_heap_growth_multiplier_,
  9. options->heap_maximum_size_,
  10. options->heap_non_moving_space_capacity_,
  11. options->image_,
  12. options->image_isa_,
  13. options->collector_type_,
  14. options->background_collector_type_,
  15. options->parallel_gc_threads_,
  16. options->conc_gc_threads_,
  17. options->low_memory_mode_,
  18. options->long_pause_log_threshold_,
  19. options->long_gc_log_threshold_,
  20. options->ignore_max_footprint_,
  21. options->use_tlab_,
  22. options->verify_pre_gc_heap_,
  23. options->verify_pre_sweeping_heap_,
  24. options->verify_post_gc_heap_,
  25. options->verify_pre_gc_rosalloc_,
  26. options->verify_pre_sweeping_rosalloc_,
  27. options->verify_post_gc_rosalloc_,
  28. options->use_homogeneous_space_compaction_for_oom_,
  29. options->min_interval_homogeneous_space_compaction_by_oom_);
  30. ......
  31. }
      这个函数定义在文件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对象的创建和初始化过程如下所示:

  1. Heap::Heap(size_t initial_size, size_t growth_limit, size_t min_free, size_t max_free,
  2. double target_utilization, double foreground_heap_growth_multiplier,
  3. size_t capacity, size_t non_moving_space_capacity, const std::string& image_file_name,
  4. const InstructionSet image_instruction_set, CollectorType foreground_collector_type,
  5. CollectorType background_collector_type, size_t parallel_gc_threads,
  6. size_t conc_gc_threads, bool low_memory_mode,
  7. size_t long_pause_log_threshold, size_t long_gc_log_threshold,
  8. bool ignore_max_footprint, bool use_tlab,
  9. bool verify_pre_gc_heap, bool verify_pre_sweeping_heap, bool verify_post_gc_heap,
  10. bool verify_pre_gc_rosalloc, bool verify_pre_sweeping_rosalloc,
  11. bool verify_post_gc_rosalloc, bool use_homogeneous_space_compaction_for_oom,
  12. uint64_t min_interval_homogeneous_space_compaction_by_oom)
  13. ......
  14. byte* requested_alloc_space_begin = nullptr;
  15. if (!image_file_name.empty()) {
  16. std::string error_msg;
  17. space::ImageSpace* image_space = space::ImageSpace::Create(image_file_name.c_str(),
  18. image_instruction_set,
  19. &error_msg);
  20. if (image_space != nullptr) {
  21. AddSpace(image_space);
  22. // Oat files referenced by image files immediately follow them in memory, ensure alloc space
  23. // isn't going to get in the middle
  24. byte* oat_file_end_addr = image_space->GetImageHeader().GetOatFileEnd();
  25. ......
  26. requested_alloc_space_begin = AlignUp(oat_file_end_addr, kPageSize);
  27. }
  28. ......
  29. }
  30. ......
  31. bool support_homogeneous_space_compaction =
  32. background_collector_type_ == gc::kCollectorTypeHomogeneousSpaceCompact ||
  33. use_homogeneous_space_compaction_for_oom;
  34. // We may use the same space the main space for the non moving space if we don't need to compact
  35. // from the main space.
  36. // This is not the case if we support homogeneous compaction or have a moving background
  37. // collector type.
  38. bool separate_non_moving_space = is_zygote ||
  39. support_homogeneous_space_compaction || IsMovingGc(foreground_collector_type_) ||
  40. IsMovingGc(background_collector_type_);
  41. if (foreground_collector_type == kCollectorTypeGSS) {
  42. separate_non_moving_space = false;
  43. }
  44. std::unique_ptr main_mem_map_1;
  45. std::unique_ptr main_mem_map_2;
  46. byte* request_begin = requested_alloc_space_begin;
  47. if (request_begin != nullptr && separate_non_moving_space) {
  48. request_begin += non_moving_space_capacity;
  49. }
  50. ......
  51. std::unique_ptr non_moving_space_mem_map;
  52. if (separate_non_moving_space) {
  53. // Reserve the non moving mem map before the other two since it needs to be at a specific
  54. // address.
  55. non_moving_space_mem_map.reset(
  56. MemMap::MapAnonymous('non moving space', requested_alloc_space_begin,
  57. non_moving_space_capacity, PROT_READ | PROT_WRITE, true, &error_str));
  58. ......
  59. // Try to reserve virtual memory at a lower address if we have a separate non moving space.
  60. request_begin = reinterpret_cast(300 * MB);
  61. }
  62. // Attempt to create 2 mem maps at or after the requested begin.
  63. main_mem_map_1.reset(MapAnonymousPreferredAddress(kMemMapSpaceName[0], request_begin, capacity_,
  64. PROT_READ | PROT_WRITE, &error_str));
  65. ......
  66. if (support_homogeneous_space_compaction ||
  67. background_collector_type_ == kCollectorTypeSS ||
  68. foreground_collector_type_ == kCollectorTypeSS) {
  69. main_mem_map_2.reset(MapAnonymousPreferredAddress(kMemMapSpaceName[1], main_mem_map_1->End(),
  70. capacity_, PROT_READ | PROT_WRITE,
  71. &error_str));
  72. ......
  73. }
  74. // Create the non moving space first so that bitmaps don't take up the address range.
  75. if (separate_non_moving_space) {
  76. // Non moving space is always dlmalloc since we currently don't have support for multiple
  77. // active rosalloc spaces.
  78. const size_t size = non_moving_space_mem_map->Size();
  79. non_moving_space_ = space::DlMallocSpace::CreateFromMemMap(
  80. non_moving_space_mem_map.release(), 'zygote / non moving space', kDefaultStartingSize,
  81. initial_size, size, size, false);
  82. non_moving_space_->SetFootprintLimit(non_moving_space_->Capacity());
  83. ......
  84. AddSpace(non_moving_space_);
  85. }
  86. // Create other spaces based on whether or not we have a moving GC.
  87. if (IsMovingGc(foreground_collector_type_) && foreground_collector_type_ != kCollectorTypeGSS) {
  88. // Create bump pointer spaces.
  89. // We only to create the bump pointer if the foreground collector is a compacting GC.
  90. // TODO: Place bump-pointer spaces somewhere to minimize size of card table.
  91. bump_pointer_space_ = space::BumpPointerSpace::CreateFromMemMap('Bump pointer space 1',
  92. main_mem_map_1.release());
  93. ......
  94. AddSpace(bump_pointer_space_);
  95. temp_space_ = space::BumpPointerSpace::CreateFromMemMap('Bump pointer space 2',
  96. main_mem_map_2.release());
  97. ......
  98. AddSpace(temp_space_);
  99. ......
  100. } else {
  101. CreateMainMallocSpace(main_mem_map_1.release(), initial_size, growth_limit_, capacity_);
  102. ......
  103. AddSpace(main_space_);
  104. if (!separate_non_moving_space) {
  105. non_moving_space_ = main_space_;
  106. ......
  107. }
  108. if (foreground_collector_type_ == kCollectorTypeGSS) {
  109. ......
  110. // Create bump pointer spaces instead of a backup space.
  111. main_mem_map_2.release();
  112. bump_pointer_space_ = space::BumpPointerSpace::Create('Bump pointer space 1',
  113. kGSSBumpPointerSpaceCapacity, nullptr);
  114. ......
  115. AddSpace(bump_pointer_space_);
  116. temp_space_ = space::BumpPointerSpace::Create('Bump pointer space 2',
  117. kGSSBumpPointerSpaceCapacity, nullptr);
  118. ......
  119. AddSpace(temp_space_);
  120. } else if (main_mem_map_2.get() != nullptr) {
  121. const char* name = kUseRosAlloc ? kRosAllocSpaceName[1] : kDlMallocSpaceName[1];
  122. main_space_backup_.reset(CreateMallocSpaceFromMemMap(main_mem_map_2.release(), initial_size,
  123. growth_limit_, capacity_, name, true));
  124. ......
  125. // Add the space so its accounted for in the heap_begin and heap_end.
  126. AddSpace(main_space_backup_.get());
  127. }
  128. }
  129. ......
  130. }

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

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

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

  1. byte* requested_alloc_space_begin = nullptr;
  2. if (!image_file_name.empty()) {
  3. std::string error_msg;
  4. space::ImageSpace* image_space = space::ImageSpace::Create(image_file_name.c_str(),
  5. image_instruction_set,
  6. &error_msg);
  7. if (image_space != nullptr) {
  8. AddSpace(image_space);
  9. // Oat files referenced by image files immediately follow them in memory, ensure alloc space
  10. // isn't going to get in the middle
  11. byte* oat_file_end_addr = image_space->GetImageHeader().GetOatFileEnd();
  12. ......
  13. requested_alloc_space_begin = AlignUp(oat_file_end_addr, kPageSize);
  14. }
  15. ......
  16. }
      关于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的,如下所示:

  1. bool support_homogeneous_space_compaction =
  2. background_collector_type_ == gc::kCollectorTypeHomogeneousSpaceCompact ||
  3. use_homogeneous_space_compaction_for_oom;
  4. // We may use the same space the main space for the non moving space if we don't need to compact
  5. // from the main space.
  6. // This is not the case if we support homogeneous compaction or have a moving background
  7. // collector type.
  8. bool separate_non_moving_space = is_zygote ||
  9. support_homogeneous_space_compaction || IsMovingGc(foreground_collector_type_) ||
  10. IsMovingGc(background_collector_type_);
  11. if (foreground_collector_type == kCollectorTypeGSS) {
  12. separate_non_moving_space = false;
  13. }
  14. std::unique_ptr main_mem_map_1;
  15. std::unique_ptr main_mem_map_2;
  16. byte* request_begin = requested_alloc_space_begin;
  17. if (request_begin != nullptr && separate_non_moving_space) {
  18. request_begin += non_moving_space_capacity;
  19. }
  20. ......
  21. std::unique_ptr non_moving_space_mem_map;
  22. if (separate_non_moving_space) {
  23. // Reserve the non moving mem map before the other two since it needs to be at a specific
  24. // address.
  25. non_moving_space_mem_map.reset(
  26. MemMap::MapAnonymous('non moving space', requested_alloc_space_begin,
  27. non_moving_space_capacity, PROT_READ | PROT_WRITE, true, &error_str));
  28. ......
  29. // Try to reserve virtual memory at a lower address if we have a separate non moving space.
  30. request_begin = reinterpret_cast(300 * MB);
  31. }

       这段代码的逻辑是判断是否需要给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的末尾。

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

  1. // Attempt to create 2 mem maps at or after the requested begin.
  2. main_mem_map_1.reset(MapAnonymousPreferredAddress(kMemMapSpaceName[0], request_begin, capacity_,
  3. PROT_READ | PROT_WRITE, &error_str));
  4. ......
  5. if (support_homogeneous_space_compaction ||
  6. background_collector_type_ == kCollectorTypeSS ||
  7. foreground_collector_type_ == kCollectorTypeSS) {
  8. main_mem_map_2.reset(MapAnonymousPreferredAddress(kMemMapSpaceName[1], main_mem_map_1->End(),
  9. capacity_, PROT_READ | PROT_WRITE,
  10. &error_str));
  11. ......
  12. }
       第一块匿名共享内存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,如下所示:

  1. // Create the non moving space first so that bitmaps don't take up the address range.
  2. if (separate_non_moving_space) {
  3. // Non moving space is always dlmalloc since we currently don't have support for multiple
  4. // active rosalloc spaces.
  5. const size_t size = non_moving_space_mem_map->Size();
  6. non_moving_space_ = space::DlMallocSpace::CreateFromMemMap(
  7. non_moving_space_mem_map.release(), 'zygote / non moving space', kDefaultStartingSize,
  8. initial_size, size, size, false);
  9. non_moving_space_->SetFootprintLimit(non_moving_space_->Capacity());
  10. ......
  11. AddSpace(non_moving_space_);
  12. }
       只有在本地变量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,如下所示:

  1. // Create other spaces based on whether or not we have a moving GC.
  2. if (IsMovingGc(foreground_collector_type_) && foreground_collector_type_ != kCollectorTypeGSS) {
  3. // Create bump pointer spaces.
  4. // We only to create the bump pointer if the foreground collector is a compacting GC.
  5. // TODO: Place bump-pointer spaces somewhere to minimize size of card table.
  6. bump_pointer_space_ = space::BumpPointerSpace::CreateFromMemMap('Bump pointer space 1',
  7. main_mem_map_1.release());
  8. ......
  9. AddSpace(bump_pointer_space_);
  10. temp_space_ = space::BumpPointerSpace::CreateFromMemMap('Bump pointer space 2',
  11. main_mem_map_2.release());
  12. ......
  13. AddSpace(temp_space_);
  14. ......
  15. } else {
  16. CreateMainMallocSpace(main_mem_map_1.release(), initial_size, growth_limit_, capacity_);
  17. ......
  18. AddSpace(main_space_);
  19. if (!separate_non_moving_space) {
  20. non_moving_space_ = main_space_;
  21. ......
  22. }
  23. if (foreground_collector_type_ == kCollectorTypeGSS) {
  24. ......
  25. // Create bump pointer spaces instead of a backup space.
  26. main_mem_map_2.release();
  27. bump_pointer_space_ = space::BumpPointerSpace::Create('Bump pointer space 1',
  28. kGSSBumpPointerSpaceCapacity, nullptr);
  29. ......
  30. AddSpace(bump_pointer_space_);
  31. temp_space_ = space::BumpPointerSpace::Create('Bump pointer space 2',
  32. kGSSBumpPointerSpaceCapacity, nullptr);
  33. ......
  34. AddSpace(temp_space_);
  35. } else if (main_mem_map_2.get() != nullptr) {
  36. const char* name = kUseRosAlloc ? kRosAllocSpaceName[1] : kDlMallocSpaceName[1];
  37. main_space_backup_.reset(CreateMallocSpaceFromMemMap(main_mem_map_2.release(), initial_size,
  38. growth_limit_, capacity_, name, true));
  39. ......
  40. // Add the space so its accounted for in the heap_begin and heap_end.
  41. AddSpace(main_space_backup_.get());
  42. }
  43. }
      当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的实现,如下所示:

  1. void Heap::CreateMainMallocSpace(MemMap* mem_map, size_t initial_size, size_t growth_limit,
  2. size_t capacity) {
  3. // Is background compaction is enabled?
  4. bool can_move_objects = IsMovingGc(background_collector_type_) !=
  5. IsMovingGc(foreground_collector_type_) || use_homogeneous_space_compaction_for_oom_;
  6. // If we are the zygote and don't yet have a zygote space, it means that the zygote fork will
  7. // happen in the future. If this happens and we have kCompactZygote enabled we wish to compact
  8. // from the main space to the zygote space. If background compaction is enabled, always pass in
  9. // that we can move objets.
  10. if (kCompactZygote && Runtime::Current()->IsZygote() && !can_move_objects) {
  11. // After the zygote we want this to be false if we don't have background compaction enabled so
  12. // that getting primitive array elements is faster.
  13. // We never have homogeneous compaction with GSS and don't need a space with movable objects.
  14. can_move_objects = !have_zygote_space_ && foreground_collector_type_ != kCollectorTypeGSS;
  15. }
  16. if (collector::SemiSpace::kUseRememberedSet && main_space_ != nullptr) {
  17. RemoveRememberedSet(main_space_);
  18. }
  19. const char* name = kUseRosAlloc ? kRosAllocSpaceName[0] : kDlMallocSpaceName[0];
  20. main_space_ = CreateMallocSpaceFromMemMap(mem_map, initial_size, growth_limit, capacity, name,
  21. can_move_objects);
  22. SetSpaceAsDefault(main_space_);
  23. VLOG(heap) <>'Created main space ' <>
  24. }
       这个函数定义在文件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,如下所示:

  1. space::MallocSpace* Heap::CreateMallocSpaceFromMemMap(MemMap* mem_map, size_t initial_size,
  2. size_t growth_limit, size_t capacity,
  3. const char* name, bool can_move_objects) {
  4. space::MallocSpace* malloc_space = nullptr;
  5. if (kUseRosAlloc) {
  6. // Create rosalloc space.
  7. malloc_space = space::RosAllocSpace::CreateFromMemMap(mem_map, name, kDefaultStartingSize,
  8. initial_size, growth_limit, capacity,
  9. low_memory_mode_, can_move_objects);
  10. } else {
  11. malloc_space = space::DlMallocSpace::CreateFromMemMap(mem_map, name, kDefaultStartingSize,
  12. initial_size, growth_limit, capacity,
  13. can_move_objects);
  14. }
  15. if (collector::SemiSpace::kUseRememberedSet) {
  16. accounting::RememberedSet* rem_set =
  17. new accounting::RememberedSet(std::string(name) + ' remembered set', this, malloc_space);
  18. CHECK(rem_set != nullptr) <>'Failed to create main space remembered set';
  19. AddRememberedSet(rem_set);
  20. }
  21. CHECK(malloc_space != nullptr) <>'Failed to create ' <>
  22. malloc_space->SetFootprintLimit(malloc_space->Capacity());
  23. return malloc_space;
  24. }
       这个函数定义在文件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,如下所示:

  1. void Heap::SetSpaceAsDefault(space::ContinuousSpace* continuous_space) {
  2. WriterMutexLock mu(Thread::Current(), *Locks::heap_bitmap_lock_);
  3. if (continuous_space->IsDlMallocSpace()) {
  4. dlmalloc_space_ = continuous_space->AsDlMallocSpace();
  5. } else if (continuous_space->IsRosAllocSpace()) {
  6. rosalloc_space_ = continuous_space->AsRosAllocSpace();
  7. }
  8. }
       这个函数定义在文件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,它的实现如下所示:

  1. void Heap::PreZygoteFork() {
  2. CollectGarbageInternal(collector::kGcTypeFull, kGcCauseBackground, false);
  3. Thread* self = Thread::Current();
  4. MutexLock mu(self, zygote_creation_lock_);
  5. // Try to see if we have any Zygote spaces.
  6. if (have_zygote_space_) {
  7. return;
  8. }
  9. ......
  10. // Trim the pages at the end of the non moving space.
  11. non_moving_space_->Trim();
  12. // The end of the non-moving space may be protected, unprotect it so that we can copy the zygote
  13. // there.
  14. non_moving_space_->GetMemMap()->Protect(PROT_READ | PROT_WRITE);
  15. const bool same_space = non_moving_space_ == main_space_;
  16. if (kCompactZygote) {
  17. ......
  18. // Temporarily disable rosalloc verification because the zygote
  19. // compaction will mess up the rosalloc internal metadata.
  20. ScopedDisableRosAllocVerification disable_rosalloc_verif(this);
  21. ZygoteCompactingCollector zygote_collector(this);
  22. zygote_collector.BuildBins(non_moving_space_);
  23. // Create a new bump pointer space which we will compact into.
  24. space::BumpPointerSpace target_space('zygote bump space', non_moving_space_->End(),
  25. non_moving_space_->Limit());
  26. // Compact the bump pointer space to a new zygote bump pointer space.
  27. bool reset_main_space = false;
  28. if (IsMovingGc(collector_type_)) {
  29. zygote_collector.SetFromSpace(bump_pointer_space_);
  30. } else {
  31. ......
  32. // Copy from the main space.
  33. zygote_collector.SetFromSpace(main_space_);
  34. reset_main_space = true;
  35. }
  36. zygote_collector.SetToSpace(&target_space);
  37. zygote_collector.SetSwapSemiSpaces(false);
  38. zygote_collector.Run(kGcCauseCollectorTransition, false);
  39. if (reset_main_space) {
  40. main_space_->GetMemMap()->Protect(PROT_READ | PROT_WRITE);
  41. madvise(main_space_->Begin(), main_space_->Capacity(), MADV_DONTNEED);
  42. MemMap* mem_map = main_space_->ReleaseMemMap();
  43. RemoveSpace(main_space_);
  44. space::Space* old_main_space = main_space_;
  45. CreateMainMallocSpace(mem_map, kDefaultInitialSize, mem_map->Size(), mem_map->Size());
  46. delete old_main_space;
  47. AddSpace(main_space_);
  48. }
  49. ......
  50. }
  51. ......
  52. space::MallocSpace* old_alloc_space = non_moving_space_;
  53. ......
  54. space::ZygoteSpace* zygote_space = old_alloc_space->CreateZygoteSpace('alloc space',
  55. low_memory_mode_,
  56. &non_moving_space_);
  57. ......
  58. if (same_space) {
  59. main_space_ = non_moving_space_;
  60. SetSpaceAsDefault(main_space_);
  61. }
  62. delete old_alloc_space;
  63. ......
  64. AddSpace(zygote_space);
  65. non_moving_space_->SetFootprintLimit(non_moving_space_->Capacity());
  66. AddSpace(non_moving_space_);
  67. have_zygote_space_ = true;
  68. ......
  69. accounting::ModUnionTable* mod_union_table =
  70. new accounting::ModUnionTableCardCache('zygote space mod-union table', this, zygote_space);
  71. ......
  72. AddModUnionTable(mod_union_table);
  73. if (collector::SemiSpace::kUseRememberedSet) {
  74. // Add a new remembered set for the post-zygote non-moving space.
  75. accounting::RememberedSet* post_zygote_non_moving_space_rem_set =
  76. new accounting::RememberedSet('Post-zygote non-moving space remembered set', this,
  77. non_moving_space_);
  78. ......
  79. AddRememberedSet(post_zygote_non_moving_space_rem_set);
  80. }
  81. }
       这个函数定义在文件art/runtime/runtime.cc中。

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

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

  1. CollectGarbageInternal(collector::kGcTypeFull, kGcCauseBackground, false);
  2. Thread* self = Thread::Current();
  3. MutexLock mu(self, zygote_creation_lock_);
  4. // Try to see if we have any Zygote spaces.
  5. if (have_zygote_space_) {
  6. return;
  7. }
  8. ......
  9. // Trim the pages at the end of the non moving space.
  10. non_moving_space_->Trim();
  11. // The end of the non-moving space may be protected, unprotect it so that we can copy the zygote
  12. // there.
  13. non_moving_space_->GetMemMap()->Protect(PROT_READ | PROT_WRITE);
  14. 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上去,如下所示:

  1. if (kCompactZygote) {
  2. ......
  3. // Temporarily disable rosalloc verification because the zygote
  4. // compaction will mess up the rosalloc internal metadata.
  5. ScopedDisableRosAllocVerification disable_rosalloc_verif(this);
  6. ZygoteCompactingCollector zygote_collector(this);
  7. zygote_collector.BuildBins(non_moving_space_);
  8. // Create a new bump pointer space which we will compact into.
  9. space::BumpPointerSpace target_space('zygote bump space', non_moving_space_->End(),
  10. non_moving_space_->Limit());
  11. // Compact the bump pointer space to a new zygote bump pointer space.
  12. bool reset_main_space = false;
  13. if (IsMovingGc(collector_type_)) {
  14. zygote_collector.SetFromSpace(bump_pointer_space_);
  15. } else {
  16. ......
  17. // Copy from the main space.
  18. zygote_collector.SetFromSpace(main_space_);
  19. reset_main_space = true;
  20. }
  21. zygote_collector.SetToSpace(&target_space);
  22. zygote_collector.SetSwapSemiSpaces(false);
  23. zygote_collector.Run(kGcCauseCollectorTransition, false);
  24. if (reset_main_space) {
  25. main_space_->GetMemMap()->Protect(PROT_READ | PROT_WRITE);
  26. madvise(main_space_->Begin(), main_space_->Capacity(), MADV_DONTNEED);
  27. MemMap* mem_map = main_space_->ReleaseMemMap();
  28. RemoveSpace(main_space_);
  29. space::Space* old_main_space = main_space_;
  30. CreateMainMallocSpace(mem_map, kDefaultInitialSize, mem_map->Size(), mem_map->Size());
  31. delete old_main_space;
  32. AddSpace(main_space_);
  33. }
  34. ......
  35. }
       当常量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的操作,如下所示:

  1. space::MallocSpace* old_alloc_space = non_moving_space_;
  2. ......
  3. space::ZygoteSpace* zygote_space = old_alloc_space->CreateZygoteSpace('alloc space',
  4. low_memory_mode_,
  5. &non_moving_space_);
  6. ......
  7. if (same_space) {
  8. main_space_ = non_moving_space_;
  9. SetSpaceAsDefault(main_space_);
  10. }
  11. delete old_alloc_space;
  12. ......
  13. AddSpace(zygote_space);
  14. non_moving_space_->SetFootprintLimit(non_moving_space_->Capacity());
  15. AddSpace(non_moving_space_);
  16. have_zygote_space_ = true;
  17. ......
  18. accounting::ModUnionTable* mod_union_table =
  19. new accounting::ModUnionTableCardCache('zygote space mod-union table', this, zygote_space);
  20. ......
  21. AddModUnionTable(mod_union_table);
  22. if (collector::SemiSpace::kUseRememberedSet) {
  23. // Add a new remembered set for the post-zygote non-moving space.
  24. accounting::RememberedSet* post_zygote_non_moving_space_rem_set =
  25. new accounting::RememberedSet('Post-zygote non-moving space remembered set', this,
  26. non_moving_space_);
  27. ......
  28. AddRememberedSet(post_zygote_non_moving_space_rem_set);
  29. }

       本地变量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

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
jvm
java堆与非堆的一些研究
掌握这3点英语陪读技巧,比上培训班更有效
Golang面试必问——内存逃逸分析
某团面试题:JVM 堆内存溢出后,其他线程是否可继续工作?
JAVA的内存模型及结构 | 并发编程网
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服