Android中GC—初探-1—触发时机和条件

一、GC 简介

Java 对象的创建由 Allocator 负责,回收由 Collector 负责。从 Android O 开始,对于前台应用默认的 GC Collector 是 CC(Concurrent Copying) Collector,与之相匹配的 Allocator 则是 Region-based Bump Pointer Allocator(with TLAB)。


二、前台应用GC何时触发

1. GC触发条件

GC触发条件在 art/runtime/gc/gc_cause.h 中定义:

// What caused the GC?
enum GcCause {
  // Invalid GC cause used as a placeholder.
  kGcCauseNone,
  // GC triggered by a failed allocation. Thread doing allocation is blocked waiting for GC before retrying allocation.
  kGcCauseForAlloc,
  // A background GC trying to ensure there is free memory ahead of allocations.
  kGcCauseBackground,
  // An explicit System.gc() call.
  kGcCauseExplicit,
  // GC triggered for a native allocation when NativeAllocationGcWatermark is exceeded.
  // (This may be a blocking GC depending on whether we run a non-concurrent collector).
  kGcCauseForNativeAlloc,
  // GC triggered for a collector transition.
  kGcCauseCollectorTransition,
  // Not a real GC cause, used when we disable moving GC (currently for GetPrimitiveArrayCritical).
  kGcCauseDisableMovingGc,
  // Not a real GC cause, used when we trim the heap.
  kGcCauseTrim,
  // Not a real GC cause, used to implement exclusion between GC and instrumentation.
  kGcCauseInstrumentation,
  // Not a real GC cause, used to add or remove app image spaces.
  kGcCauseAddRemoveAppImageSpace,
  // Not a real GC cause, used to implement exclusion between GC and debugger.
  kGcCauseDebugger,
  // GC triggered for background transition when both foreground and background collector are CMS.
  kGcCauseHomogeneousSpaceCompact,
  // Class linker cause, used to guard filling art methods with special values.
  kGcCauseClassLinker,
  // Not a real GC cause, used to implement exclusion between code cache metadata and GC.
  kGcCauseJitCodeCache,
  // Not a real GC cause, used to add or remove system-weak holders.
  kGcCauseAddRemoveSystemWeakHolder,
  // Not a real GC cause, used to prevent hprof running in the middle of GC.
  kGcCauseHprof,
  // Not a real GC cause, used to prevent GetObjectsAllocated running in the middle of GC.
  kGcCauseGetObjectsAllocated,
  // GC cause for the profile saver.
  kGcCauseProfileSaver,
  // GC cause for running an empty checkpoint.
  kGcCauseRunEmptyCheckpoint,
};

根据 GcCause 可知,可以触发GC的条件还是很多的。对于开发者而言,常见的是其中三种:

GcCauseForAlloc:通过new分配新对象时,堆中剩余空间(普通应用默认上限为256M,在 AndroidManifest.xml 中声明 largeHeap 的应用为512M)不足,因此需要先进行GC。这种情况会导致当前线程阻塞。
GcCauseExplicit:当应用调用系统API System.gc()时,会产生一次GC动作。
GcCauseBackground:后台GC,这里的“后台”并不是指应用切到后台才会执行的GC,而是GC在运行时基本不会影响其他线程的执行,所以也可以理解为并发GC。

相比于前两种GC,后台GC出现的更多也更加隐秘,因此值得详细介绍。下文讲述的全是这种GC。


Java堆的实际大小起起伏伏,影响的因素无非是分配和回收。分配的过程是离散且频繁的,它来自于不同的工作线程,而且可能每次只分配一小块区域。回收的过程则是统一且偶发的,它由 HeapTaskDaemon 线程执行,在GC的多个阶段中都采用并发算法,因此不会暂停工作线程(实际上会暂停很短一段时间)。

当我们在Java代码中通过new分配对象时,虚拟机会调用 AllocObjectWithAllocator 来执行真实的分配。在每一次成功分配Java对象后,都会去检测是否需要进行下一次GC,这就是 GcCauseBackground GC的触发时机。代码实现位置
AllocObjectWithAllocator 模板函数定义在 art/runtime/gc/heap-inl.h 中

template <bool kInstrumented, bool kCheckLargeObject, typename PreFenceVisitor> 
inline mirror::Object* Heap::AllocObjectWithAllocator(Thread* self, ObjPtr<mirror::Class> klass, size_t byte_count, AllocatorType allocator, const PreFenceVisitor& pre_fence_visitor) {
    ...
    // IsGcConcurrent() isn't known at compile time so we can optimize by not checking it for
    // the BumpPointer or TLAB allocators. This is nice since it allows the entire if statement to be
    // optimized out. And for the other allocators, AllocatorMayHaveConcurrentGC is a constant since
    // the allocator_type should be constant propagated.
    if (AllocatorMayHaveConcurrentGC(allocator) && IsGcConcurrent()) {
        // New_num_bytes_allocated is zero if we didn't update num_bytes_allocated_.
        // That's fine.
        CheckConcurrentGCForJava(self, new_num_bytes_allocated, &obj);
    }
    VerifyObject(obj);
    self->VerifyStack();
    return obj.Ptr();
}

inline void Heap::CheckConcurrentGCForJava(Thread* self, size_t new_num_bytes_allocated, ObjPtr<mirror::Object>* obj) {
    if (UNLIKELY(ShouldConcurrentGCForJava(new_num_bytes_allocated))) {
        RequestConcurrentGCAndSaveObject(self, false /* force_full */, obj);
    }
}

inline bool Heap::ShouldConcurrentGCForJava(size_t new_num_bytes_allocated) {
    // For a Java allocation, we only check whether the number of Java allocated bytes excceeds a
    // threshold. By not considering native allocation here, we (a) ensure that Java heap bounds are
    // maintained, and (b) reduce the cost of the check here.
    return new_num_bytes_allocated >= concurrent_start_bytes_;
}

触发的条件需要满足一个判断,如果 new_num_bytes_allocated(所有已分配的字节数,包括此次新分配的对象) >= concurrent_start_bytes_(下一次GC触发的阈值),那么就请求一次新的GC。new_num_bytes_alloated 是当前分配时计算的,
concurrent_start_bytes_ 是上一次GC结束时计算的。以下将分别介绍这两个值的计算过程和背后的设计思想。


2. new_num_bytes_allocated 的计算过程

在 art/runtime/gc/heap-inl.h 中:

template <bool kInstrumented, bool kCheckLargeObject, typename PreFenceVisitor>
inline mirror::Object* Heap::AllocObjectWithAllocator(Thread* self, ObjPtr<mirror::Class> klass, size_t byte_count, AllocatorType allocator, const PreFenceVisitor& pre_fence_visitor) {
    size_t new_num_bytes_allocated = 0;
    {
        // Do the initial pre-alloc
        pre_object_allocated();
        ...
        if (IsTLABAllocator(allocator)) {
            byte_count = RoundUp(byte_count, space::BumpPointerSpace::kAlignment);
        }
        // If we have a thread local allocation we don't need to update bytes allocated.
        if (IsTLABAllocator(allocator) && byte_count <= self->TlabSize()) {
            obj = self->AllocTlab(byte_count);
            DCHECK(obj != nullptr) << "AllocTlab can't fail";
            obj->SetClass(klass);
            if (kUseBakerReadBarrier) {
                obj->AssertReadBarrierState();
            }
            bytes_allocated = byte_count;
            usable_size = bytes_allocated;
            no_suspend_pre_fence_visitor(obj, usable_size);
            QuasiAtomic::ThreadFenceForConstructor();
        } else if (!kInstrumented && allocator == kAllocatorTypeRosAlloc && (obj = rosalloc_space_->AllocThreadLocal(self, byte_count, &bytes_allocated)) != nullptr &&
            LIKELY(obj != nullptr)) {
                DCHECK(!is_running_on_memory_tool_);
                obj->SetClass(klass);
                if (kUseBakerReadBarrier) {
                    obj->AssertReadBarrierState();
                }
                usable_size = bytes_allocated;
                no_suspend_pre_fence_visitor(obj, usable_size);
                QuasiAtomic::ThreadFenceForConstructor();
        } else {
            // Bytes allocated that includes bulk thread-local buffer allocations in addition to direct non-TLAB object allocations.
            size_t bytes_tl_bulk_allocated = 0u;
            obj = TryToAllocate<kInstrumented, false>(self, allocator, byte_count, &bytes_allocated, &usable_size, &bytes_tl_bulk_allocated);
            if (UNLIKELY(obj == nullptr)) {
                // AllocateInternalWithGc can cause thread suspension, if someone instruments the
                // entrypoints or changes the allocator in a suspend point here, we need to retry the
                // allocation. It will send the pre-alloc event again.
                obj = AllocateInternalWithGc(self, allocator, kInstrumented, byte_count, &bytes_allocated, &usable_size, &bytes_tl_bulk_allocated, &klass);
                if (obj == nullptr) {
                    // The only way that we can get a null return if there is no pending exception is if the
                    // allocator or instrumentation changed.
                    if (!self->IsExceptionPending()) {
                        // Since we are restarting, allow thread suspension.
                        ScopedAllowThreadSuspension ats;
                        // AllocObject will pick up the new allocator type, and instrumented as true is the safe default.
                        return AllocObject</*kInstrumented=*/true>(self, klass, byte_count, pre_fence_visitor);
                    }
                    return nullptr;
                }
            }
            DCHECK_GT(bytes_allocated, 0u);
            DCHECK_GT(usable_size, 0u);
            obj->SetClass(klass);
            if (kUseBakerReadBarrier) {
                obj->AssertReadBarrierState();
            }
            if (collector::SemiSpace::kUseRememberedSet && UNLIKELY(allocator == kAllocatorTypeNonMoving)) {
                // (Note this if statement will be constant folded away for the fast-path quick entry
                // points.) Because SetClass() has no write barrier, the GC may need a write barrier in the
                // case the object is non movable and points to a recently allocated movable class.
                WriteBarrier::ForFieldWrite(obj, mirror::Object::ClassOffset(), klass);
            }
            no_suspend_pre_fence_visitor(obj, usable_size);
            QuasiAtomic::ThreadFenceForConstructor();
            if (bytes_tl_bulk_allocated > 0) {
                size_t num_bytes_allocated_before = num_bytes_allocated_.fetch_add(bytes_tl_bulk_allocated, std::memory_order_relaxed);
                new_num_bytes_allocated = num_bytes_allocated_before + bytes_tl_bulk_allocated;
                // Only trace when we get an increase in the number of bytes allocated. This happens when
                // obtaining a new TLAB and isn't often enough to hurt performance according to golem.
                if (region_space_) {
                    // With CC collector, during a GC cycle, the heap usage increases as
                    // there are two copies of evacuated objects. Therefore, add evac-bytes
                    // to the heap size. When the GC cycle is not running, evac-bytes
                    // are 0, as required.
                    TraceHeapSize(new_num_bytes_allocated + region_space_->EvacBytes());
                } else {
                    TraceHeapSize(new_num_bytes_allocated);
                }
            }
        }
    }
    ...
    // IsGcConcurrent() isn't known at compile time so we can optimize by not checking it for
    // the BumpPointer or TLAB allocators. This is nice since it allows the entire if statement to be
    // optimized out. And for the other allocators, AllocatorMayHaveConcurrentGC is a constant since
    // the allocator_type should be constant propagated.
    if (AllocatorMayHaveConcurrentGC(allocator) && IsGcConcurrent()) {
        // New_num_bytes_allocated is zero if we didn't update num_bytes_allocated_.
        // That's fine.
        CheckConcurrentGCForJava(self, new_num_bytes_allocated, &obj);
    }
    ...
}

AllocObjectWithAllocator 的实际分配可以分为三条分支,但如果限定为 Region-based Bump Pointer Allocator,则只剩两条分支:

(1) 如果当前线程 TLAB 区域的剩余空间可以容纳下这次分配的对象,则在 TLAB 区域中直接分配。分配算法采用 Bump Pointer 的方式,仅仅更新已分配区域的游标,简单高效。

//art/runtime/thread-inl.h
inline mirror::Object* Thread::AllocTlab(size_t bytes) {
    DCHECK_GE(TlabSize(), bytes);
    ++tlsPtr_.thread_local_objects;
    mirror::Object* ret = reinterpret_cast<mirror::Object*>(tlsPtr_.thread_local_pos);
    tlsPtr_.thread_local_pos += bytes;
    return ret;
}

在这种情况下,new_num_bytes_allocated 为0,表明Java堆的已使用区域并没有增大。这是因为 TLAB 在创建之初,它的大小已经计入了 num_bytes_allocated_,所以这次虽然分配了新的对象,但 num_bytes_allocated_ 没必要增加。

那么紧接着就来了一个问题:为什么TLAB在创建之初就要将大小计入 num_bytes_allocated_ 呢? 可是此时TLAB明明还没有被使用。这实际上是一个空间换时间的策略。以下是当时这笔改动的 commit message,通过事先将大小计入
num_bytes_allocated_ 从而不必要每次分配都更新它,减少针对 num_bytes_allocated_ 的原子操作,提高性能。代价就是会导致 num_bytes_allocated_ 略大于真实使用的字节数。

[Commit Message]

Faster TLAB allocator.New TLAB allocator doesn't increment bytes allocated until we allocatea new TLAB. 
This increases allocation performance by avoiding a CAS.MemAllocTest:Before GSS TLAB: 3400ms.After GSS 
TLAB: 2750ms.Bug: 9986565Change-Id: I1673c27555330ee90d353b98498fa0e67bd57fadAuthor: mathieuc@google.com
Date: 2014-07-12 05:18

 

(2) 如果当前线程 TLAB 区域的剩余空间无法容纳下这次分配的对象,则为当前线程创建一个新的 TLAB。在这种情况下,新分配出来的 TLAB 大小需要计入 num_bytes_allocated_,因此 new_num_bytes_allocated = num_bytes_allocated_before +
bytes_tl_bulk_allocated。


3. concurrent_start_bytes_ 的计算过程

在 art/runtime/gc/heap.cc 中:

collector::GcType Heap::CollectGarbageInternal(collector::GcType gc_type, GcCause gc_cause, bool clear_soft_references) {
    ...
    collector->Run(gc_cause, clear_soft_references || runtime->IsZygote()); //2671
    IncrementFreedEver();
    RequestTrim(self);
    // Collect cleared references.
    SelfDeletingTask* clear = reference_processor_->CollectClearedReferences(self);
    // Grow the heap so that we know when to perform the next GC.
    GrowForUtilization(collector, bytes_allocated_before_gc); //2677
    LogGC(gc_cause, collector);
    FinishGC(self, gc_type);
    // Actually enqueue all cleared references. Do this after the GC has officially finished since
    // otherwise we can deadlock.
    clear->Run(self);
    clear->Finalize();
    // Inform DDMS that a GC completed.
    Dbg::GcDidFinish();
    old_native_bytes_allocated_.store(GetNativeBytes());
    // Unload native libraries for class unloading. We do this after calling FinishGC to prevent
    // deadlocks in case the JNI_OnUnload function does allocations.
    {
        ScopedObjectAccess soa(self);
        soa.Vm()->UnloadNativeLibraries();
        }
    return gc_type;
}

CollectGarbageInternal 是 HeapTaskDaemon 线程执行GC时需要调用的函数。其中 2671 行将执行真正的GC,而 concurrent_start_bytes_ 的计算则在 2677 行的 GrowForUtilization 函数中。

void Heap::GrowForUtilization(collector::GarbageCollector* collector_ran, size_t bytes_allocated_before_gc) {
    // We know what our utilization is at this moment.
    // This doesn't actually resize any memory. It just lets the heap grow more when necessary.
    const size_t bytes_allocated = GetBytesAllocated();
    // Trace the new heap size after the GC is finished.
    TraceHeapSize(bytes_allocated);
    uint64_t target_size, grow_bytes;
    collector::GcType gc_type = collector_ran->GetGcType();
    MutexLock mu(Thread::Current(), process_state_update_lock_);
    // Use the multiplier to grow more for foreground.
    onst double multiplier = HeapGrowthMultiplier();
    if (gc_type != collector::kGcTypeSticky) {
        // Grow the heap for non sticky GC.
        uint64_t delta = bytes_allocated * (1.0 / GetTargetHeapUtilization() - 1.0);
        DCHECK_LE(delta, std::numeric_limits<size_t>::max()) << "bytes_allocated=" << bytes_allocated << " target_utilization_=" << target_utilization_;
        grow_bytes = std::min(delta, static_cast<uint64_t>(max_free_));
        grow_bytes = std::max(grow_bytes, static_cast<uint64_t>(min_free_));
        target_size = bytes_allocated + static_cast<uint64_t>(grow_bytes * multiplier);
        next_gc_type_ = collector::kGcTypeSticky;
    } else {
        ...
        // If we have freed enough memory, shrink the heap back down.
        const size_t adjusted_max_free = static_cast<size_t>(max_free_ * multiplier);
        if (bytes_allocated + adjusted_max_free < target_footprint) {
            target_size = bytes_allocated + adjusted_max_free;
            grow_bytes = max_free_;
        } else {
            target_size = std::max(bytes_allocated, target_footprint);
            // The same whether jank perceptible or not; just avoid the adjustment.
            grow_bytes = 0;
        }
    }
    CHECK_LE(target_size, std::numeric_limits<size_t>::max());
    if (!ignore_target_footprint_) {
        SetIdealFootprint(target_size);
        ...
        if (IsGcConcurrent()) {
            const uint64_t freed_bytes = current_gc_iteration_.GetFreedBytes() + current_gc_iteration_.GetFreedLargeObjectBytes() + current_gc_iteration_.GetFreedRevokeBytes();
            // Bytes allocated will shrink by freed_bytes after the GC runs, so if we want to figure out
            // how many bytes were allocated during the GC we need to add freed_bytes back on.
            CHECK_GE(bytes_allocated + freed_bytes, bytes_allocated_before_gc);
            const size_t bytes_allocated_during_gc = bytes_allocated + freed_bytes - bytes_allocated_before_gc;
            // Calculate when to perform the next ConcurrentGC.
            // Estimate how many remaining bytes we will have when we need to start the next GC.
            size_t remaining_bytes = bytes_allocated_during_gc;
            remaining_bytes = std::min(remaining_bytes, kMaxConcurrentRemainingBytes);
            remaining_bytes = std::max(remaining_bytes, kMinConcurrentRemainingBytes);
            size_t target_footprint = target_footprint_.load(std::memory_order_relaxed);
            if (UNLIKELY(remaining_bytes > target_footprint)) {
                // A never going to happen situation that from the estimated allocation rate we will exceed
                // the applications entire footprint with the given estimated allocation rate. Schedule
                // another GC nearly straight away.
                remaining_bytes = std::min(kMinConcurrentRemainingBytes, target_footprint);
            }
            DCHECK_LE(target_footprint_.load(std::memory_order_relaxed), GetMaxMemory());
            // Start a concurrent GC when we get close to the estimated remaining bytes. When the
            // allocation rate is very high, remaining_bytes could tell us that we should start a GC
            // right away.
            concurrent_start_bytes_ = std::max(target_footprint - remaining_bytes, bytes_allocated);
        }
    }
}

concurrent_start_bytes_ 的计算分为两个步骤:

a. 计算出 target_size,一个仅具有指导意义的最大可分配字节数。

b. 根据 target_size 计算出下一次GC的触发水位 concurrent_start_bytes_。


4. target_size 的计算过程

(1) Sticky GC

kGcTypeSticky 是分代GC下的一种GC类型,表示只针对两次GC时间内新分配的对象进行回收,也可以理解为 Young-generation GC。如果 gc_type 为 kGcTypeSticky,则执行如下过程:

art/runtime/gc/heap.cc 中:

// If we have freed enough memory, shrink the heap back down.
const size_t adjusted_max_free = static_cast<size_t>(max_free_ * multiplier);
if (bytes_allocated + adjusted_max_free < target_footprint) {
    target_size = bytes_allocated + adjusted_max_free;
    grow_bytes = max_free_;
} else {
    target_size = std::max(bytes_allocated, target_footprint);
    // The same whether jank perceptible or not; just avoid the adjustment.
    grow_bytes = 0;3571
}

max_free_ 的本意是 target_size 与已分配内存间可允许的最大差异,差异过小会导致GC频繁,差异过大则又会延迟下一次GC的到来,目前很多设备将这个值设为 8M,min_free_ 设为 512K。其实针对RAM超过6G的大内存设备,Google建议可以
提高 min_free_,用空间换时间获取更好的GC性能。multiplier 的引入主要是为了优化前台应用,默认的前台 multipiler 为 2,这样可以在下次GC前有更多的空间分配对象。以下是引入 multipiler 的代码的 commit message,增大free的空间自然就降低了利用率。

[Commit Message]

Decrease target utilization for foreground apps.GC time in FormulaEvaluationActions.EvaluateAndApplyChanges goes from 26.1s to 23.2s. 
Benchmark score goes down ~50 inFormulaEvaluationActions.EvaluateAndApplyChanges, and up ~50 inGenericCalcActions.MemAllocTest.
Bug: 8788501
Change-Id: I412af1205f8b67e70a12237c990231ea62167bc0
Author: mathieuc@google.com
Date: 2014-04-17 03:37

a. 当 bytes_allocated + adjusted_max_free < target_footprint 时,说明这次GC释放了很多空间,因此可以适当地降低下次GC的触发水位。

b. 如果 bytes_allocated + adjusted_max_free ≥ target_footprint,则取 target_footprint 和 bytes_allocated 中的较大值作为 target_size。

这种情况这次GC释放的空间不多。当 target_footprint 较大时,即便 bytes_allocated 逼近 target_footprint 也不增大 target_size,是因为当前GC为Sticky GC(又可理解为Young-generation GC),如果它释放的空间不多,接下来还可以采用Full GC来更彻底地回收。换言之,只有等Full GC回收完,才能决定将GC的水位提升,因为这时已经尝试了所有的回收策略。

当 bytes_allocated 较大时,说明在GC过程中新申请的对象空间大于GC释放的空间(因为并发,所以申请和释放可以同步进行)。这样一来,最终计算的水位值将会小于 bytes_allocated,那岂不是下一次调用new分配对象时必然会阻塞?实则不然。
因为不论是 target_size 还是 concurrent_start_bytes_,他们都只有指导意义而无法实际限制堆内存的分配。对于CC Collector而言,唯一限制堆内存分配的只有 growth_limit_。不过水位值小于 bytes_allocated 倒是会使得下一次对象分配成功后立马触发一轮新的GC。


(2) Non-sticky GC

在 art/runtime/gc/heap.cc 中:

if (gc_type != collector::kGcTypeSticky) {
    // Grow the heap for non sticky GC.
    uint64_t delta = bytes_allocated * (1.0 / GetTargetHeapUtilization() - 1.0);
    DCHECK_LE(delta, std::numeric_limits<size_t>::max()) << "bytes_allocated=" << bytes_allocated << " target_utilization_=" << target_utilization_;
    grow_bytes = std::min(delta, static_cast<uint64_t>(max_free_));
    grow_bytes = std::max(grow_bytes, static_cast<uint64_t>(min_free_));
    target_size = bytes_allocated + static_cast<uint64_t>(grow_bytes * multiplier);
    next_gc_type_ = collector::kGcTypeSticky;
}

首先会根据目标的利用率计算出新的 delta,然后将 delta 与 min_free_ 和 max_free_ 进行比较,使得最终的 grow_bytes 落在 [min_free_, max_free_] 之间。target_size 的计算还需考虑 multipiler 的影响,这样会降低前台应用的堆利用率,
从而留有更多空间进行分配(降低GC的频率,代价就是内存资源向前台应用倾斜)。以下是一部手机的堆配置,其中数值可做参考。

 

5. concurrent_start_bytes_ 的计算

在 art/runtime/gc/heap.cc 中:

if (IsGcConcurrent()) {
    const uint64_t freed_bytes = current_gc_iteration_.GetFreedBytes() + current_gc_iteration_.GetFreedLargeObjectBytes() + current_gc_iteration_.GetFreedRevokeBytes();
    // Bytes allocated will shrink by freed_bytes after the GC runs, so if we want to figure out
    // how many bytes were allocated during the GC we need to add freed_bytes back on.
    CHECK_GE(bytes_allocated + freed_bytes, bytes_allocated_before_gc);
    const size_t bytes_allocated_during_gc = bytes_allocated + freed_bytes - bytes_allocated_before_gc;
    // Calculate when to perform the next ConcurrentGC.
    // Estimate how many remaining bytes we will have when we need to start the next GC.
    size_t remaining_bytes = bytes_allocated_during_gc;
    remaining_bytes = std::min(remaining_bytes, kMaxConcurrentRemainingBytes);
    remaining_bytes = std::max(remaining_bytes, kMinConcurrentRemainingBytes);
    size_t target_footprint = target_footprint_.load(std::memory_order_relaxed);
    if (UNLIKELY(remaining_bytes > target_footprint)) {
        // A never going to happen situation that from the estimated allocation rate we will exceed
        // the applications entire footprint with the given estimated allocation rate. Schedule
        // another GC nearly straight away.
        remaining_bytes = std::min(kMinConcurrentRemainingBytes, target_footprint);
    }
    DCHECK_LE(target_footprint_.load(std::memory_order_relaxed), GetMaxMemory());
    // Start a concurrent GC when we get close to the estimated remaining bytes. When the
    // allocation rate is very high, remaining_bytes could tell us that we should start a GC
    // right away.
    concurrent_start_bytes_ = std::max(target_footprint - remaining_bytes, bytes_allocated);
}

首先需要计算出在GC过程中新分配的对象大小,记为 bytes_allocated_during_gc。然后将它与 kMinConcurrentRemainingBytes 和 kMaxConcurrentRemainingBytes 进行比较,使得最终的 grow_bytes 落在 [kMinConcurrentRemainingBytes, kMaxConcurrentRemainingBytes] 之间。

// Minimum amount of remaining bytes before a concurrent GC is triggered.
static constexpr size_t kMinConcurrentRemainingBytes = 128 * KB;
static constexpr size_t kMaxConcurrentRemainingBytes = 512 * KB;

最终 concurrent_start_bytes_ 的计算如下。之所以需要用 target_footprint 减去 remaining_bytes,是因为在理论意义上,target_footprint_ 代表当前堆的最大可分配字节数。而由于是同步GC,回收的过程中可能会有其他线程依然在分配。
所以为了保证本次GC的顺利进行,需要将这段时间分配的内存空间预留出来。

concurrent_start_bytes_ = std::max(target_footprint - remaining_bytes, bytes_allocated);

不过需要注意的是,上面阐述的理由仅局限在理论意义上,就像 target_footprint_ 和 concurrent_start_bytes_ 只具有指导意义一样。所以即便下一次GC过程中分配的内存超过了预留的空间,也并不会出现内存分配不出来而等待的情况。

 

 


参考:
Android中GC的触发时机和条件:https://blog.51cto.com/u_15091060/2668373

 

posted on 2023-04-15 12:20  Hello-World3  阅读(807)  评论(0编辑  收藏  举报

导航