引进了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对象开始的,如下所示:
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。
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。