heaptargetutilization/heapmaxfree/heapminfree/heapstartsize/multiplier虚拟机参数的配置

关于虚拟机的参数的调整 --- heaptargetutilization/heapmaxfree/heapminfree/heapstartsize/foreground-heap-growth-multiplier的配置

1. 这些参数都是什么意思

[dalvik.vm.foreground-heap-growth-multiplier]: [2.0]
=>
标记的是前后台进程分配内存用量的时候增长空间的比例,如果设置了2.0,代表前台分配空间会是3倍的增长。

建议配置值:2.0 (前后台申请内存用量分开,前台是后台的3倍)
如果是特别低内存的版本如512M,可以不设置这个值,如果不存在这个值,在低内存设备默认前台也会是1倍的增长(相当于值设置了 0 )

[dalvik.vm.heaptargetutilization]: [0.5]
=>
Android Q或者更小的android版本默认值是0.5, android R/S已经将这个值默认设置成0.75
具体可以查看art/runtime/gc/heap.hkDefaultTargetUtilization

值越大,每次增长堆的量越小,堆的利用率越大
如果是0.5, 则每次前台增长空间量大约是 已分配空间的 3倍, 后台为 1倍
如果是 0.75, 则每次前台增长空间量大约是 已分配空间的 1倍, 后台为 1/3倍

=>这个值改成0.75可以降低process的memory用量,该值影响的是zygote进程的初始用量(如某个项目,修改前是6m,修改后是3m),
对于每个进程的最终用量也会有优化(影响的是最后memory分配后稳定的值的范围)

建议配置值:0.75,当然如果你内存比较充裕,设置成0.5也没有问题

[dalvik.vm.heapmaxfree]: [8m]
=>
heap 的max_free 就是用来设置max_free_
用来设置单次堆内存调整的最大值。 注:这里还需要 * multiplier, 即 后台为 max_free_, 前台为max_free_ * 3(这里写的是heaptargetutilization是0.5的情况)

这个可以明显减少gc次数,为调整前system_server gc次数是21次/com.android.systemui是18次,调整后system_server gc次数是16次/com.android.systemui是6次

默认art中没有配置是2M, 请实际测试开机是各个进程gc此次,
还有如taobao/weixin启动过程中gc次数再来确定这个值(需要兼顾实体memory用量大小)

如下是建议配置值: 1G-2G RAM : 2m/3m 3G-4G RAM : 4mheapstartsize 4G-6G RAM : 8m
8G RAM以上请实际测试为准

[dalvik.vm.heapminfree]: [512k]
=>
对应min_free_ ,设置单次增长最小值。也是一样要* multiplier ,同时前后也不一样会根据heaptargetutilization 调整
和上面的heapmaxfree 是一个逻辑

在低RAM的设备上(如2G或者以下)不建议调整这个值,在高RAM设备上(如6G或者以上)可以适当调整这个值,
这个值会影响很多小内存进程的内存用量,太大了会对内存压力影响较大

建议配置值: 512K,内存充裕可以设置成2m/4m/8m

[dalvik.vm.heapstartsize]: [2m]
=>
art默认设置成2m
具体可以查看art/runtime/gc/heap.hstatic constexpr size_t kDefaultInitialSize = 2 * MB;

在此基础上,把startsize改为8M 50M都对 max_allowed_footprint_(现在叫target_footprint_)的初始值没有大的影响
因为在 PreZygoteFork 时会对 Zygote 做一次GC,这时 Zygote 会调整这个 footprint
后面进程都是从Zygote fork出来的,所以新进程的值主要依赖于上面这个 Zygote 的值
当然 Zygote 的值是变动的,新fork出来的进程值也是变动的

建议配置值:关注zygote大小和gc次数再确定,可以根据设备内存大小适当增大这个值,如5m/8m/16m都是可以配置的

2. ART解析使用这些参数

这些参数,传入都是在虚拟机初始化的时候,注意里面的注释,如dalvik.vm.heapstartsize,传入虚拟机中的是-Xms

//frameworks/base/core/jni/AndroidRuntime.cpp
parseRuntimeOption("dalvik.vm.heapstartsize", heapstartsizeOptsBuf, "-Xms", "4m");//传入的是-Xms
parseRuntimeOption("dalvik.vm.heapminfree", heapminfreeOptsBuf, "-XX:HeapMinFree=");//传入的是-XX:HeapMinFree
parseRuntimeOption("dalvik.vm.heapmaxfree", heapmaxfreeOptsBuf, "-XX:HeapMaxFree=");//传入的是-XX:HeapMaxFree
parseRuntimeOption("dalvik.vm.heaptargetutilization",
                   heaptargetutilizationOptsBuf,
                   "-XX:HeapTargetUtilization=");//传入的是-XX:HeapTargetUtilization
/* Foreground heap growth multiplier option */
parseRuntimeOption("dalvik.vm.foreground-heap-growth-multiplier",
                 foregroundHeapGrowthMultiplierOptsBuf,
                 "-XX:ForegroundHeapGrowthMultiplier=");//传入的是-XX:ForegroundHeapGrowthMultiplier

后面art解析参数是在art/runtime/parsed_options.cc,使用其构建heap是在art/runtime/runtime.cc和之前介绍heapgrowthlimit/heapsize流程是一样的。

dalvik.vm.foreground-heap-growth-multiplier需要特别提一下,关于章节1里面讲的“前后台”内存申请的区别具体代码在下面,最终用来构建前台内存申请的倍数foreground_heap_growth_multiplier

//art/runtime/gc/heap.h
static constexpr double kDefaultHeapGrowthMultiplier = 2.0;

//art/runtime/runtime.cc
static constexpr double kExtraDefaultHeapGrowthMultiplier = kUseReadBarrier ? 1.0 : 0.0;//默认这个是1.0

  float foreground_heap_growth_multiplier;
  //低内存没有设置dalvik.vm.foreground-heap-growth-multiplier默认是1.0,如果设置了就跑else的逻辑
  if (is_low_memory_mode_ && !runtime_options.Exists(Opt::ForegroundHeapGrowthMultiplier)) {
    // If low memory mode, use 1.0 as the multiplier by default.
    foreground_heap_growth_multiplier = 1.0f;
  } else {
    //其它情况没有设置默认是dalvik.vm.foreground-heap-growth-multiplier + 1 = 3;(ForegroundHeapGrowthMultiplier没设置是2)
    foreground_heap_growth_multiplier =
        runtime_options.GetOrDefault(Opt::ForegroundHeapGrowthMultiplier) +
            kExtraDefaultHeapGrowthMultiplier;
  }

构建heap的时候,这几个属性值都对应如下参数

Heap::Heap(size_t initial_size, //dalvik.vm.heapstartsize
           size_t min_free, //dalvik.vm.heapminfree
           size_t max_free, //dalvik.vm.heapmaxfree
           double target_utilization,//heaptargetutilization
           double foreground_heap_growth_multiplier,//heaptargetutilization
    :
      target_footprint_(initial_size),
      min_free_(min_free),
      max_free_(max_free),
      target_utilization_(target_utilization),
      foreground_heap_growth_multiplier_(foreground_heap_growth_multiplier),

后面我们直接来讲一下其中比较关键的一个函数GrowForUtilization,如CollectGarbageInternal->GrowForUtilizationCollectGarbageInternal会先回收gc,然后再调用GrowForUtilization进行内存增长,然后gc才会结束

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();//bytes_allocated 需要分配的内存用量
  // Trace the new heap size after the GC is finished.
  TraceHeapSize(bytes_allocated);//trace这个应用
  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.
  const double multiplier = HeapGrowthMultiplier();  //返回需要增长的内存用量倍数,上面已经讲过,前后台比例会根据dalvik.vm.foreground-heap-growth-multiplier的设置也改变
  if (gc_type != collector::kGcTypeSticky) {//一般都走进这里来
    // Grow the heap for non sticky GC.
    // GetTargetHeapUtilization获取的是target_utilization_,也就是dalvik.vm.heaptargetutilization
    // 如果dalvik.vm.heaptargetutilization设置0.5, 那么bytes_allocated/0.5 - bytes_allocated = bytes_allocated,就是被分配的内存,1倍的增长量
    // 如果dalvik.vm.heaptargetutilization设置0.75,那么bytes_allocated/0.75 - bytes_allocated = 1/3 * bytes_allocated,1/3的增长量
    // delta 你可以先认为是当前准备增长的内存用量
    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_;
    // 准备增长的内存用量delta,和max_free_(dalvik.vm.heapmaxfree)的最小值,最大不能超过max_free_
    grow_bytes = std::min(delta, static_cast<uint64_t>(max_free_));
    // 准备增长的内存用量grow_bytes,和min_free_(dalvik.vm.heapminfree)的最大值,最小不会小于min_free_
    grow_bytes = std::max(grow_bytes, static_cast<uint64_t>(min_free_));
    // 最终增加的内存用量是grow_bytes * multiplier(上面说的前后台增长比例,一般前台是3.0,后台是1.0)
    // 最后得出内存用量目标的大小target_size
    target_size = bytes_allocated + static_cast<uint64_t>(grow_bytes * multiplier);
    next_gc_type_ = collector::kGcTypeSticky;

=> GetBytesAllocated获取的是当前内存的用量

  // Number of bytes currently allocated and not yet reclaimed. Includes active
  // TLABS in their entirety, even if they have not yet been parceled out.
  Atomic<size_t> num_bytes_allocated_;

  // Returns the number of bytes currently allocated.
  // The result should be treated as an approximation, if it is being concurrently updated.
  size_t GetBytesAllocated() const {
    return num_bytes_allocated_.load(std::memory_order_relaxed);
  }

到这里基本上虚拟机内存分配的逻辑讲的差不多了。后面会讲一个虚拟机分配,通俗的例子

3. 案例讲解

1.org (heaptargetutilization 0.5, heapmaxfree=2m, heapminfree=512k):

(前台增长空间量大约是 增加已分配空间的 3倍, 后台为 1倍)

=> 如上一次gc之后,需要1M,那么前台设置4M的基准值,后台2M基准值

前台增量给约束到1.5m-6m之间(heapminfree的3倍是1.5m,heapmaxfree的3倍是6m):也就是最终值(下次到达后触发gc的值)是4m

后台增量给约束到512K-2m之间:也就是最终值(下次到达后触发gc的值)是2m

=> 如上一次gc之后,需要10M,那么前台设置40M的基准值,后台20M基准值

前台增量给约束到1.5m-6m之间:也就是最终值是16m

后台增量给约束到512K-2m之间,也就是最终值是12m

=>(前台进程2M、后台进程2M以下的进程都是基准值进行调整,2M已经是增加6m/2m了)

2.0.75设置之后(heaptargetutilization 0.75, heapmaxfree=2m, heapminfree=512k):

0.75设置之后,会修改基准值:前台增长空间量大约是 已分配空间的 1倍, 后台为 1/3倍

=> 如上一次gc之后,需要1M,那么前台设置2M的基准值,后台1.3M基准值

前台增量给约束到1.5m-6m之间:也就是最终值是2.5m //最低增长1.5M

后台增量给约束到512K-2m之间:也就是最终值是1.5m //最低增长0.5M

=> 如上一次gc之后,需要10M,那么前台设置20M的基准值,后台13.3M基准值

前台增量给约束到1.5m-6m之间:也就是最终值是16m

后台增量给约束到512K-2m之间,也就是最终值是12m

ps: 修改对于小应用有影响,大应用影响不大,因为大应用都是基于在大的heapmaxfree在确定下次gc的值。 小进程gc的频率可能增加(前台进程6M、后台进程6M以下的进程),不过考虑到最终小应用的内存用量会减少,而且小进程的堆小的话gc时间也会较短(影响较小)。

3.0.75+heapmaxfree=3m(heaptargetutilization 0.75, heapmaxfree=3m, heapminfree=512k)

0.75设置之后,会修改基准值:前台增长空间量大约是 已分配空间的 1倍, 后台为 1/3倍

=> 如上一次gc之后,需要1M,那么前台设置2M的基准值,后台1.3M基准值

前台增量给约束到1.5m-9m之间:也就是最终值是2.5m

后台增量给约束到512K-3m之间:也就是最终值是1.5m

=> 如上一次gc之后,需要10M,那么前台设置20M的基准值,后台13.3M基准值

前台增量给约束到1.5m-9m之间:也就是最终值是19m

后台增量给约束到512K-3m之间,也就是最终值是13m

ps:可以看到heapmaxfree增加可以显著减少大应用gc次数,更快分配到相应的堆大小,对于第三方应用应该有帮助。 小应用和上述一样,跟这个值关系不是很大。这个修改是为了第三方和一些大应用的gc造成的卡顿。

=>通俗的分配内存例子
100变量(假设100的变量中有1半可以给gc,有1半是临时变量用完可以马上回收)
利用率0.75(前台2倍)

50 触发gc :

0/50->分配50->50/50(使用量/上限值)->gc->25/50->(根据利用率调整)->25/50->再次分配25->50(有25的已经回收不能再次回收)/50->gc->37.5/50->(根据利用率调整)->37.5/(37.5*2=75)
->最后再分配25(目前已经分配完了100)->62.5/75 =>最后的值是62.5

30 触发gc:

0/30->分配30->30/30(使用量/上限值)->gc->15/30->(根据利用率调整)->15/30->再次分配15->30(有15的已经回收不能再次回收)/30->gc->22.5/30->(根据利用率调整)->22.5/45
->最后再分配22.5->45(有22.5的已经回收不能再次回收)/45->gc->33.75/45->(根据利用率调整)->33.75/67.5->再次分配32.5(目前已经分配完了100)->66.25/67.5

=> gc此处增加了,最后一次分配的量就是剩余没有gc的数值

4. ART参数调试时加入的日志

具体请参考下面的日志,
1、查看调整后的after1 target_size和最初请求的大小org1 target_size之间的差异。
2、同时也可以查看调整前后system_server 、com.android.systemui、微信、淘宝冷启动时gc的次数
3、还需要system_server 、com.android.systemui、zygote等系统各个进程最终稳定时的内存用量(如果嫌个数多,至少需要查看上面说的3个),如开机5分钟用google脚本测试内存用量值。

void Heap::GrowForUtilization(collector::GarbageCollector* collector_ran,
                              size_t bytes_allocated_before_gc) {//内存增长的时候调用
    //...
    grow_bytes = std::max(grow_bytes, static_cast<uint64_t>(min_free_));
    LOG(INFO) << "yun_hen org1 target_size = " << target_size;
    target_size = bytes_allocated + static_cast<uint64_t>(grow_bytes * multiplier);
    LOG(INFO) << "yun_hen after1 target_size = " << target_size;
    //...
    if (bytes_allocated + adjusted_max_free < target_footprint) {
      LOG(INFO) << "yun_hen org2 target_size = " << target_size;
      target_size = bytes_allocated + adjusted_max_free;
      LOG(INFO) << "yun_hen after2 target_size = " << target_size;
      grow_bytes = max_free_;
    } else {
      LOG(INFO) << "yun_hen org3 target_size = " << target_size;
      target_size = std::max(bytes_allocated, target_footprint);
      LOG(INFO) << "yun_hen after3 target_size = " << target_size;
      // The same whether jank perceptible or not; just avoid the adjustment.
      grow_bytes = 0;
    }
    //...

=> 同时附上art虚拟机堆栈调试的方法,这个方法对于跟进流程也挺有帮助的

//yun_hen change start
          std::string owner_stack_dump;

          Thread* self = Thread::Current();

          if (self != nullptr) {
            // Reacquire mutator_lock_ for logging.
            ScopedObjectAccess soa(self);

            Locks::thread_list_lock_->ExclusiveLock(self);
            struct CollectStackTrace : public Closure {
              void Run(art::Thread* thread) OVERRIDE
                REQUIRES_SHARED(art::Locks::mutator_lock_) {
                thread->DumpJavaStack(oss);
              }

              std::ostringstream oss;
            };

            CollectStackTrace owner_trace;
            // RequestSynchronousCheckpoint releases the thread_list_lock_ as a part of its
            // execution.
            self->RequestSynchronousCheckpoint(&owner_trace);
            owner_stack_dump = owner_trace.oss.str();


            LOG(WARNING) << "yun_hen change Current owner stack:\n" << owner_stack_dump
                << "......";
          }

//yun_hen change end

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值