This article analyzes the source code based on Android R(11)
Java object creation is handled by Allocator and collection is handled by Collector. Starting with Android O, the default GC Collector for foreground applications is CC(Concurrent Copying) Collector, The matching Allocator is region-based Bump Pointer Allocator(with TLAB).
I’m not going to discuss the details and implementation of CC Collector and Region TLAB in this article, both because they have a small audience and because I’m still trying to figure out the details myself and may write about them later.
In addition to the tedious details, this article aims to answer a simple question:
When exactly does the GC trigger for foreground applications?
art/runtime/gc/gc_cause.h
25 // What caused the GC?
26 enum GcCause {
27 // Invalid GC cause used as a placeholder.
28 kGcCauseNone,
29 // GC triggered by a failed allocation. Thread doing allocation is blocked waiting for GC before
30 // retrying allocation.
31 kGcCauseForAlloc,
32 // A background GC trying to ensure there is free memory ahead of allocations.
33 kGcCauseBackground,
34 // An explicit System.gc() call.
35 kGcCauseExplicit,
36 // GC triggered for a native allocation when NativeAllocationGcWatermark is exceeded.
37 // (This may be a blocking GC depending on whether we run a non-concurrent collector).
38 kGcCauseForNativeAlloc,
39 // GC triggered for a collector transition.
40 kGcCauseCollectorTransition,
41 // Not a real GC cause, used when we disable moving GC (currently for GetPrimitiveArrayCritical).
42 kGcCauseDisableMovingGc,
43 // Not a real GC cause, used when we trim the heap.
44 kGcCauseTrim,
45 // Not a real GC cause, used to implement exclusion between GC and instrumentation.
46 kGcCauseInstrumentation,
47 // Not a real GC cause, used to add or remove app image spaces.
48 kGcCauseAddRemoveAppImageSpace,
49 // Not a real GC cause, used to implement exclusion between GC and debugger.
50 kGcCauseDebugger,
51 // GC triggered for background transition when both foreground and background collector are CMS.
52 kGcCauseHomogeneousSpaceCompact,
53 // Class linker cause, used to guard filling art methods with special values.
54 kGcCauseClassLinker,
55 // Not a real GC cause, used to implement exclusion between code cache metadata and GC.
56 kGcCauseJitCodeCache,
57 // Not a real GC cause, used to add or remove system-weak holders.
58 kGcCauseAddRemoveSystemWeakHolder,
59 // Not a real GC cause, used to prevent hprof running in the middle of GC.
60 kGcCauseHprof,
61 // Not a real GC cause, used to prevent GetObjectsAllocated running in the middle of GC.
62 kGcCauseGetObjectsAllocated,
63 // GC cause for the profile saver.
64 kGcCauseProfileSaver,
65 // GC cause for running an empty checkpoint.
66 kGcCauseRunEmptyCheckpoint,
67 };
Copy the code
According to GcCause, there are many conditions that can trigger a GC. For developers, there are three common ones:
- GcCauseForAlloc: When new is used to allocate new objects, the remaining space in the heap (the default maximum for common applications is 256 MB, and the declared largeHeap is 512 MB) is insufficient, so GC is required first. This will cause the current thread to block.
- GcCauseExplicit: A GC action is generated when the application calls System API System.gc().
- GcCauseBackground: Background GC, here “background” does not refer to the application cuts to the background to execute the GC, but during the runtime GC will not affect the execution of other threads, so it can also be understood as concurrent GC. Background GC is more present and stealthy than the first two GC types, so it’s worth covering in detail. This GC is all that follows.
The actual size of the Java heap goes up and down, influenced by nothing more than allocation and recycling. The process of allocation is discrete and frequent, coming from different worker threads, and may only allocate a small area at a time. The collection process is uniform and episodic, it is performed by the HeapTaskDaemon thread, and is performed concurrently through multiple stages of the GC so that the worker thread is not paused (in fact for a short time).
When we allocate objects by new in Java code, the virtual machine calls AllocObjectWithAllocator to perform the actual allocation. After each successful allocation of a Java object, the need for a next GC is checked, which is when the GcCauseBackground GC is triggered.
art/runtime/gc/heap-inl.h
44 template <bool kInstrumented, bool kCheckLargeObject, typename PreFenceVisitor>
45 inline mirror::Object* Heap::AllocObjectWithAllocator(Thread* self,
46 ObjPtr<mirror::Class> klass,
47 size_t byte_count,
48 AllocatorType allocator,
49 const PreFenceVisitor& pre_fence_visitor) {...243 // IsGcConcurrent() isn't known at compile time so we can optimize by not checking it for
244 // the BumpPointer or TLAB allocators. This is nice since it allows the entire if statement to be
245 // optimized out. And for the other allocators, AllocatorMayHaveConcurrentGC is a constant since
246 // the allocator_type should be constant propagated.
247 if (AllocatorMayHaveConcurrentGC(allocator) && IsGcConcurrent()) {
248 // New_num_bytes_allocated is zero if we didn't update num_bytes_allocated_.
249 // That's fine.
250 CheckConcurrentGCForJava(self, new_num_bytes_allocated, &obj); <===================== This line
251 }
252 VerifyObject(obj);
253 self->VerifyStack(a);254 return obj.Ptr(a);255 }
Copy the code
467 inline void Heap::CheckConcurrentGCForJava(Thread* self,
468 size_t new_num_bytes_allocated,
469 ObjPtr<mirror::Object>* obj) {
470 if (UNLIKELY(ShouldConcurrentGCForJava(new_num_bytes_allocated))) { <======================= This line
471 RequestConcurrentGCAndSaveObject(self, false /* force_full */, obj);
472 }
473 }
Copy the code
460 inline bool Heap::ShouldConcurrentGCForJava(size_t new_num_bytes_allocated) {
461 // For a Java allocation, we only check whether the number of Java allocated bytes excceeds a
462 // threshold. By not considering native allocation here, we (a) ensure that Java heap bounds are
463 // maintained, and (b) reduce the cost of the check here.
464 return new_num_bytes_allocated >= concurrent_start_bytes_; <======================== This line
465 }
466
Copy the code
The trigger condition needs to satisfy a judgment that if new_num_bytes_allocated(all allocated bytes including the newly allocated object) >= conCURRENT_START_bytes_ (threshold for the next GC trigger) then a new GC will be requested. New_num_bytes_alloated is calculated at the time of the current allocation, and concurrent_START_bytes_ is calculated at the end of the last GC. The calculation of these two values and the design ideas behind them are described below.
1. Calculation process of new_NUM_bytes_Allocated tokens
art/runtime/gc/heap-inl.h
44 template <bool kInstrumented, bool kCheckLargeObject, typename PreFenceVisitor>
45 inline mirror::Object* Heap::AllocObjectWithAllocator(Thread* self,
46 ObjPtr<mirror::Class> klass,
47 size_t byte_count,
48 AllocatorType allocator,
49 const PreFenceVisitor& pre_fence_visitor) {...83 size_t new_num_bytes_allocated = 0;
84 {
85 // Do the initial pre-alloc
86 pre_object_allocated(a); .107 if (IsTLABAllocator(allocator)) {
108 byte_count = RoundUp(byte_count, space::BumpPointerSpace::kAlignment);
109 }
110 // If we have a thread local allocation we don't need to update bytes allocated.
111 if (IsTLABAllocator(allocator) && byte_count <= self->TlabSize()) {
112 obj = self->AllocTlab(byte_count);
113 DCHECK(obj ! =nullptr) < <"AllocTlab can't fail";
114 obj->SetClass(klass);
115 if (kUseBakerReadBarrier) {
116 obj->AssertReadBarrierState(a);117 }
118 bytes_allocated = byte_count;
119 usable_size = bytes_allocated;
120 no_suspend_pre_fence_visitor(obj, usable_size);
121 QuasiAtomic::ThreadFenceForConstructor(a);122 } else if (
123! kInstrumented && allocator == kAllocatorTypeRosAlloc &&124 (obj = rosalloc_space_->AllocThreadLocal(self, byte_count, &bytes_allocated)) ! =nullptr &&
125 LIKELY(obj ! =nullptr)) {
126 DCHECK(! is_running_on_memory_tool_);127 obj->SetClass(klass);
128 if (kUseBakerReadBarrier) {
129 obj->AssertReadBarrierState(a);130 }
131 usable_size = bytes_allocated;
132 no_suspend_pre_fence_visitor(obj, usable_size);
133 QuasiAtomic::ThreadFenceForConstructor(a);134 } else {
135 // Bytes allocated that includes bulk thread-local buffer allocations in addition to direct
136 // non-TLAB object allocations.
137 size_t bytes_tl_bulk_allocated = 0u;
138 obj = TryToAllocate<kInstrumented, false>(self, allocator, byte_count, &bytes_allocated,
139 &usable_size, &bytes_tl_bulk_allocated);
140 if (UNLIKELY(obj == nullptr)) {
141 // AllocateInternalWithGc can cause thread suspension, if someone instruments the
142 // entrypoints or changes the allocator in a suspend point here, we need to retry the
143 // allocation. It will send the pre-alloc event again.
144 obj = AllocateInternalWithGc(self,
145 allocator,
146 kInstrumented,
147 byte_count,
148 &bytes_allocated,
149 &usable_size,
150 &bytes_tl_bulk_allocated,
151 &klass);
152 if (obj == nullptr) {
153 // The only way that we can get a null return if there is no pending exception is if the
154 // allocator or instrumentation changed.
155 if(! self->IsExceptionPending()) {
156 // Since we are restarting, allow thread suspension.
157 ScopedAllowThreadSuspension ats;
158 // AllocObject will pick up the new allocator type, and instrumented as true is the safe
159 // default.
160 return AllocObject</*kInstrumented=*/true>(self,
161 klass,
162 byte_count,
163 pre_fence_visitor);
164 }
165 return nullptr;
166 }
167 }
168 DCHECK_GT(bytes_allocated, 0u);
169 DCHECK_GT(usable_size, 0u);
170 obj->SetClass(klass);
171 if (kUseBakerReadBarrier) {
172 obj->AssertReadBarrierState(a);173 }
174 if (collector::SemiSpace::kUseRememberedSet &&
175 UNLIKELY(allocator == kAllocatorTypeNonMoving)) {
176 // (Note this if statement will be constant folded away for the fast-path quick entry
177 // points.) Because SetClass() has no write barrier, the GC may need a write barrier in the
178 // case the object is non movable and points to a recently allocated movable class.
179 WriteBarrier::ForFieldWrite(obj, mirror::Object::ClassOffset(), klass);
180 }
181 no_suspend_pre_fence_visitor(obj, usable_size);
182 QuasiAtomic::ThreadFenceForConstructor(a);183 if (bytes_tl_bulk_allocated > 0) {
184 size_t num_bytes_allocated_before =
185 num_bytes_allocated_.fetch_add(bytes_tl_bulk_allocated, std::memory_order_relaxed);
186 new_num_bytes_allocated = num_bytes_allocated_before + bytes_tl_bulk_allocated;
187 // Only trace when we get an increase in the number of bytes allocated. This happens when
188 // obtaining a new TLAB and isn't often enough to hurt performance according to golem.
189 if (region_space_) {
190 // With CC collector, during a GC cycle, the heap usage increases as
191 // there are two copies of evacuated objects. Therefore, add evac-bytes
192 // to the heap size. When the GC cycle is not running, evac-bytes
193 // are 0, as required.
194 TraceHeapSize(new_num_bytes_allocated + region_space_->EvacBytes());
195 } else {
196 TraceHeapSize(new_num_bytes_allocated);
197 }
198 }
199 }
200}...243 // IsGcConcurrent() isn't known at compile time so we can optimize by not checking it for
244 // the BumpPointer or TLAB allocators. This is nice since it allows the entire if statement to be
245 // optimized out. And for the other allocators, AllocatorMayHaveConcurrentGC is a constant since
246 // the allocator_type should be constant propagated.
247 if (AllocatorMayHaveConcurrentGC(allocator) && IsGcConcurrent()) {
248 // New_num_bytes_allocated is zero if we didn't update num_bytes_allocated_.
249 // That's fine.
250 CheckConcurrentGCForJava(self, new_num_bytes_allocated, &obj);
251}...255 }
Copy the code
The actual allocation of AllocObjectWithAllocator can be divided into three branches, but if restricted to region-based Bump Pointer Allocator, there are only two branches:
1. If the remaining space of the TLAB area of the current thread can hold the allocated object, the allocated object is directly allocated in the TLAB area. The allocation algorithm uses Bump Pointer to update only the cursor of the allocated area, which is simple and efficient.
art/runtime/thread-inl.h
307 inline mirror::Object* Thread::AllocTlab(size_t bytes) { 308 DCHECK_GE(TlabSize(), bytes); 309 ++tlsPtr_.thread_local_objects; 310 mirror::Object* ret = reinterpret_cast<mirror::Object*>(tlsPtr_.thread_local_pos); 311 tlsPtr_.thread_local_pos += bytes; 312 return ret; 313}Copy the code
In this case, new_num_bytes_allocated is 0, indicating that the used area of the Java heap has not increased. This is because when TLAB was created, its size was already accounted for by NUM_bytes_allocated_, so there is no need for num_bytes_allocated_ to be increased this time, although new objects are allocated.
Then there is the question: why did TLAB count the size into NUM_bytes_allocated_ when it was created? But TLAB wasn’t being used yet. This is actually a space-for-time strategy. Here’s the commit message for this change, which improves performance by pre-sizing num_bytes_allocated_ so that it doesn’t have to be updated with each allocation. The trade-off is that num_bytes_allocated_ is slightly larger than the actual number of bytes used.
[Commit Message]
Faster TLAB allocator. New TLAB allocator doesn't increment bytes allocated until we allocate a new TLAB. This increases allocation performance by avoiding a CAS. MemAllocTest: Before GSS TLAB: 3400ms. After GSS TLAB: 2750ms. Bug: 9986565 Change-Id: I1673c27555330ee90d353b98498fa0e67bd57fad Author: [email protected] Date: 2014-07-12 05:18Copy the code
2. If the remaining space of the TLAB area of the current thread does not contain the allocated object, a new TLAB is created for the current thread. In this case, the newly allocated TLAB size needs to be counted toward num_byTES_allocated_, Therefore, new_NUM_bytes_allocated = num_bytes_allocated_before + bytes_TL_BULK_allocated.
2. The calculation process of concurrent_STARt_bytes_
art/runtime/gc/heap.cc
2573 collector::GcType Heap::CollectGarbageInternal(collector::GcType gc_type,
2574 GcCause gc_cause,
2575 bool clear_soft_references) {...2671 collector->Run(gc_cause, clear_soft_references || runtime->IsZygote());
2672 IncrementFreedEver(a);2673 RequestTrim(self);
2674 // Collect cleared references.
2675 SelfDeletingTask* clear = reference_processor_->CollectClearedReferences(self);
2676 // Grow the heap so that we know when to perform the next GC.
2677 GrowForUtilization(collector, bytes_allocated_before_gc);
2678 LogGC(gc_cause, collector);
2679 FinishGC(self, gc_type);
2680 // Actually enqueue all cleared references. Do this after the GC has officially finished since
2681 // otherwise we can deadlock.
2682 clear->Run(self);
2683 clear->Finalize(a);2684 // Inform DDMS that a GC completed.
2685 Dbg::GcDidFinish(a);2686
2687 old_native_bytes_allocated_.store(GetNativeBytes());
2688
2689 // Unload native libraries for class unloading. We do this after calling FinishGC to prevent
2690 // deadlocks in case the JNI_OnUnload function does allocations.
2691 {
2692 ScopedObjectAccess soa(self);
2693 soa.Vm() - >UnloadNativeLibraries(a);2694 }
2695 return gc_type;
2696 }
Copy the code
CollectGarbageInternal is the function that the HeapTaskDaemon thread calls when it executes GC. Line 2671 performs the actual GC, and the calculation for concurrent_STARt_bytes_ is in the GrowForUtilization function at line 2677.
art/runtime/gc/heap.cc
3514 void Heap::GrowForUtilization(collector::GarbageCollector* collector_ran,
3515 size_t bytes_allocated_before_gc) {
3516 // We know what our utilization is at this moment.
3517 // This doesn't actually resize any memory. It just lets the heap grow more when necessary.
3518 const size_t bytes_allocated = GetBytesAllocated(a);3519 // Trace the new heap size after the GC is finished.
3520 TraceHeapSize(bytes_allocated);
3521 uint64_t target_size, grow_bytes;
3522 collector::GcType gc_type = collector_ran->GetGcType(a);3523 MutexLock mu(Thread::Current(), process_state_update_lock_);
3524 // Use the multiplier to grow more for foreground.
3525 const double multiplier = HeapGrowthMultiplier(a);3526 if(gc_type ! = collector::kGcTypeSticky) {3527 // Grow the heap for non sticky GC.
3528 uint64_t delta = bytes_allocated * (1.0 / GetTargetHeapUtilization() - 1.0);
3529 DCHECK_LE(delta, std::numeric_limits<size_t> : :max()) < <"bytes_allocated=" << bytes_allocated
3530 << " target_utilization_=" << target_utilization_;
3531 grow_bytes = std::min(delta, static_cast<uint64_t>(max_free_));
3532 grow_bytes = std::max(grow_bytes, static_cast<uint64_t>(min_free_));
3533 target_size = bytes_allocated + static_cast<uint64_t>(grow_bytes * multiplier);
3534 next_gc_type_ = collector::kGcTypeSticky;
3535 } else{...3562 // If we have freed enough memory, shrink the heap back down.
3563 const size_t adjusted_max_free = static_cast<size_t>(max_free_ * multiplier);
3564 if (bytes_allocated + adjusted_max_free < target_footprint) {
3565 target_size = bytes_allocated + adjusted_max_free;
3566 grow_bytes = max_free_;
3567 } else {
3568 target_size = std::max(bytes_allocated, target_footprint);
3569 // The same whether jank perceptible or not; just avoid the adjustment.
3570 grow_bytes = 0;
3571 }
3572 }
3573 CHECK_LE(target_size, std::numeric_limits<size_t> : :max());
3574 if(! ignore_target_footprint_) {3575 SetIdealFootprint(target_size); .3585 if (IsGcConcurrent()) {
3586 const uint64_t freed_bytes = current_gc_iteration_.GetFreedBytes() +
3587 current_gc_iteration_.GetFreedLargeObjectBytes() +
3588 current_gc_iteration_.GetFreedRevokeBytes(a);3589 // Bytes allocated will shrink by freed_bytes after the GC runs, so if we want to figure out
3590 // how many bytes were allocated during the GC we need to add freed_bytes back on.
3591 CHECK_GE(bytes_allocated + freed_bytes, bytes_allocated_before_gc);
3592 const size_t bytes_allocated_during_gc = bytes_allocated + freed_bytes -
3593 bytes_allocated_before_gc;
3594 // Calculate when to perform the next ConcurrentGC.
3595 // Estimate how many remaining bytes we will have when we need to start the next GC.
3596 size_t remaining_bytes = bytes_allocated_during_gc;
3597 remaining_bytes = std::min(remaining_bytes, kMaxConcurrentRemainingBytes);
3598 remaining_bytes = std::max(remaining_bytes, kMinConcurrentRemainingBytes);
3599 size_t target_footprint = target_footprint_.load(std::memory_order_relaxed);
3600 if (UNLIKELY(remaining_bytes > target_footprint)) {
3601 // A never going to happen situation that from the estimated allocation rate we will exceed
3602 // the applications entire footprint with the given estimated allocation rate. Schedule
3603 // another GC nearly straight away.
3604 remaining_bytes = std::min(kMinConcurrentRemainingBytes, target_footprint);
3605 }
3606 DCHECK_LE(target_footprint_.load(std::memory_order_relaxed), GetMaxMemory());
3607 // Start a concurrent GC when we get close to the estimated remaining bytes. When the
3608 // allocation rate is very high, remaining_bytes could tell us that we should start a GC
3609 // right away.
3610 concurrent_start_bytes_ = std::max(target_footprint - remaining_bytes, bytes_allocated);
3611 }
3612 }
3613 }
Copy the code
The calculation of concurrent_STARt_bytes_ is divided into two steps:
- Calculate target_size, a maximum number of bytes that can be allocated, for guidance only.
- Calculate the trigger water level for the next GC, concurrent_START_bytes_, based on target_size.
2.1 Calculation process of target_size
2.1.1 Sticky GC
KGcTypeSticky is a type of GC under generational GC that only collects newly allocated objects within two GC cycles, also known as young-generation GC. If gc_type is kGcTypeSticky, the following process is performed:
art/runtime/gc/heap.cc
3562 // If we have freed enough memory, shrink the heap back down.
3563 const size_t adjusted_max_free = static_cast<size_t>(max_free_ * multiplier);
3564 if (bytes_allocated + adjusted_max_free < target_footprint) {
3565 target_size = bytes_allocated + adjusted_max_free;
3566 grow_bytes = max_free_;
3567 } else {
3568 target_size = std::max(bytes_allocated, target_footprint);
3569 // The same whether jank perceptible or not; just avoid the adjustment.
3570 grow_bytes = 0;
3571 }
Copy the code
Max_free_ is meant to be the maximum allowable difference between target_size and allocated memory. A small difference can lead to frequent GC, while a large difference can delay the next GC. Currently, many devices set this value to 8M and min_free_ to 512K. In fact, for large memory devices with more than 6GB of RAM, Google suggests improving min_free_ and trading space for time for better GC performance. Multipliers were introduced primarily to optimize foreground applications, and the default foreground multipiler is 2 so that more space can be allocated for objects before the next GC. Here is the commit message for the code that introduced multipiler, as increasing free space naturally reduces utilization.
[Commit Message]
Decrease target utilization for foreground apps. GC time in FormulaEvaluationActions.EvaluateAndApplyChanges goes from 26.1 s to 23.2 s. Benchmark score goes down ~ 50 in FormulaEvaluationActions. EvaluateAndApplyChanges, and up ~50 in GenericCalcActions.MemAllocTest. Bug: 8788501 Change-Id: I412af1205f8b67e70a12237c990231ea62167bc0 Author: [email protected] Date: 2014-04-17 03:37Copy the code
1. If Bytes_allocated + adjusted_max_free < target_footprint, this GC has freed up a lot of space so that the trigger level of the next GC can be lowered appropriately.
2. If Bytes_allocated + ADJUsted_MAX_free ≥ target_footprint, take the larger value of the target_footprint and Bytes_allocated as the target_size.
In this case, the GC does not free much space. If the target_footprint is large, the target_size will not be increased even if Bytes_allocated approaches the target_footprint, because the current GC is Sticky GC(also understood as young-generation GC). If it doesn’t free much space, you can follow up with Full GC for a more complete collection. In other words, you can’t decide to raise the GC’s water level until the Full GC has been collected, because all the collection strategies have been tried.
If Bytes_allocated object space is large, the newly allocated object space in the GC is larger than the allocated space in the GC. Allocated object. In this case, the calculated water level will be smaller than bytes_allocated object, which will block the next time the new allocated object is called. It’s not. Because neither target_SIZE nor CONcurrent_START_bytes_ are intended as guidelines and do not actually limit heap memory allocation. For CC Collector, the only limit on heap memory allocation is growth_limit_. However, a water level less than bytes_allocated can trigger a new GC as soon as the next object allocation is successful.
2.1.2 Non – sticky GC
art/runtime/gc/heap.cc
3526 if(gc_type ! = collector::kGcTypeSticky) {3527 // Grow the heap for non sticky GC.
3528 uint64_t delta = bytes_allocated * (1.0 / GetTargetHeapUtilization() - 1.0);
3529 DCHECK_LE(delta, std::numeric_limits<size_t> : :max()) < <"bytes_allocated=" << bytes_allocated
3530 << " target_utilization_=" << target_utilization_;
3531 grow_bytes = std::min(delta, static_cast<uint64_t>(max_free_));
3532 grow_bytes = std::max(grow_bytes, static_cast<uint64_t>(min_free_));
3533 target_size = bytes_allocated + static_cast<uint64_t>(grow_bytes * multiplier);
3534 next_gc_type_ = collector::kGcTypeSticky;
3535 }
Copy the code
The new delta is first calculated based on the utilization of the target, and then compared with min_free_ and max_free_ so that the final grow_bytes fall between [min_free_,max_free_]. The target_size calculation also takes into account the effect of multipiler, which reduces the heap utilization of foreground applications and leaves more space to allocate (at the cost of reducing the frequency of GC at the expense of memory resources being skewed towards foreground applications). The following is the heap configuration of a mobile phone, which can be used for reference.
2.2 Calculation of concurrent_STARt_bytes_
art/runtime/gc/heap.cc
3585 if (IsGcConcurrent()) {
3586 const uint64_t freed_bytes = current_gc_iteration_.GetFreedBytes() +
3587 current_gc_iteration_.GetFreedLargeObjectBytes() +
3588 current_gc_iteration_.GetFreedRevokeBytes(a);3589 // Bytes allocated will shrink by freed_bytes after the GC runs, so if we want to figure out
3590 // how many bytes were allocated during the GC we need to add freed_bytes back on.
3591 CHECK_GE(bytes_allocated + freed_bytes, bytes_allocated_before_gc);
3592 const size_t bytes_allocated_during_gc = bytes_allocated + freed_bytes -
3593 bytes_allocated_before_gc;
3594 // Calculate when to perform the next ConcurrentGC.
3595 // Estimate how many remaining bytes we will have when we need to start the next GC.
3596 size_t remaining_bytes = bytes_allocated_during_gc;
3597 remaining_bytes = std::min(remaining_bytes, kMaxConcurrentRemainingBytes);
3598 remaining_bytes = std::max(remaining_bytes, kMinConcurrentRemainingBytes);
3599 size_t target_footprint = target_footprint_.load(std::memory_order_relaxed);
3600 if (UNLIKELY(remaining_bytes > target_footprint)) {
3601 // A never going to happen situation that from the estimated allocation rate we will exceed
3602 // the applications entire footprint with the given estimated allocation rate. Schedule
3603 // another GC nearly straight away.
3604 remaining_bytes = std::min(kMinConcurrentRemainingBytes, target_footprint);
3605 }
3606 DCHECK_LE(target_footprint_.load(std::memory_order_relaxed), GetMaxMemory());
3607 // Start a concurrent GC when we get close to the estimated remaining bytes. When the
3608 // allocation rate is very high, remaining_bytes could tell us that we should start a GC
3609 // right away.
3610 concurrent_start_bytes_ = std::max(target_footprint - remaining_bytes, bytes_allocated);
3611 }
Copy the code
The first step is to calculate the size of the newly allocated object during the GC, denoted bytes_allocated_during_GC. Then it compared with kMinConcurrentRemainingBytes and kMaxConcurrentRemainingBytes, Make an eventual grow_bytes fall between [kMinConcurrentRemainingBytes kMaxConcurrentRemainingBytes].
art/runtime/gc/heap.cc
108 // Minimum amount of remaining bytes before a concurrent GC is triggered.
109 static constexpr size_t kMinConcurrentRemainingBytes = 128 * KB;
110 static constexpr size_t kMaxConcurrentRemainingBytes = 512 * KB;
Copy the code
The final concurrent_STARt_bytes_ calculation is as follows. The target_footprint minus remaining_bytes is required because in a theoretical sense, target_footprint_ represents the maximum number of bytes currently allocated in the heap. Because of synchronous GC, there may be other threads still allocating during the collection process. Therefore, in order to ensure the smooth progress of this GC, the memory space allocated during this period needs to be reserved.
art/runtime/gc/heap.cc
concurrent_start_bytes_ = std::max(target_footprint - remaining_bytes, bytes_allocated);
Copy the code
It is important to note, however, that the reasons given above are theoretical, just as target_footprint_ and concurrent_STARt_bytes_ are instructive. So even if more memory is allocated during the next GC than the reserved space, there is no waiting for memory to be allocated.