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的开销和内存占用都处在一个可控的范围内

相关推荐
千里马学框架5 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台5 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone5 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc5 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo5 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077005 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼5 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone5 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen5 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone5 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui