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) 编辑 收藏 举报