ART | GC Configuration

之前学习ART GC触发流程时总是一头雾水,根据源码流程可以大致清楚每一次GC完成后会根据当前已经申请的内存大小和以下参数来计算得到一个新的GC水线

  1. dalvik.vm.heaptargetutilization
  2. dalvik.vm.heapmaxfree
  3. dalvik.vm.heapminfree

但是说实话,对于这些参数有什么具体的含义,为什么需要这么复杂的机制总是摸不着头脑。 例如为什么每次GC后需要GrowForUtilization来重新计算水线?这些参数的意义是什么?

偶然间发现了一篇位于art/runtime/目录,名为gc_configuration.md的文档解答了我很多疑惑 art/runtime/gc_configuration.md

heaptargetutilization

GC的次数越少那么应用占用的内存就会越多;而GC的次数越频繁,GC的开销就会越大 。因此GC开销和内存占用之间是不可以兼得的

由于堆的大小不同,衡量GC开销通常用回收每字节垃圾对象所需要的开销来衡量:

假设当前存活的对象大小为 L L L,扫描每个字节所需要的开销为 c c c,GC的总开销可以近似表示为扫描所有存活对象的带来的开销 c L cL cL。将heaptargetutilization简写为 u u u,当Heap增长到 L / u L/u L/u时触发GC可以回收大小为 L / u − L L/u - L L/u−L的垃圾,那么回收每字节垃圾对象所需要的开销等于 c L L / u − L = u c 1 − u \frac{cL}{L/u - L} = \frac{uc}{1-u} L/u−LcL=1−uuc,这是一个和当前存活对象大小无关的结果,并且随着 u u u的增加而增加。

也就是说:

  1. heaptargetutilization越大说明回收每字节垃圾对象所需要的开销越大
  2. heaptargetutilization越大占用的最大堆大小越小( L u \frac{L}{u} uL)

当前的设备上通常将heaptargetutilization设置为0.5

当然这个值的设置需要有一个上界:

当前Concurrent Copying GC中存活对象比例超过75%的region是不需要进行回收的(kEvacuateLivePercentThreshold );Concurrent Mark-Compact GC中是存活对象比例超过95%的region是不需要进行回收的(kBlackDenseRegionThreshold)

也就是说不管是CC算法还是CMC算法,都会产生内存碎片。因此设置的heaptargetutilization应该严格小于对应的Threshold,否则可能会造成每一次回收的垃圾对象很少导致非常频繁的回收

heap max/min free

只依赖 L u \frac{L}{u} uL 的值来触发GC也会存在一些问题:

  • 在 L L L比较小的场景下, L u \frac{L}{u} uL 这个水线会非常容易到达,导致频繁的触发GC
  • 在 L L L比较大的场景下, L u \frac{L}{u} uL 这个水线会变的非常高,导致堆内存的占用比较大

GC还会有一些固定的开销,例如扫描GC roots,通过STW来扫描references等。在正常情况下GC的开销会和 L L L近似的成线性关系,但是在 L L L非常小的场景下,每次GC的固定开销可能就不能忽视了。假设当前的 L = 0 L=0 L=0,那么每次分配一次对象就会触发一次GC,这显然是不太合理的,因此需要heapminfree来保证触发GC的最低水线。最理想的场景下heapminfree也是应该需要根据固定的开销来进行一个动态的更新,但是在ART中当前的heapminfree还是一个经验值。

在应用的堆大小已经非常大的场景下,如果当前的业务需要尽可能的限制其堆空间的进一步增长,可以通过设置heapmaxfree来保证触发GC的最高水线。这个值在设备的物理内存快速增长的场景下可能不太具备有现实意义了,Google的ART团队建议将这个值设置为maximum heap size,在将来可能会移除这个参数。

总结

现在可以回答开头的一些问题:

这些GC的参数都是在GC开销和内存占用中寻找一个平衡点

  1. heaptargetutilization作为一个基准值来确定GC开销和内存占用
  2. heapminfree用来在HeapSize较小的场景下抑制GC,通过增加内存占用减少GC开销
  3. heapmaxfree用来在HeapSize较大的场景下促进GC,通过增加GC开销来减少内存占用

每次GC完成后,可以认为当前已经分配的对象都是存活的对象,根据上面三个参数动态的计算出下一次的GC水线,保证GC的开销和内存占用都处在一个可控的范围内

相关推荐
JMchen15 分钟前
【Jetpack 内核实战 01】开篇:Jetpack 组件全家桶核心逻辑与专栏完整规划
android·kotlin·android jetpack
骑着蜗牛撵大象3272 小时前
Linux 进程间通信之管道:从 pipe() 的内核语义到 FIFO 落地排错
android·java·linux·系统编程·fifo·管道·进程间通信
hunterandroid3 小时前
Retrofit 网络请求的异常处理与重试策略实战
android
sejinan3 小时前
极致轻量与秒级起播:NativeWasmTv 开源硬核技术解析与升级版体验
android·开源·webview·tv
小羊没烦恼!3 小时前
Ajax与JSON的一些总结
android·java·前端·数据库·mysql·算法·okhttp
mmsx3 小时前
Android 静默失败实录:五个没有日志的线上事故
android·kotlin
hai_android4 小时前
从零实现一个支持跨进程(IPC)的自定义 Toast
android·kotlin·android jetpack
龙腾-虎跃4 小时前
基于 SIP 协议的自研电话终端:FreeSWITCH 语音与视频通信实战
android·音视频·freeswitch·sip协议终端·wertc
imDwAaY5 小时前
操作系统如何管理程序运行?从线程、进程到内存隔离与用户态切换:CS162 Note2
android·java·redis
andyweike5 小时前
Android文件系统
android