概述:
众所周知jvm虚拟机为了提高内存回收效率,更高效的进行内存管理与回收,对堆内存进行了分代管理比如hotspot虚拟机的新生代,老年代。根据各代的特征( 新生代对象分配频繁而生存周期短,老年代生存周期长不需要频繁回收)来应用不同的内存回收算法
art虚拟机也是如此,不过Android U上年轻代、老年代回收都采用的concurrent_copying回收算法,虚拟机定义了gc_type枚举类来定义不同的回收策略
enum GcType {
kGcTypeNone,
//粘性回收,只回收上次回收后分配的对象
kGcTypeSticky,
//部分回收,回收初zygote space之外的应用堆内存
kGcTypePartial,
//圈梁回收,应用堆包括zygote space
kGcTypeFull,
kGcTypeMax,
};
枚举类型自上至下回收力度逐渐增大,本文主要分析kGcTypeSticky,其他回收类型后续文章再分析
上图可以是在进行并发回收任务的时候(HeapTaskDaemon线程),如果没有找到合适的回收类型,会依次进行力度更大的回收
如果回收类型为 kGcTypeSticky,会启动年轻代回收器进行回收,那年轻代回收器到底是什么,都回收哪块内存就是我们今天要阐述的内容。
并发拷贝cc算法的一般流程:初始化->标记->复制->回收
art虚拟中并发拷贝算法的实现类concurrent_copying 中的gc全貌
void ConcurrentCopying::RunPhases() {
CHECK(kUseBakerReadBarrier || kUseTableLookupReadBarrier);
CHECK(!is_active_);
is_active_ = true;
Thread* self = Thread::Current();
thread_running_gc_ = self;
Locks::mutator_lock_->AssertNotHeld(self);
{
ReaderMutexLock mu(self, *Locks::mutator_lock_);
InitializePhase(); //初始化阶段
// In case of forced evacuation, all regions are evacuated and hence no
// need to compute live_bytes.
if (use_generational_cc_ && !young_gen_ && !force_evacuate_all_) {
MarkingPhase();
}
}
if (kUseBakerReadBarrier && kGrayDirtyImmuneObjects) {
// Switch to read barrier mark entrypoints before we gray the objects. This is required in case
// a mutator sees a gray bit and dispatches on the entrypoint. (b/37876887).
ActivateReadBarrierEntrypoints();
// Gray dirty immune objects concurrently to reduce GC pause times. We re-process gray cards in
// the pause.
ReaderMutexLock mu(self, *Locks::mutator_lock_);
GrayAllDirtyImmuneObjects();
}
FlipThreadRoots(); //threadroots切换阶段
{
ReaderMutexLock mu(self, *Locks::mutator_lock_);
CopyingPhase(); //复制阶段
}
// Verify no from space refs. This causes a pause.
if (kEnableNoFromSpaceRefsVerification) {
TimingLogger::ScopedTiming split("(Paused)VerifyNoFromSpaceReferences", GetTimings());
ScopedPause pause(this, false);
CheckEmptyMarkStack();
if (kVerboseMode) {
LOG(INFO) << "Verifying no from-space refs";
}
VerifyNoFromSpaceReferences();
if (kVerboseMode) {
LOG(INFO) << "Done verifying no from-space refs";
}
CheckEmptyMarkStack();
}
{
ReaderMutexLock mu(self, *Locks::mutator_lock_);
ReclaimPhase();//回收阶段
}
FinishPhase();
CHECK(is_active_);
is_active_ = false;
thread_running_gc_ = nullptr;
}
1. 初始化阶段
void ConcurrentCopying::InitializePhase() {
TimingLogger::ScopedTiming split("InitializePhase", GetTimings());
num_bytes_allocated_before_gc_ = static_cast<int64_t>(heap_->GetBytesAllocated());
CheckEmptyMarkStack();
BindBitmaps();
if (use_generational_cc_ && !young_gen_) {
region_space_bitmap_->Clear(ShouldEagerlyReleaseMemoryToOS());
}
mark_stack_mode_.store(ConcurrentCopying::kMarkStackModeThreadLocal, std::memory_order_release);
MarkZygoteLargeObjects();
}
void ConcurrentCopying::BindBitmaps() {
Thread* self = Thread::Current();
WriterMutexLock mu(self, *Locks::heap_bitmap_lock_);
// Mark all of the spaces we never collect as immune.
for (const auto& space : heap_->GetContinuousSpaces()) {
if (space->GetGcRetentionPolicy() == space::kGcRetentionPolicyNeverCollect ||
space->GetGcRetentionPolicy() == space::kGcRetentionPolicyFullCollect) {
CHECK(space->IsZygoteSpace() || space->IsImageSpace());
immune_spaces_.AddSpace(space); //imagespace zygote space添加到不动space
} else {
CHECK(!space->IsZygoteSpace());
CHECK(!space->IsImageSpace());
CHECK(space == region_space_ || space == heap_->non_moving_space_);
if (use_generational_cc_) {
if (space == region_space_) {
region_space_bitmap_ = region_space_->GetMarkBitmap();
} else if (young_gen_ && space->IsContinuousMemMapAllocSpace()) {
DCHECK_EQ(space->GetGcRetentionPolicy(), space::kGcRetentionPolicyAlwaysCollect);
space->AsContinuousMemMapAllocSpace()->BindLiveToMarkBitmap();
}
if (young_gen_) {
// Age all of the cards for the region space so that we know which evac regions to scan.
heap_->GetCardTable()->ModifyCardsAtomic(space->Begin(),
space->End(),
AgeCardVisitor(),
VoidFunctor());
} else {
heap_->GetCardTable()->ClearCardRange(space->Begin(), space->Limit());
}
}
}
}
if (use_generational_cc_ && young_gen_) {
for (const auto& space : GetHeap()->GetDiscontinuousSpaces()) {
CHECK(space->IsLargeObjectSpace());
space->AsLargeObjectSpace()->CopyLiveToMarked();
}
}
}
2.标记阶段
void ConcurrentCopying::FlipThreadRoots() {
TimingLogger::ScopedTiming split("FlipThreadRoots", GetTimings());
if (kVerboseMode || heap_->dump_region_info_before_gc_) {
LOG(INFO) << "time=" << region_space_->Time();
region_space_->DumpNonFreeRegions(LOG_STREAM(INFO));
}
Thread* self = Thread::Current();
Locks::mutator_lock_->AssertNotHeld(self);
ThreadFlipVisitor thread_flip_visitor(this, heap_->use_tlab_);
FlipCallback flip_callback(this);
Runtime::Current()->GetThreadList()->FlipThreadRoots(
&thread_flip_visitor, &flip_callback, this, GetHeap()->GetGcPauseListener());
is_asserting_to_space_invariant_ = true;
QuasiAtomic::ThreadFenceForConstructor(); // TODO: Remove?
if (kVerboseMode) {
LOG(INFO) << "time=" << region_space_->Time();
region_space_->DumpNonFreeRegions(LOG_STREAM(INFO));
LOG(INFO) << "GC end of FlipThreadRoots";
}
}
hotspot虚拟机中8:1比例划分对新生代堆空间进行划分,大空间分配的对象往往朝生夕死,当内存被回收后会被拷贝到小空间,art虚拟机虽然也采用分代策略但是采用了不同的机制
art虚拟机中应用分配的普通对象会分配在main_space(region_space类实现)空间,而>12k的string类型或者原始数组类型会分配到large_object_space空间
enum class RegionType : uint8_t {
kRegionTypeAll, // All types.
kRegionTypeFromSpace, // From-space. To be evacuated.
kRegionTypeUnevacFromSpace, // Unevacuated from-space. Not to be evacuated.
kRegionTypeToSpace, // To-space.
kRegionTypeNone, // None.
};
FlipThreadRoot 会进行
a. region_space类型切换
regionspace由一个个固定大小的region组成,分配对象的region类型为kRegionTypeToSpace,而当需要内存回收的时候会切换为kRegionTypeFromSpace(拷贝从from拷贝的to)代码位置在FlipCallback->setFromSpace
b. 标记gcroot
art虚拟机中的gcroot-CSDN博客可以参考这篇文章,被标记的对象会被压入到mark_stack(PushOntoMarkStack)中
那kGcTypeSticky回收策略是怎么做到只回收上次分配的对象的呢?
inline bool RegionSpace::Region::ShouldBeEvacuated(EvacMode evac_mode) {
if (IsLarge()) {
return false;
}
if (UNLIKELY(evac_mode == kEvacModeForceAll)) {
return true;
}
DCHECK(IsAllocated());
if (is_newly_allocated_) {
return true;
} else if (evac_mode == kEvacModeLivePercentNewlyAllocated) {
bool is_live_percent_valid = (live_bytes_ != static_cast<size_t>(-1));
if (is_live_percent_valid) {
return live_bytes_ * 100U < kEvacuateLivePercentThreshold * bytes_allocated;
}
}
return false;
}
当空间是新分配或者存活对象比例低于75%,才会设置成kRegionTypeFromSpace,即需要被复制的空间
3.复制阶段
CopyingPhase(); //复制阶段,
for (auto& space : immune_spaces_.GetSpaces()) {
DCHECK(space->IsImageSpace() || space->IsZygoteSpace());
accounting::ContinuousSpaceBitmap* live_bitmap = space->GetLiveBitmap();
accounting::ModUnionTable* table = heap_->FindModUnionTableFromSpace(space);
ImmuneSpaceScanObjVisitor visitor(this);
if (kUseBakerReadBarrier && kGrayDirtyImmuneObjects && table != nullptr) {
table->VisitObjects(ImmuneSpaceScanObjVisitor::Callback, &visitor);
} else {
WriterMutexLock rmu(Thread::Current(), *Locks::heap_bitmap_lock_);
card_table->Scan<false>(
live_bitmap,
space->Begin(),
space->Limit(),
visitor,
accounting::CardTable::kCardDirty - 1);
}
}
需要把immuse space(zygote space,imagespace)中的存活对象进行拷贝,ImageSpace中的对象可以直接标记,因为映射dex 等Image文件所以可以直接被认为是gcroot
广度优先遍历mark_stack,把mark_stack中的对象及其引用拷贝到toSpace中,同时
4.回收阶段
回收fromspace(非stick会回收largetObjecSpace)