android 根据gc优化,Android性能优化之如何写出GC友好的应用

作者:乐蛙科技高级研发经理 易宁

Android应用普遍没有iOS应用流畅,这与Android应用是运行在Java虚拟机上有很大的关系,而关系最大的恐怕就是Java虚拟机上的Garbage Collection(GC)了。Java语言提供了自动内存管理,垃圾对象会在GC过程中自动被释放。这提供了开发效率,使开发者能集中精力在业务逻辑上;但天下没有免费的午餐,你写的Android应用的某些性能上的问题很可能就是GC导致的。

下面是一段非常简单的Java语句:

1byte[] data = new byte[512 * 512 * 4];

这条语句申请了一块大小相等于512*512大小图片的内存。执行这条语句会消耗多少时间呢?0+ms吗?基本没有性能影响吗?

实际的情况是:0~200ms,甚至更多!这取决于应用当前Heap使用情况。在一个类似Hello World的测试程序里,执行这条语句有时候只需要0+ms就完成了,但一般情况是16+ms,甚至有时候需要30+ms。我们知道一段流畅的动画每帧绘图消耗的时间应该小于16ms,因为这样才能保证动画有60FPS的帧率。但如果执行上面那句简单的语句就要错过1~2帧,那就难怪动画不流畅了。

不过,有人可能要说了:“没人会傻到在每帧绘图的时候去申请一块这么大的内存,实际情况不会有这个问题”。实际的工程中的确不会这么写,但造成执行这条语句耗时比较长的原因不是因为申请的内存太大,而是申请过程中产生了GC,就算是一个Byte的内存申请也可能消耗比这更长的时间。

Dalvik中GC的原理

在Dalvik虚拟机中定义了四种触发GC的条件(参看Heap.h):

GC_CONCURRENT,当Heap的使用率达到某一阈值时自动触发。

GC_FOR_MALLOC,当Heap没有足够的空间用于容下新创建的对象时。

GC_EXPLICIT,用户主动调用GC时。

GC_BEFORE_OOM,当要发生OOM时系统尝试进行最后GC的努力。

GC_CONCURRENT和GC_EXPLICIT是并行的,GC过程中应用不会被暂停,只在GC开始和结束时会暂停应用,每次暂停的时间比较短,一般只有3~4ms。GC_FOR_MALLOC是非并行的,GC过程中应用被暂停,耗时比较长,可能几十毫秒也可能几百毫秒。在应用向Dalvik申请内存时,Dalvik先检查当前Heap中有无足够的空余空间用来安排对象,当发现没有足够的空间的时候会先进行GC_FOR_MALLOC以试图释放垃圾对象来获取新的空间,如果发现空间还不够则进行Heap的增长。在每次成功分配完新的空间后,Dalvik会检查当前Heap的使用情况,如果使用空间超过一定的阈值的时候,GC_CONCURRENT就会触发。

每次GC的时候会打印如下的Log信息:

1D/dalvikvm(10497): GC_CONCURRENT freed 2940K, 54% free 2885K/6204K, paused 1ms+4ms, total 38ms

上面的Log信息表明:这次GC_CONCURRENT释放了2940K的空间,当前Heap使用率为54%,总的Heap大小是6204K,空余2885K,GC开始时暂停了1ms,GC结束时暂停了4ms,GC总共花了38ms。

Dalvik虚拟机通过下面几个个属性值来控制Heap的空间配置:

targetUtilization,理想的Heap利用率,每当Heap增长时Dalvik会使增长后的Heap维持在这个利用率附近。

minFree,空余空间最小值。

maxFree,空余空间最大值。

startSize,虚拟机启动时初始的Heap大小。

growthLimit,用户设置的允许的最大Heap大小。

maximumSize, Heap空间最大极限值。

这些属性值都可以通过system/build.prop来配置,zygote启动dalvik虚拟机时会从该文件中读取这些参数。通常来说,Heap空余空间越大应用越流畅,消耗的内存也更多。

下图为一次“顺利”的4M内存申请:

b6007f3ef176f3cb7ac3eaf411cdff01.png

可以看到在申请之前,空余的空间达到8M,申请4M的内存很顺利没有任何GC发生。

下图为一次“不顺利”的8M内存申请:

ae39cca920d5e8970280c917694a9bdb.png

虽然空余的空间已经有8M,但是为了保证正常的Heap利用率,Heap空间还是增长了,并且增长Heap之前进行了一次GC_FOR_MALLOC。

下图为继续申请8M的内存:

6c21c9665f302b5d5b1175901dac454d.png

相应的输出Log:

1D/dalvikvm﹕ GC_FOR_ALLOC freed 8195K, 34% free 16726K/24972K, paused 23ms, total 23ms

上图和Log更加清晰的表明Heap空间不够时会先进行一次GC,GC的类型就是GC_FOR_MALLOC。

Android流畅的关键:GC_FOR_MALLOC

GC_CONCURRENT和GC_FOR_MALLOC虽然都是系统自动调用的,都暂停了应用,但它们花费的时间不在一个数量级。通过上面的分析我们知道每次申请内存空间不够时就会产生GC_FOR_MALLOC,我们不可能不申请内存,所以也不可能完全避免GC_FOR_MALLOC,但还是有些策略能降低GC_FOR_MALLOC的影响:

避免不必要的GC_FOR_MALLOC

Heap空间都有其理想的利用率,在理想的利用空间内,申请内存是不会发生GC_FOR_MALLOC。

应用应该避免内存开销的波动,将内存的波动维持在maxFree和mixFree之间。

避免大的内存需求。比如,不要轻易去用WallpaperManager,因为壁纸占用巨大的内存。

应该尽量复用对象。比如,使用BitmapFactory加载图片时使用BitmapFactory.Option.inBitmap复用Bitmap,避免申请内存。

对于已知有垃圾对象的情况下,先进行手动的System.gc()来释放空间,而不是等到系统自动调用GC_FOR_MALLOC。因为GC_EXPLICIT是异步的,暂停应用的时间远小于GC_FOR_MALLOC。

不要在循环中申请创建对象。循环中申请的对象会不断累积,直到空间不够发生GC_FOR_MALLOC。

减少GC_FOR_MALLOC的影响

不要在Android应用运行的关键阶段申请内存。比如,不要在onDraw, onLayout, onXXX中创建对象。在关键阶段创建对象可能使应用出现随机性卡顿。

尽量在onCreate和onStart阶段创建对象,同时在onStop和onDestroy阶段释放对象。

尽量复用ListView中Item,在ListView滑动时不要创建对象。

降低每次GC_FOR_MALLOC的时间消耗

避免大量的,细小的对象。这些对象会增加Heap空间的复杂度,在GC时会严重影响GC的耗时。

对于不用的对象近早将其引用消除,减少Heap空间占用和复杂度。

尽量用数据结构,数组来组织对象。

明确对象之间的关系,使对象之间的依赖关系简单明了。

减少View的数量。每个View包含大量属性,对于没有交互的View,大多数的属性都是没有用的,可以用Drawable替代。

需要注意的是:本文讨论的只限于Dalvik虚拟机。对于Art虚拟机,官方文档已经表明Art虚拟机有极大的改进,其中就特别提到不用主动调用System.gc()来避免产生GC_FOR_MALLOC。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值