ART堆大小设置及动态调整过程分析(Android 8.1)

ART虚拟机中,堆(Heap)空间负责进行对象内存的分配。堆的大小也决定了一个应用可使用的内存大小。当应用内存量超过了Android系统给定的最大堆内存限制时,就会产生OOM。

我们接下来逐步分析,决定堆内存大小的因素;以及伴随GC过程中的堆大小的状态变化。

堆的初始设定

手机出厂时,会设置堆内存相关的几个系统参数(每个厂商和机型的设定可能不同):

# Provides overrides to configure the Dalvik heap for a 2G phone
# 192m of RAM gives enough space for 5 8 megapixel camera bitmaps in RAM.

PRODUCT_PROPERTY_OVERRIDES += \
    dalvik.vm.heapstartsize=8m \  //表示进程启动后,堆得初始内存大小,它影响初始启动的流畅度。
    dalvik.vm.heapgrowthlimit=192m \ //单个进程的最大可用的堆的内存。(不包含netive堆)
    dalvik.vm.heapsize=512m \ //单个进程理论上可使用的内存的最大值。
    dalvik.vm.heaptargetutilization=0.75 \ //内存利用率
    dalvik.vm.heapminfree=512k \ //堆最小空闲内存heapminfree
    dalvik.vm.heapmaxfree=8m //堆最大空闲内存heapmaxfree

这几个参数决定了Android中,堆内存的大小及堆变化的控制。

dalvik.vm.heapstartsize

Android在每个应用启动时,都会给每个应用一个初始的可用内存空间,也就是设定一个初始的堆大小。这是因为如果应用刚启动,就给这个应用直接分配最大可用内存空间,那么一部手机也就运行不了几个应用就会消耗掉所有的内存空间了。Android为了,提高用户体验,让系统在有限的内存资源中,启动更多的应用,在应用启动时,都会给予应用一个很小的内存空间,随着应用内存需求的增加,系统会逐步增加该应用的可用内存空间(也就是堆空间的大小)。初始堆大小是通过系统参数dalvik.vm.heapstartsize设定的。

dalvik.vm.heapgrowthlimit

dalvik.vm.heapgrowthlimit表示堆得最大可用内存值。也就是说,应用启动后,随着堆空间的增加,占用的系统内存量也会逐步增加,但是增长是有限制的,堆得空间不可能无限增长。dalvik.vm.heapgrowthlimit就代表了Java堆内存的增长上限值。

dalvik.vm.heapsize

dalvik.vm.heapsize表示应用最大可用的堆内存空间。当我们在应用程序Manifest中配置android:largeHeap="true"时,我们的最大堆内存大小就可以使用dalvik.vm.heapsize的值了。它代表了“大堆”空间,目的是给有特殊需求的,内存使用量非常大的应用使用的。

dalvik.vm.heaptargetutilization

dalvik.vm.heaptargetutilization代表了内存的利用率。每次GC之后会评估当前堆内存的大小是否是适合的。dalvik.vm.heaptargetutilization = 已使用内存良/堆大小。过大或者过小都会影响GC的频繁程度及效率,它通常被设置为0.75。

dalvik.vm.heapminfree

dalvik.vm.heapminfree可允许的堆的最大空闲内存量。

dalvik.vm.heapmaxfree

dalvik.vm.heapmaxfree可允许的堆的最小空闲内存量。

查看手机中堆相关系统参数

  1. 我们可以通过adb命令查看测试手机的相关参数

    方法一:

    adb shell  getprop 

    [dalvik.vm.heapgrowthlimit]: [256m]
    [dalvik.vm.heapmaxfree]: [8m]
    [dalvik.vm.heapminfree]: [512k]
    [dalvik.vm.heapsize]: [512m]
    [dalvik.vm.heapstartsize]: [8m]
    [dalvik.vm.heaptargetutilization]: [0.75]

    方法二:

    adb shell cat /system/build.prop   #需要root权限

    dalvik.vm.heapgrowthlimit=256m
    dalvik.vm.heapstartsize=8m
    dalvik.vm.heapsize=512m
    dalvik.vm.heaptargetutilization=0.75
    dalvik.vm.heapminfree=512k
    dalvik.vm.heapmaxfree=8m

  2. 运行时查看
    代码位置:/frameworks/base/core/java/android/app/ActivityManager.java

        public int getMemoryClass() {
            return staticGetMemoryClass();
        }

        /** @hide */
        static public int staticGetMemoryClass() {
            // Really brain dead right now -- just take this from the configured
            // vm heap size, and assume it is in megabytes and thus ends with "m".
            String vmHeapSize = SystemProperties.get("dalvik.vm.heapgrowthlimit", "");
            if (vmHeapSize != null && !"".equals(vmHeapSize)) {
                return Integer.parseInt(vmHeapSize.substring(0, vmHeapSize.length()-1));
            }
            return staticGetLargeMemoryClass();
        }
        
        public int getLargeMemoryClass() {
            return staticGetLargeMemoryClass();
        }

        /** @hide */
        static public int staticGetLargeMemoryClass() {
            // Really brain dead right now -- just take this from the configured
            // vm heap size, and assume it is in megabytes and thus ends with "m".
            String vmHeapSize = SystemProperties.get("dalvik.vm.heapsize", "16m");
            return Integer.parseInt(vmHeapSize.substring(0, vmHeapSize.length() - 1));
        }

Heap类中的相关属性

当应用(进程)启动后,会创建相关堆用于内存分配。

heap.h中保存了堆大小相关属性(/art/runtime/gc/heap.h):

  // Maximum size that the heap can reach.
  size_t capacity_;

  // The size the heap is limited to. This is initially smaller than capacity, but for largeHeap
  // programs it is "cleared" making it the same as capacity.
  size_t growth_limit_;

  // When the number of bytes allocated exceeds the footprint TryAllocate returns null indicating
  // a GC should be triggered.
  size_t max_allowed_footprint_;

  // When num_bytes_allocated_ exceeds this amount then a concurrent GC should be requested so that
  // it completes ahead of an allocation failing.
  size_t concurrent_start_bytes_;

  // Since the heap was created, how many bytes have been freed.
  uint64_t total_bytes_freed_ever_;

  // Since the heap was created, how many objects have been freed.
  uint64_t total_objects_freed_ever_;

  // Number of bytes allocated.  Adjusted after each allocation and free.
  Atomic<size_t> num_bytes_allocated_;
  
  // Minimum free guarantees that you always have at least min_free_ free bytes after growing for
  // utilization, regardless of target utilization ratio.
  size_t min_free_;

  // The ideal maximum free size, when we grow the heap for utilization.
  size_t max_free_;

  // Target ideal heap utilization ratio
  double target_utilization_;

  • capacity_:堆可以达到的最大大小。对应dalvik.vm.heapsize。

  • growth_limit_:堆的大小限制。对应dalvik.vm.heapgrowthlimit。

  • max_allowed_footprint_:当前最大可分配空间容量。

  • concurrent_start_bytes_:当num_bytes_allocated_超过此数量时,应该请求并发GC,以便在分配失败之前完成。

  • total_bytes_freed_ever_:自创建堆以来,已释放了多少字节。

  • min_free_:最小空闲内存。对应dalvik.vm.heapminfree。

  • max_free_:最大空闲内存。对应dalvik.vm.heapmaxfree。

以上相关参数,在进程启动后会有相对应的默认值。Android中可分配内存的堆也会随着程序的允许而动态调整。

堆大小的调整过程分析

堆内存空间的大小会随着应用程序的运行而动态改变。当应用程序创建了大量对象,需要更多内存空间时,可用的堆内存阈值(当前堆的最大可用内存值)就会增加;而当GC过后,回收了大量内存空间之后,可用的堆内存阈值就会相应变小,以释放给系统更多未使用的内存资源。

通常,堆内存的改变都是在GC之后发生的,在GC之后,系统会根据当前的已使用内存大小、未使用内存大小、内存使用率、最大内存空闲值、最小内存空闲值等参数,最终计算出当前堆内存的最优大小。那么在什么情况下会运行GC呢?比如,在对象内存分配尝试失败之后、堆上已分配内存达到一定阈值、堆空间切换等场景,都会触发GC过程。GC主要过程是通过CollectGarbageInternal方法实现的。

CollectGarbageInternal方法

我们来看CollectGarbageInternal方法(/art/runtime/gc/heap.cc):

collector::GcType Heap::CollectGarbageInternal(collector::GcType gc_type,
                                               GcCause gc_cause,
                                               bool clear_soft_references) {
  …… //一些错误处理逻辑
  …… //压缩GC相关处理
  if (gc_cause == kGcCauseForAlloc && runtime->HasStatsEnabled()) {//对象内存分配引发的GC。记录GC次数
    ++runtime->GetStats()->gc_for_alloc_count;
    ++self->GetStats()->gc_for_alloc_count;
  }
  const uint64_t bytes_allocated_before_gc = GetBytesAllocated();//当前已分配的内存量

  if (gc_type == NonStickyGcType()) {
    // Move all bytes from new_native_bytes_allocated_ to
    // old_native_bytes_allocated_ now that GC has been triggered, resetting
    // new_native_bytes_allocated_ to zero in the process.
    old_native_bytes_allocated_.FetchAndAddRelaxed(new_native_bytes_allocated_.ExchangeRelaxed(0));
    if (gc_cause == kGcCauseForNativeAllocBlocking) {
      MutexLock mu(self, *native_blocking_gc_lock_);
      native_blocking_gc_in_progress_ = true;
    }
  }

  DCHECK_LT(gc_type, collector::kGcTypeMax);
  DCHECK_NE(gc_type, collector::kGcTypeNone);
  //以下设置,选择哪个垃圾回收器
  collector::GarbageCollector* collector = nullptr;
  // TODO: Clean this up.
  if (compacting_gc) {
    DCHECK(current_allocator_ == kAllocatorTypeBumpPointer ||
           current_allocator_ == kAllocatorTypeTLAB ||
           current_allocator_ == kAllocatorTypeRegion ||
           current_allocator_ == kAllocatorTypeRegionTLAB);
    switch (collector_type_) {
      case kCollectorTypeSS:
        // Fall-through.
      case kCollectorTypeGSS:
        semi_space_collector_->SetFromSpace(bump_pointer_space_);
        semi_space_collector_->SetToSpace(temp_space_);
        semi_space_collector_->SetSwapSemiSpaces(true);
        collector = semi_space_collector_;
        break;
      case kCollectorTypeCC:
        collector = concurrent_copying_collector_;
        break;
      case kCollectorTypeMC:
        mark_compact_collector_->SetSpace(bump_pointer_space_);
        collector = mark_compact_collector_;
        break;
      default:
        LOG(FATAL) << "Invalid collector type " << static_cast<size_t>(collector_type_);
    }
    if (collector != mark_compact_collector_ && collector != concurrent_copying_collector_) {
      temp_space_->GetMemMap()->Protect(PROT_READ | PROT_WRITE);
      if (kIsDebugBuild) {
        // Try to read each page of the memory map in case mprotect didn't work properly b/19894268.
        temp_space_->GetMemMap()->TryReadable();
      }
      CHECK(temp_space_->IsEmpty());
    }
    gc_type = collector::kGcTypeFull;  // TODO: Not hard code this in.
  } else if (current_allocator_ == kAllocatorTypeRosAlloc ||
      current_allocator_ == kAllocatorTypeDlMalloc) {
    collector = FindCollectorByGcType(gc_type);
  } else {
    LOG(FATAL) << "Invalid current allocator " << current_allocator_;
  }
  if (IsGcConcurrent()) {
    // Disable concurrent GC check so that we don't have spammy JNI requests.
    // This gets recalculated in GrowForUtilization. It is important that it is disabled /
    // calculated in the same thread so that there aren't any races that can cause it to become
    // permanantly disabled. b/17942071
    concurrent_start_bytes_ = std::numeric_limits<size_t>::max();
  }

  CHECK(collector != nullptr)
      << "Could not find garbage collector with collector_type="
      << static_cast<size_t>(collector_type_) << " and gc_type=" << gc_type;
  collector->Run(gc_cause, clear_soft_references || runtime->IsZygote()); //执行GC
  total_objects_freed_ever_ += GetCurrentGcIteration()->GetFreedObjects(); //对象释放数量
  total_bytes_freed_ever_ += GetCurrentGcIteration()->GetFreedBytes(); //GC的内存回收量
  RequestTrim(self);//裁剪堆
  // Enqueue cleared references.
  reference_processor_->EnqueueClearedReferences(self);//处理被回收了的引用对象
  // Grow the heap so that we know when to perform the next GC.
  GrowForUtilization(collector, bytes_allocated_before_gc);//调整堆的大小
  LogGC(gc_cause, collector);
  FinishGC(self, gc_type);
  // Inform DDMS that a GC completed.
  Dbg::GcDidFinish();
  // 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;
}

主要过程:

  1. 错误处理逻辑、压缩GC等相关处理。

  2. 设置本次GC所使用的垃圾回收器。

  3. 调用collector->Run执行GC。

  4. 调用RequestTrim(self)执行堆剪切,将内存归还给内核。此操作会造成卡顿,Android设置了一个最小时间间隔来优化性能。

  5. 调用EnqueueClearedReferences()处理被回收了的引用对象。

  6. 调用GrowForUtilization()调整堆的大小。

GrowForUtilization方法

GC完成之后,就会进行堆内存空间大小的调整,这个过程是通过GrowForUtilization方法实现的。

我们来看GrowForUtilization方法(/art/runtime/gc/heap.cc):void Heap::GrowForUtilization(collector::GarbageCollector* collector_ran,
                              uint64_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 uint64_t bytes_allocated = GetBytesAllocated();//已分配内存大小
  // Trace the new heap size after the GC is finished.
  TraceHeapSize(bytes_allocated);
  uint64_t target_size;
  collector::GcType gc_type = collector_ran->GetGcType();//GC类型
  const double multiplier = HeapGrowthMultiplier();  // 乘数因子,用于为前台应用设置更高的空闲内存
  // 前台应用设置,最小、最大空闲内存均乘于乘数因子。
  const uint64_t adjusted_min_free = static_cast<uint64_t>(min_free_ * multiplier);
  const uint64_t adjusted_max_free = static_cast<uint64_t>(max_free_ * multiplier);
  if (gc_type != collector::kGcTypeSticky) {//非粘性GC配置
    // Grow the heap for non sticky GC.
    ssize_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 + adjusted_max_free);
    target_size = std::max(target_size, bytes_allocated + adjusted_min_free);
    next_gc_type_ = collector::kGcTypeSticky;
  } else {//粘性GC配置  
    collector::GcType non_sticky_gc_type = NonStickyGcType();
    // 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 + adjusted_max_free < max_allowed_footprint_) {
      target_size = bytes_allocated + adjusted_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()) {
      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 uint64_t bytes_allocated_during_gc = bytes_allocated + freed_bytes -
          bytes_allocated_before_gc;
      // 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 = bytes_allocated_during_gc * 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));
    }
  }
}

  1. 乘数因子,用于为前台应用设置更高的空闲内存。前台应用设置,最小、最大空闲内存均乘于乘数因子。

  2. 设置目标堆大小,设置下一次GC类型。这里分为了粘性GC(只回收上次垃圾回收到本次垃圾回收之间产生的垃圾)、非粘性GC(部分GC和full GC)。

  3. 非粘性GC的设置:

  • 目标大小target_size的值 = 已分配大小值bytes_allocated + 按照内存利用率计算出的空闲内存值 * 乘数因子multiplier;
  • target_size的大小设置后的空闲内存量,必须在heapminfree和heapmaxfree之间(前台进程都乘了multiplier)
  • 下次GC类型设置为粘性GC。
  1. 粘性GC设置:
  • 设置下一次GC类型。
  • 目标大小设置比较简单,只考虑最大空闲。
  1. 调用SetIdealFootprint(target_size)方法设置堆大小,也就是设置max_allowed_footprint_属性的值。

  2. 对于并发GC,需要处理并发过程中的对象分配情况。

SetIdealFootprint方法

修改当前允许分配的最大空间(/art/runtime/gc/heap.cc):

void Heap::SetIdealFootprint(size_t max_allowed_footprint) {
  if (max_allowed_footprint > GetMaxMemory()) {
    VLOG(gc) << "Clamp target GC heap from " << PrettySize(max_allowed_footprint) << " to "
             << PrettySize(GetMaxMemory());
    max_allowed_footprint = GetMaxMemory();
  }
  max_allowed_footprint_ = max_allowed_footprint;
}

GetMaxMemory方法

返回当前进程可使用的最大内存值(/art/runtime/gc/heap.h):

  size_t GetMaxMemory() const {
    // There is some race conditions in the allocation code that can cause bytes allocated to
    // become larger than growth_limit_ in rare cases.
    return std::max(GetBytesAllocated(), growth_limit_);//返回已分配内存量和堆内存大小限制值之间的最大值。
  }

小结

至此,ART堆空间大小的设置,以及堆空间大小的调整过程我们已经分析完了。下面我们来总结一下。

  1. ART虚拟机中,堆(Heap)空间负责进行对象内存的分配。堆的大小也决定了一个应用可使用的内存大小。

  2. 手机出厂时,会设置堆内存相关的几个系统参数(每个厂商和机型的设定可能不同),这几个参数决定了Android中,堆内存的大小及堆变化的控制。

  3. heap.h中保存了堆大小相关属性。

  4. 堆内存空间的大小会随着应用程序的运行而动态改变。

  5. 通常,堆内存的改变都是在GC之后发生的,在GC之后,系统会根据当前的已使用内存大小、未使用内存大小、内存使用率、最大内存空闲值、最小内存空闲值等参数,最终计算出当前堆内存的最优大小。

  6. GC主要过程是通过CollectGarbageInternal方法实现的。

  7. GC完成之后,就会进行堆内存空间大小的调整,这个过程是通过GrowForUtilization方法实现的。

  8. GrowForUtilization方法会设置目标堆大小,设置下一次GC类型。并且会区分粘性GC(只回收上次垃圾回收到本次垃圾回收之间产生的垃圾)、非粘性GC(部分GC和full GC)。

转载:ART堆大小设置及动态调整过程分析(Android 8.1)_卜大爷的博客-CSDN博客_dalvik.vm.heapmaxfree

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值