引进了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。
扫一扫
在手机上阅读