ART GC &APP memory

2022-05-13 19:27:11 浏览数 (3)

Android ART 虚拟机分配及GC

[dalvik.vm.heapgrowthlimit]: [192m]

[dalvik.vm.heapmaxfree]: [8m]

[dalvik.vm.heapminfree]: [512k]

[dalvik.vm.heapsize]: [512m]

[dalvik.vm.heapstartsize]: [8m]

[dalvik.vm.heaptargetutilization]: [0.75]

  • dalvik.vm.heapgrowthlimit和dalvik.vm.heapsize都是java虚拟机的最大内存限制,一般heapgrowthlimit< heapsize,如果在Manifest中的application标签中声明android:largeHeap=“true”,APP直到heapsize才OOM,否则达到heapgrowthlimit就OOM
  • dalvik.vm.heapstartsize Java堆的起始大小,指定了Davlik虚拟机在启动的时候向系统申请的物理内存的大小,后面再根据需要逐渐向系统申请更多的物理内存,直到达到MAX
  • dalvik.vm.heapminfree 堆最小空闲值,GC后
  • dalvik.vm.heapmaxfree堆最大空闲值
  • dalvik.vm.heaptargetutilization 堆目标利用率

后面三个值用来确保每次GC之后Java堆已经使用和空闲的内存有一个合适的比例,这样可以尽量地减少GC的次数,堆的利用率为U,最小空闲值为MinFree字节,最大空闲值为MaxFree字节,假设在某一次GC之后,存活对象占用内存的大小为LiveSize。那么这时候堆的理想大小应该为(LiveSize / U)。但是(LiveSize / U)必须大于等于(LiveSize MinFree)并且小于等于(LiveSize MaxFree),否则,就要进行调整,调整的其实是软上限softLimit,

二、ART GC何时用到这三个值

首先从ART的GC流程中找出用到这三个值的地方。

ART分配对象失败或者已使用内存超过某个设定的阈值就会触发垃圾回收(GC),GC时调用的接口函数是CollectGarbageInternal,此方法的实现如下所示:

collector::GcType Heap::CollectGarbageInternal(…) {

collector->Run(gc_cause, clear_soft_references || runtime->IsZygote());//执行GC

代码语言:javascript复制
RequestTrim(self);//裁剪堆

reference_processor_.EnqueueClearedReferences(self);// 将被回收了的引用对象添加到各自关联的队列中

GrowForUtilization(collector);//调整堆的大小

}

代码语言:javascript复制
enum GcType { // Placeholder for when no GC has been performed.

 kGcTypeNone,
 // Sticky mark bits GC that attempts to only free objects allocated since the last GC.

 kGcTypeSticky,
 // Partial GC that marks the application heap but not the Zygote.

kGcTypePartial,
  // Full GC that marks and frees in both the application and Zygote heap.

 kGcTypeFull,
   // Number of different GC types.

 kGcTypeMax,
};

此方法首先会根据选定的垃圾回收算法执行一次垃圾回收,通常的设置是前台垃圾回收(Foreground GC)执行的是并发标记清除算法(CMS),后台垃圾回收(Background GC)执行的是Compacting GC,在GC执行完成之后,就进入了垃圾回收收尾阶段,此阶段主要完成如下三件事:

(1)RequestTrim()对堆进行裁剪

GC回收垃圾完成之后需要决定是否要对堆进行裁剪,也就是将空闲内存临时归还给操作系统。此操作会对堆进行一次裁剪操作,而裁剪一个堆空间时会先锁住这个堆然后才能执行扫描操作,这会造成一点卡顿。谷歌目前对于何时进行堆裁剪的设定如下:如果5秒内没有任何堆裁剪发生过,那么此次GC之后就得进行一次堆裁剪。这个简单的设定防止了堆裁剪的过于频繁。

(2)EnqueueClearedReferences()处理被回收了的引用对象

调用函数EnqueueClearedReferences将目标对象已经被回收了的引用对象添加到各自关联的队列中去,以便应用程序可以知道它们的目标对象已经被回收了。

(3)GrowForUtilization()调整堆的大小

调用函数GrowForUtilization根据预先设置的堆目标利率以及最小和最大空闲内存数调整预留空闲内存大小,同时也会重新设置下次的GC策略以及并发垃圾回收的触发阈值。

综上,堆利用率heaptargetutilization、堆最小空闲内存heapminfree和堆最大空闲内存heapmaxfree这三个值在GrowForUtilization()中使用,用来调整GC后堆的大小。

三、这三个属性值的作用

GC触发后,垃圾回收器回收了应用不再使用的垃圾对象,这样应用的空闲内存就可能很大或者由于回收垃圾不够多导致空闲内存还是很小。如果此空闲内存很大,Android系统出于提高内存利用率的考虑是不会把这么大一块内存都给应用程序的,它会根据应用预先设定的堆利用率(heaptargetutilization)、最大和最小空闲内存数(heapmaxfree、heapminfree)等参数来调整此空闲内存的大小;如果此空闲内存很小,那么势必此空闲内存将很快分配光,下次GC会来的很快,所以遇到这种情况,ART会扩大此空闲内存的大小。

堆利用率(heaptargetutilization)、最大空闲内存(heapmaxfree)和最小空闲内存(heapminfree)在代码里的变量名为:utilization,max_free_,min_free_。

我们知道 ART中有三种GC策略:

i)Sticky GC:分代GC,只回收上次GC后分配的对象

ii)Partial GC:局部GC,不回收zygote堆

iii)Full GC:全局GC

当前GC使用哪种GC策略ART有一套判断的标准,总的来说,Sticky GC可能性最大,Full GC可能性最小。

对于不同的GC策略,预留空闲内存有不同的计算方法。

(1)如果GC策略是局部垃圾回收(Partial GC)或者全局垃圾回收(Full GC),那么预留空闲内存的计算方法如下所示:

const float multiplier = HeapGrowthMultiplier();//获取堆扩展系数

intptr_t delta = bytes_allocated / GetTargetHeapUtilization() - bytes_allocated;//由堆利用率算出所需空闲内存

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));//考虑最小空闲内存后的最终堆大小

此计算方法首先会获取一个堆扩展系数,它是根据前后台GC来区分的,一般前台GC此系数是2,也就是把空闲内存扩大2倍,后台GC此系数是1。因为对于前台GC来说,此时应用程序是运行在前台的,如果堆扩展较小,那么空闲内存就会更快的用光,也就是说下次GC来的更快,而GC会给应用造成短时间的暂停,影响应用的性能,所以说为了使前台应用有更好的性能,ART运行时会给前台应用更多的堆空闲空间。

然后由堆利用率(utilization)算出理论上所需的空闲内存,堆利用率按照谷歌的推荐一般设为0.75。此时算出来的堆大小还不是最终结果,还需考虑另外两个限制值:最小空闲内存(min_free_)和最大空闲内存(max_free_)。也就是需要把预留空闲内存控制在两倍的最小空闲内存和两倍的最大空闲内存之间。这样获得的target_size才是堆的最终大小,也就是已分配对象的大小和预留空闲内存之和。对于Partial GC和Full GC,预留空闲内存的大小和已分配对象的大小的关系如下:

更直观点画个图:

(2) 如果GC策略是分代垃圾回收(Sticky GC),也就是只回收上次GC后分配的对象,那么预留空闲内存的大小按如下方式计算:

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_));

}

max_allowed_footprint_是堆增长的上限值,上述计算方法就是在堆空间允许的范围内,尽量使预留空闲内存为最大空闲内存(max_free_),而与已使用内存的多少无关,如下图:

综上,堆利用率heaptargetutilization、堆最小空闲内存heapminfree和堆最大空闲内存heapmaxfree这三个值是用来调整GC后堆的空闲内存大小的,并且对于不同的GC策略有不同的调整方法,如图所示,从而使GC后堆的空闲内存不至于太大或者太小,太大则浪费内存,太小则会导致GC触发次数增多。

四、通过属性值对ART GC调优

从图看出,

(a)当前使用Sticky GC则预留空闲内存为heapmaxfree;

(b)当前使用非Sticky GC则预留空闲内存在2*heapminfree到2*heapmaxfree之间变动,utilization决定转折点,即(ub/(1-u),2b)和(ua/(1-u),2a),一般utilization都默认设为0.75,所以当应用已使用内存超过3a即3*heapmaxfree后,预留空闲内存恒定为3*heapmaxfree

从上面的分析可以看出,属性值heapmaxfree、heapminfree、heaptargetutilization对ART GC的性能影响如下,

i)heapmaxfree:当应用使用内存超过3*heapmaxfree后,预留空闲内存恒为2*heapmaxfree,所以heapmaxfree越小,2*heapmaxfree大小的空闲内存就会越快用光,GC就会触发的越频繁。增大heapmaxfree可以使应用的GC频率降低,但会使应用的内存占用变大。

ii)heapminfree:当应用使用内存小于3*heapminfree时,预留空闲内存设为2*heapminfree,一般来说应用在启动时占用内存才会这么小。

iii)heaptargetutilization:影响不大,一般都默认设为0.75。

综上,三个属性值heapmaxfree、heapminfree、heaptargetutilization中,相比较而言,heapmaxfree对GC调优效果最好,能影响GC触发的频率,对GC执行时间、GC暂停时间、GC吞吐量等关键GC性能指标的影响很小。

对于内存2-3GB的手机,heapmaxfree一般设为8MB,如果GC触发比较频繁,则可以把heapmaxfree设的大一点。

优化app memory 也可以修改这几个值。

minfree 为512k

Pss Private Private SwapPss Heap Heap Heap

Total Dirty Clean Dirty Size Alloc Free

------ ------ ------ ------ ------ ------ ------

Native Heap 4332 4272 0 0 12288 8403 3884

Dalvik Heap 1140 1104 0 0 2905 1369 1536

Dalvik Other 548 548 0 0

Stack 92 92 0 0

Ashmem 2 0 0 0

Gfx dev 2560 1100 1460 0

Other dev 8 0 8 0

.so mmap 1678 152 152 0

.apk mmap 345 0 36 0

.ttf mmap 51 0 0 0

.dex mmap 4231 4 3104 0

.oat mmap 75 0 4 0

.art mmap 4086 3832 64 0

Other mmap 7 4 0 0

EGL mtrack 29600 29600 0 0

GL mtrack 6676 6676 0 0

Unknown 603 548 0 0

TOTAL 56034 47932 4828 0 15193 9772 5420

App Summary

Pss(KB)

------

Java Heap: 5000

Native Heap: 4272

Code: 3452

Stack: 92

Graphics: 38836

Private Other: 1108

System: 3274

TOTAL: 56034 TOTAL SWAP PSS: 0

Objects

Views: 19 ViewRootImpl: 1

AppContexts: 3 Activities: 1

Assets: 2 AssetManagers: 3

Local Binders: 8 Proxy Binders: 15

Parcel memory: 2 Parcel count: 10

Death Recipients: 0 OpenSSL Sockets: 0

WebViews: 0

SQL

MEMORY_USED: 0

PAGECACHE_OVERFLOW: 0 MALLOC_SIZE: 0

minfree 为6m

Pss Private Private SwapPss Heap Heap Heap

Total Dirty Clean Dirty Size Alloc Free

------ ------ ------ ------ ------ ------ ------

Native Heap 4320 4256 0 0 12288 8473 3814

Dalvik Heap 1507 1116 0 0 19801 1369 18432

Dalvik Other 648 632 0 0

Stack 92 92 0 0

Ashmem 2 0 0 0

Gfx dev 2560 1896 664 0

Other dev 8 0 8 0

.so mmap 1628 152 152 0

.apk mmap 300 0 36 0

.ttf mmap 51 0 0 0

.dex mmap 4154 4 3096 0

.oat mmap 73 0 4 0

.art mmap 4042 3824 68 0

Other mmap 8 4 0 0

EGL mtrack 29600 29600 0 0

GL mtrack 6676 6676 0 0

Unknown 621 568 0 0

TOTAL 56290 48820 4028 0 32089 9842 22246

App Summary

Pss(KB)

------

Java Heap: 5008

Native Heap: 4256

Code: 3444

Stack: 92

Graphics: 38836

Private Other: 1212

System: 3442

TOTAL: 56290 TOTAL SWAP PSS: 0

Objects

Views: 19 ViewRootImpl: 1

AppContexts: 3 Activities: 1

Assets: 2 AssetManagers: 3

Local Binders: 8 Proxy Binders: 15

Parcel memory: 3 Parcel count: 12

Death Recipients: 0 OpenSSL Sockets: 0

WebViews: 0

SQL

MEMORY_USED: 0

PAGECACHE_OVERFLOW: 0 MALLOC_SIZE: 0

0 人点赞