Android ART GC之GrowForUtilization的分析
Android運行過程中有多種情況會觸發垃圾回收(GC,garbage collection),以android 5.0為例,可以發現,在應用運行過程中最常見的觸發GC的方式如下圖所示:
此圖是通過android studio截取的android應用運行過程中某應用內存占用情況的動態變化圖,藍色部分是應用占用的內存,灰色部分是當前空閑的內存。可以看到,在白色圈內的那點,當應用空閑的內存到達某閾值時,android系統認為當前內存不太夠,所以系統喚醒GC線程來進行垃圾回收。通過logcat可以看到打印出如下log表示垃圾回收的效果。
在現在android 5.0 ART GC中,每次GC清掃玩垃圾之后,系統都會重新調整堆大小以控制堆的剩余內存使其滿足預先設置好的堆利用率等限制條件(實際上,java堆在應用啟動時就已經初始化并固定到內存地址空間中,這里調整堆大小的意思只是調整堆可用內存這個統計量,android這樣做的目的是通過動態調整堆可用內存這個統計量,使堆中的對象分布更加緊湊,可以稍微消除由于采取標記清楚垃圾回收算法導致的堆內存碎片化)。如下圖所示:
GC觸發后,回收了應用不再使用的垃圾對象,這樣可用內存就變大了,如上圖右邊collect garbage過程所示,但是android系統不會把這么大一塊可用內存都給應用,它會根據系統預先設定的堆利用率等參數調整可用內存的大小,暫且把這塊調節大小后的可用內存稱為預留空閑內存,這一過程在代碼里通過調用GrowForUtilization實現。當這塊預留空閑內存被應用使用差不多的時候就會觸發下次GC。通過后面的分析可以看出這個預留空閑內存的大小從某種意義上來說幾乎就是一個定值。
下面我就詳細分析下GrowForUtilization的實現。
GrowForUtilization在art/runtime/gc/heap.cc中實現。代碼如下:
void Heap::GrowForUtilization(collector::GarbageCollector* collector_ran) {// 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 uint64_t bytes_allocated = GetBytesAllocated();last_gc_size_ = bytes_allocated;last_gc_time_ns_ = NanoTime();uint64_t target_size;collector::GcType gc_type = collector_ran->GetGcType();if (gc_type != collector::kGcTypeSticky) {// Grow the heap for non sticky GC.const float multiplier = HeapGrowthMultiplier(); // Use the multiplier to grow more for// foreground.intptr_t delta = bytes_allocated / GetTargetHeapUtilization() - bytes_allocated;CHECK_GE(delta, 0);target_size = bytes_allocated + delta * multiplier;target_size = std::min(target_size,bytes_allocated + static_cast<uint64_t>(max_free_ * multiplier));target_size = std::max(target_size,bytes_allocated + static_cast<uint64_t>(min_free_ * multiplier));native_need_to_run_finalization_ = true;next_gc_type_ = collector::kGcTypeSticky;} else {collector::GcType non_sticky_gc_type =have_zygote_space_ ? collector::kGcTypePartial : collector::kGcTypeFull;// Find what the next non sticky collector will be.collector::GarbageCollector* non_sticky_collector = FindCollectorByGcType(non_sticky_gc_type);// If the throughput of the current sticky GC >= throughput of the non sticky collector, then// do another sticky collection next.// We also check that the bytes allocated aren't over the footprint limit in order to prevent a// pathological case where dead objects which aren't reclaimed by sticky could get accumulated// if the sticky GC throughput always remained >= the full/partial throughput.if (current_gc_iteration_.GetEstimatedThroughput() * kStickyGcThroughputAdjustment >=non_sticky_collector->GetEstimatedMeanThroughput() &&non_sticky_collector->NumberOfIterations() > 0 &&bytes_allocated <= max_allowed_footprint_) {next_gc_type_ = collector::kGcTypeSticky;} else {next_gc_type_ = non_sticky_gc_type;}// If we have freed enough memory, shrink the heap back down.if (bytes_allocated + max_free_ < max_allowed_footprint_) {target_size = bytes_allocated + max_free_;} else {target_size = std::max(bytes_allocated, static_cast<uint64_t>(max_allowed_footprint_));}}if (!ignore_max_footprint_) {SetIdealFootprint(target_size);if (IsGcConcurrent()) {// Calculate when to perform the next ConcurrentGC.// Calculate the estimated GC duration.const double gc_duration_seconds = NsToMs(current_gc_iteration_.GetDurationNs()) / 1000.0;// Estimate how many remaining bytes we will have when we need to start the next GC.size_t remaining_bytes = allocation_rate_ * gc_duration_seconds;remaining_bytes = std::min(remaining_bytes, kMaxConcurrentRemainingBytes);remaining_bytes = std::max(remaining_bytes, kMinConcurrentRemainingBytes);if (UNLIKELY(remaining_bytes > max_allowed_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 = kMinConcurrentRemainingBytes;}DCHECK_LE(remaining_bytes, max_allowed_footprint_);DCHECK_LE(max_allowed_footprint_, 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(max_allowed_footprint_ - remaining_bytes,static_cast<size_t>(bytes_allocated));}} }代碼根據GC的種類來設置target_size,而target-size - bytes-allocated就是上面所說的預留空閑內存。設max-free- = a,min-free- = b,utilization = c,multiplier = d,bytes-allocated = x,target-size = T。
當GC是不是sticky gc時,
delta = x/c - x
t1 = x + d * delta = x + d(1-c)x/c
t2 = min(t1,x + da)
T = max(t2,x + db)
預留空閑內存 = T - x
而當GC是sticky gc時,
預留空閑內存 = a
假如開發版的配置(build.prop中配置):max-free- = 8M,min-free- = 4M,utilization = 0.75,multiplier = 2 。
可以得到預留空閑內存與已使用內存的關系如下圖所示:
可以看出,當GC為sticky gc的時候,預留空閑內存就是一個定值為max-free-。而當GC為非sticky gc 的時候,預留空閑內存的大小與應用已使用的內存有關,以本開發版為例,當應用的占用內存超過24M后,預留空閑內存也成為了一個定值16M,而對于用戶經常使用的應用,很容易超過24M。超過24M之后,每次GC后,預留空閑內存就為16M,當16M用光,就會觸發下一個GC。
所以當應用在短時間內分配了很多對象的話,8/16M內存會很快用光,這樣的話GC的數量會非常的多,如下圖所示:
從代碼里也可以看出,如果本次GC不是sticky gc,那么下次gc就一定是sticky gc。而如果本次gc是sticky gc,那么會根據gc的吞吐量來決定下次是sticky gc還是partial gc或者full gc。
原文地址:?http://hello2mao.github.io/2015/12/16/Android_ART_GC_GrowForUtilization.html
總結
以上是生活随笔為你收集整理的Android ART GC之GrowForUtilization的分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android 大牛开发者博客列表
- 下一篇: 0xBenchmark中垃圾回收测试模块