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

相关推荐
Kapaseker5 小时前
Kotlin Toolchain 0.11 发布:主要是把 Amper 干没了
android·kotlin
三少爷的鞋6 小时前
Android 现代架构不需要事件总线进阶篇
android
杉氧20 小时前
深入理解 Compose 重组机制:快照系统如何驱动 UI 精准刷新?
android·架构·android jetpack
召钱熏20 小时前
状态枚举正确≠渲染正确:一个语音按钮的状态机边界修复实录
android·前端
杉氧21 小时前
深度解析:Jetpack Compose 核心架构与底层原理 —— 十年安卓老兵的“破茧重生”
android·架构·android jetpack
通玄21 小时前
Jetpack Compose 入门系列(七):ViewModel 与界面状态管理
android
落魄Android在线炒饭21 小时前
Android Framework 开发技巧:android.jar 生成与系统快速编译验证
android
如此风景1 天前
Kotlin Flow操作符学习
android·kotlin
plainGeekDev1 天前
GreenDAO → Room
android·java·kotlin
weiggle1 天前
第八篇:ViewModel + Compose——生产级状态管理实践
android