关于GC碎碎念
目前大部分Android开发者能接触到的GC材料限制在八股文/关于Hotspot虚拟机的完结,大部分不适用ART虚拟机关于GC的完结,或许大部分材料只限制在GC的某个点,管中窥豹的办法很难有一个全貌。不了解ART虚拟机的进程也意味着当遇到GC相关的体系问题其实也很难解决。
即使ART虚拟机发展了这么多年,GC搜集这一块代码依旧在不断迭代当中,其间特殊情况下依旧有不少段错误的呈现,信任各位APP开发者也会遇到。
看完本文你将会学习到
- ART虚拟机中触及GC算法流程
- ART虚拟机是怎么依据不同的内存区域拟定不同的GC战略
- GC的全体大纲,以及后续怎么深化学习
学习完之后,这些常识能够被运用到:
- android功能优化战略
- 体系常识理解以及运用在自界说OS当中
阅览本文前,期望你已经掌握基本的GC算法的大致介绍,比方符号铲除,仿制等
ART中关于GC的完结
ART虚拟机的GC流程,从全体上看,其实首要围绕着以下几个点展开:
- GC战略:对接ART内存模型,GC战略怎么与ART内存模型对接
- GC规模:怎么确认GC的规模,全量扫描仍是部分扫描
- GC挑选:怎么依据场景挑选详细的废物收回器
- GC流程:符号流程与铲除流程,怎么采纳愈加速捷的办法让GC完结
GC战略
ART虚拟机是适用于Android这种手机的特殊定制化虚拟机,信任大家都知道这点,而ART的内存模型,其实决议着GC的战略。
这儿说的是一个战略的问题,比方某一块内存区域里边内容不简单/不发生废物目标,或许收回效益不大(GC的意图便是要开释更多的可用内存),其实咱们就能够不用参加GC的流程。假如某一块区域常常发生gc,那么天然,咱们就需求针对这一块区域进行GC,这样才能达到效益最大化。ART虚拟机其实便是依托这种思想,把进程堆的可用内存,分为了好几块。
这儿的内存块,由SpaceType表明
enum SpaceType {
kSpaceTypeImageSpace,
kSpaceTypeMallocSpace,
kSpaceTypeZygoteSpace,
kSpaceTypeBumpPointerSpace,
kSpaceTypeLargeObjectSpace,
kSpaceTypeRegionSpace,
};
它有以上几种,其间每种SpaceType的详细完结,也能够有多种,比方LargeObjectSpace的完结类依据32/64位架构不同,详细的完结分别为LargeObjectMapSpace/FreeListSpace等。无论这些完结类怎么,其实他们代表的space永远都是按照SpaceType表明。
ART虚拟机把内存依据不同的特征,抽象出一个个Space,它是一个抽象类,在创立每一个space的时分,会由详细的子类去依据自己的界说,经过GetType回来回来SpaceType,表明自己属于哪种内存类型
在这儿咱们先打住,咱们知道了ART虚拟机会把堆划分为不同的Space这一个现实,详细的Space承载着什么,能够阅览笔者之前关于ART虚拟机内存模型的解析。本章重点是GC模型,因而咱们只需求知道虚拟机有不同的内存块类型即可
在有了这些前置常识之后,咱们引出来第一个GC相关的概念,GC的战略
enum GcRetentionPolicy {
// Objects are retained forever with this policy for a space.
永远不GC
kGcRetentionPolicyNeverCollect,
// Every GC cycle will attempt to collect objects in this space.
每次GC都会测验铲除该区域
kGcRetentionPolicyAlwaysCollect,
// Objects will be considered for collection only in "full" GC cycles, ie faster partial
// collections won't scan these areas such as the Zygote.
“full”gc条件下才会扫描该区域,跟GC的规模有关
kGcRetentionPolicyFullCollect,
};
它为后续GC的规模奠定了扫描基础,举个例子,比方当时内存比较富余情况下,只需求对那些常常会发生废物目标的区域扫描即可,关于那些不怎么发生废物目标(有,可是相对少)的Space,就能够不同归入扫描规模,这样能让GC的整个扫描进程更快
GC的战略束缚的是Space,因而Space会在创立的时分,就会依据详细完结的差异,分别拟定不同的GcRetentionPolicy
下面我把Space与GcRetentionPolicy分别对应起来,就会得到下面表格。
ImageSpace | kGcRetentionPolicyNeverCollect |
---|---|
MallocSpace | kGcRetentionPolicyAlwaysCollect |
ZygoteSpace | kGcRetentionPolicyFullCollect |
BumpPointerSpace | kGcRetentionPolicyAlwaysCollect |
LargeObjectSpace | kGcRetentionPolicyAlwaysCollect |
RegionSpace | kGcRetentionPolicyAlwaysCollect |
经过对Space设定不同的GC战略,能够十分高效确认GC的扫描规模,让每一次GC都依据自身的特性去扫描特定的Space,比方ZygoteSpace自身废物目标不多,因而只需求等到内存不太充足的FullGC条件下,才会被归入扫描规模。让GC自身的战略愈加高效
GC规模/类型
找到废物目标这个流程,其实算是一个较为耗时的进程,所以ART虚拟机的规划其实也跟咱们常见的思想相同。假如在内存较为富余的情况下,咱们其实能够只收回部分区域的废物目标即可,这样更快一起也能够较为有用的收回内存。在内存不足情况下,咱们收回战略便是尽或许的收回更多的内存,不然就会导致OOM对吧。这也是ART虚拟机的通用战略,只不过在一些情况下会有小部分的特殊处理算了
GcType目标就承担着GC的扫描规模规模这么一个进程
enum GcType {
// Placeholder for when no GC has been performed.
不进行GC
kGcTypeNone,
// Sticky mark bits GC that attempts to only free objects allocated since the last GC.
针对前次存活的目标进行GC
kGcTypeSticky,
// Partial GC that marks the application heap but not the Zygote.
针对heap一切进行GC不包括ZygoteSpace
kGcTypePartial,
// Full GC that marks and frees in both the application and Zygote heap.
针对heap一切进行GC包括ZygoteSpace
kGcTypeFull,
// Number of different GC types.
目前是充当鸿沟查看符号,即校验gctype是否在【kGcTypeNone,kGcTypeMax) 里边,由于gctype是外部传入
kGcTypeMax,
/*
{
比方heap 初始化时校验
DCHECK_LT(gc_type, collector::kGcTypeMax);
DCHECK_NE(gc_type, collector::kGcTypeNone);
}
*/
};
各个GcType 对应的收回规模也不相同,而GcType由GarbageCollector这个废物收回器的基类决议。
GarbageCollector
virtual GcType GetGcType() const = 0;
这儿咱们其实就能够知道,GarbageCollector有着自己的GC规模,不同的GarbageCollector的完结,也决议了上述讲到的哪些Space内存会被参加扫描。
相同的,ART虚拟机也为GarbageCollector划分了不同的类型,即CollectorType,当然,这儿的type就描绘着GarbageCollector是基于哪种废物收回算法完结的,比方符号铲除,符号收拾,又或许是仿制算法等
enum CollectorType {
// No collector selected.
kCollectorTypeNone,
// Non concurrent mark-sweep.
kCollectorTypeMS,
// Concurrent mark-sweep.
kCollectorTypeCMS,
// Concurrent mark-compact.
kCollectorTypeCMC,
// The background compaction of the Concurrent mark-compact GC.
kCollectorTypeCMCBackground,
// Semi-space / mark-sweep hybrid, enables compaction.
kCollectorTypeSS,
// Heap trimming collector, doesn't do any actual collecting.
kCollectorTypeHeapTrim,
// A (mostly) concurrent copying collector.
kCollectorTypeCC,
// The background compaction of the concurrent copying collector.
kCollectorTypeCCBackground,
// Instrumentation critical section fake collector.
kCollectorTypeInstrumentation,
// Fake collector for adding or removing application image spaces.
kCollectorTypeAddRemoveAppImageSpace,
// Fake collector used to implement exclusion between GC and debugger.
kCollectorTypeDebugger,
// A homogeneous space compaction collector used in background transition
// when both foreground and background collector are CMS.
kCollectorTypeHomogeneousSpaceCompact,
// Class linker fake collector.
kCollectorTypeClassLinker,
// JIT Code cache fake collector.
kCollectorTypeJitCodeCache,
// Hprof fake collector.
kCollectorTypeHprof,
// Fake collector for installing/removing a system-weak holder.
kCollectorTypeAddRemoveSystemWeakHolder,
// Fake collector type for GetObjectsAllocated
kCollectorTypeGetObjectsAllocated,
// Fake collector type for ScopedGCCriticalSection
kCollectorTypeCriticalSection,
};
详细的废物收回器都是GarbageCollector的子类,假如咱们想要研究某一类废物收回算法在ART的完结,能够经过阅览对应子类的完结进行了解
废物收回器中会依据详细的完结,界说好详细的GC规模,一起GC规模也间接影响了哪些Space会被参加废物收回。值得一提,废物收回器也决议了详细分配器的完结,不同的废物收回也对应着不同的内存分配器,这儿简单提一下,Heap::ChangeCollector
GC挑选
咱们知道到了废物收回器的相关特征,那么ART虚拟机是怎么挑选哪种废物收回器的呢?总不或许都挑选一切废物收回器吧。
影响废物收回器的挑选能够首要有三个大的要素
- 废物收回器的默许挑选:功能突破,比方在早期Android版别挑选CMS进行废物收回,而后续高版别中默许采纳CC分配办法,由于它功能更好,暂停时间更短等。
- 厂商自界说:比方OS厂商,会有部分厂商测验自研废物收回完结,贴合自研的体系,因而能够忽略默许选项,经过自界说“-Xgc”类型选中特定的废物收回器,比方在一些车企OS中会指定即使android高版别的体系通用采纳CMS(毕竟CMS经过内部迭代多版)
- 前后台:应用前后台不同(这儿指ProcessState)区分,比方前台由于直接与用户进行交互,就会挑选废物收回更快的办法,而后台由于用户看不到,就能够挑选把内存收拾一下,腾出更多接连内存(避免内存颤动)
废物收回器的决议会在Heap初始化的时分,挑选废物收回器,需求指定前台废物收回器与后台废物收回器
其间会依据是否使用读屏障选中默许的CC仍是Xgc参数拟定的废物收回器类型(目前手机AndroidOS大部分都是使用读屏障(其实它的是一个mutex,会在编译被嵌入相关的指令))
自界说gctype会更具-Xgc参数赋值
当然,当发现前后台切换时,会经过UpdateProcessState函数,改动前后台Collecter的切换
void Heap::UpdateProcessState(ProcessState old_process_state, ProcessState new_process_state) {
if (old_process_state != new_process_state) {
前台jank_perceptible true
const bool jank_perceptible = new_process_state == kProcessStateJankPerceptible;
if (jank_perceptible) {
// Transition back to foreground right away to prevent jank.
RequestCollectorTransition(foreground_collector_type_, 0);
GrowHeapOnJankPerceptibleSwitch();
} else {
// If background_collector_type_ is kCollectorTypeHomogeneousSpaceCompact then we have
// special handling which does a homogenous space compaction once but then doesn't transition
// the collector. Similarly, we invoke a full compaction for kCollectorTypeCC but don't
// transition the collector.
RequestCollectorTransition(background_collector_type_, 0);
}
}
}
废物收回器需求指定前后台废物收回器,总的大纲便是,在前台环境下,用户关于卡顿会愈加灵敏,因而需求挑选更快的废物收回,而后台环境下,卡顿不灵敏,因而需求进行内存的收拾,便于内存块的整合
GC流程
抛开System.gc引起的主动gc,大部分GC由ConcurrentGCTask与分配时AllocInternalWithGc触发,咱们简单看一下ConcurrentGCTask,分配GC咱们在内存海绵计划中介绍过了(关于GC流程的追踪,咱们就能够经过这个Task触发进行调用链分析)
class Heap::ConcurrentGCTask : public HeapTask {
public:
ConcurrentGCTask(uint64_t target_time, GcCause cause, bool force_full, uint32_t gc_num)
: HeapTask(target_time), cause_(cause), force_full_(force_full), my_gc_num_(gc_num) {}
void Run(Thread* self) override {
Runtime* runtime = Runtime::Current();
gc::Heap* heap = runtime->GetHeap();
DCHECK(GCNumberLt(my_gc_num_, heap->GetCurrentGcNum() + 2)); // <= current_gc_num + 1
heap->ConcurrentGC(self, cause_, force_full_, my_gc_num_);
CHECK_IMPLIES(GCNumberLt(heap->GetCurrentGcNum(), my_gc_num_), runtime->IsShuttingDown(self));
}
private:
const GcCause cause_;
const bool force_full_; // If true, force full (or partial) collection.
const uint32_t my_gc_num_; // Sequence number of requested GC.
};
进行一系列的纠正
void Heap::ConcurrentGC(Thread* self, GcCause cause, bool force_full, uint32_t requested_gc_num) {
if (!Runtime::Current()->IsShuttingDown(self)) {
// Wait for any GCs currently running to finish. If this incremented GC number, we're done.
WaitForGcToComplete(cause, self);
if (GCNumberLt(GetCurrentGcNum(), requested_gc_num)) {
collector::GcType next_gc_type = next_gc_type_;
// If forcing full and next gc type is sticky, override with a non-sticky type.
if (force_full && next_gc_type == collector::kGcTypeSticky) {
next_gc_type = NonStickyGcType();
}
// If we can't run the GC type we wanted to run, find the next appropriate one and try
// that instead. E.g. can't do partial, so do full instead.
// We must ensure that we run something that ends up incrementing gcs_completed_.
// In the kGcTypePartial case, the initial CollectGarbageInternal call may not have that
// effect, but the subsequent KGcTypeFull call will.
if (CollectGarbageInternal(next_gc_type, cause, false, requested_gc_num)
== collector::kGcTypeNone) {
for (collector::GcType gc_type : gc_plan_) {
if (!GCNumberLt(GetCurrentGcNum(), requested_gc_num)) {
// Somebody did it for us.
break;
}
// Attempt to run the collector, if we succeed, we are done.
if (gc_type > next_gc_type &&
CollectGarbageInternal(gc_type, cause, false, requested_gc_num)
!= collector::kGcTypeNone) {
break;
}
}
}
}
}
}
堆建议,并进行校验,判断是否真正建议一次GC,并更新下一次gc的类型(比方当时是全量gc且下次gc为sticky情况下,下次gc旧设置为null,节约功率)
force_full 对应的是fullgc 或许 partial gc 取决于废物收回器
这儿面便是一个功率问题,尽或许触发短快的stick或许一定条件答应下不触发,建议后由对应collect决议。
终究调用CollectGarbageInternal办法,里边会由GarbageCollector的详细子类进行废物收回,调用其run办法
废物收回器首要做的其实便是两件事:
- 符号可达的内存
- 删除不可达的内存
由于符号可达内存这一步,能够采纳多线程的办法进行符号,所以一些Concurrent前缀的战略,其实便是采纳多线程的办法加速符号。
符号这儿会触及两个处理:
- 去增:不要让新的内存进行分配,由于新内存分配简单改动引证链,让其愈加复杂,因而大多数采纳读锁获取的办法,能让分配器能够读取已有的内存,不至于让gc卡顿,注意,写锁获取仍然被堵塞,比方分配内存。
- 减存:经过获取写锁,把堆中的废物目标铲除,获取写锁便是避免让无效的被开释的内存还能被读取或许使用。这一步往往是ART GC的最难的一步,假如发生异常,那么很简单发生段错误SIGSEGV (signalsegmentation violation)比方jni操作数组越界后,会破坏jvm的引证链,形成gc时SIGSEGV。
所以废物收回器的本质都是在这两点基础上做出差异优化,咱们拿手机os中默许也是干流的ConcurrentCopying废物收回器进行解析
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_);
初始化符号,这儿会把目标Space与非目标Space进行符号与分离,这儿符号的是上一次存活的目标
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_) {
假如启用了分代并且不是年青代且force_evacuate_all_符号位为false,再次发动符号
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();
{
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;
}
经过**CheckEmptyMarkStack**();
循环符号,尽或许减少分配线程中存留目标或许引证到了其他目标
最后仍是要上写锁,把内存搬迁
当然,这儿触及的gc算法,比方染色、读屏障就不详细分析了,这块材料仍是有很多的。
GC流程本质便是依据内存情况进行内存删减的流程,这儿面便是调用详细的废物收回器进行搜集,整个废物收回器的规划中心都是两部,怎么更快且更准确做出去增减存
总结
ART中GC还有不少的内容,不过信任读完这篇文章,咱们对GC有了一个愈加宏观的全貌,接下来只需求依据需求进行细节的阅览即可。当然,了解后,咱们能够在这基础上,开宣布各种各样的gc黑科技,从而让APP功能有多的或许。
我是Pika,假如你喜欢我的文章,请不要忘掉点赞重视!后面还有一系列鸿蒙/Android文章等你!