6、Dalvik GC优化:Concurrent GC,Partial GC,Young Generation概念,GC日志分析。

聊到Android的内存管理,GC(垃圾回收)是个绕不开的话题。很多开发同学一看到GC日志就头大,觉得那是系统工程师才该关心的事。其实不然,我个人的经验是,搞懂GC的基本原理,能帮你写出更流畅、更省内存的代码。

Dalvik虚拟机早期用的是Stop-The-World式的GC,说白了就是GC一启动,所有线程都得停下来等它干完活。这在单核时代还能忍,到了多核大屏时代,稍微卡顿一下用户都能感觉到。所以后来ART和Dalvik都引入了更先进的GC策略。

核心观点:GC优化的目标不是消灭GC,而是让GC的停顿时间短到用户感知不到。

6.1 Concurrent GC:边干活边打扫

Concurrent GC,顾名思义,就是并发执行。GC线程和你的应用线程可以同时运行。这听起来很美好,但实现起来有不少坑。

我记得在优化一个视频播放应用时,发现播放器在滑动进度条时偶尔会掉帧。抓了GC日志一看,全是Full GC在作祟。换成Concurrent GC后,掉帧问题明显缓解了。

Concurrent GC的核心思路是:

  • 标记阶段并发:GC线程标记存活对象时,应用线程可以继续分配对象。
  • 短暂停顿:只在开始和结束时有短暂的暂停,用于同步状态。
  • 写屏障:通过写屏障技术,记录并发期间对象引用的变化。

你想想看,如果GC期间应用完全不能动,那体验得多糟糕。Concurrent GC就是解决了这个问题。

小提示:在Android 4.4及以上版本,ART默认使用Concurrent GC。如果你的应用还在用Dalvik,可以考虑在AndroidManifest中声明android:largeHeap="true"来减少GC频率。

6.2 Partial GC:只扫一半,效率翻倍

Partial GC是Dalvik里一个很实用的优化。它不像Full GC那样扫描整个堆,而是只扫描一部分。说白了,就是偷懒但偷得聪明。

Partial GC主要分两种:

类型 扫描范围 适用场景
Partial GC 只扫描Young Generation 大部分临时对象都在这里
Full GC 扫描整个堆 内存压力大时触发

我曾经在分析一个图片加载库的内存问题时发现,它频繁触发Full GC。原因是它把大量Bitmap缓存到了Old Generation。后来改成使用LruCache配合软引用,Partial GC就能搞定大部分回收工作,性能提升很明显。

注意:Partial GC虽然快,但如果Old Generation里垃圾太多,最终还是逃不过Full GC。所以不要以为有了Partial GC就可以随意创建大对象。

6.3 Young Generation:新生代,短命鬼的聚集地

Young Generation这个概念是从Java虚拟机借鉴过来的。Dalvik和ART虽然没有严格意义上的分代,但内部实现上是有类似思想的。

Young Generation的特点:

  • 对象存活率低:大部分对象创建后很快就变成垃圾。
  • 回收频率高:因为对象死得快,所以需要频繁回收。
  • 回收成本低:只扫描一小块区域,速度很快。

为什么会这样?你想想看,一个Activity里创建的临时变量、循环中的中间对象,大多活不过几帧。把这些对象集中管理,GC效率自然就高了。

我习惯在写代码时注意以下几点:

  1. 避免在循环中创建大对象:比如在onDraw里new Paint,这会让Young Generation压力山大。
  2. 使用对象池:对于频繁创建和销毁的对象,比如Message、Rect,用对象池复用。
  3. 注意匿名内部类:它们会隐式持有外部类的引用,容易导致对象晋升到Old Generation。

避坑指南:我曾经在写一个列表滑动优化时,发现每次快速滑动都会触发大量GC。排查后发现是ViewHolder里创建了太多临时String对象。改用StringBuilder后,GC次数直接降了一半。

6.4 GC日志分析:读懂系统的悄悄话

GC日志是系统给你的反馈,读懂它就能知道内存的健康状况。在Android中,可以通过adb logcat查看GC日志。

一个典型的Dalvik GC日志长这样:

D/dalvikvm( 1234): GC_CONCURRENT freed 2048K, 35% free 3820K/5896K, paused 2ms+3ms, total 35ms

我们来拆解一下:

字段 含义 我的解读
GC_CONCURRENT GC类型 并发GC,说明系统在努力减少停顿
freed 2048K 回收了多少内存 这次回收了2MB,还不错
35% free 3820K/5896K 堆使用情况 堆总大小5.8MB,空闲35%,约3.8MB
paused 2ms+3ms 暂停时间 两次暂停,总共5ms,用户基本无感
total 35ms 总耗时 从开始到结束35ms

ART的日志稍微有点不同:

I/art( 5678): Background partial concurrent mark sweep GC freed 1047KB, 42% free 8MB/14MB, paused 1.636ms, total 27.342ms

这里多了个"Background",说明是后台GC。ART会把GC分为前台和后台,前台GC要求更短的暂停时间。

实用技巧:我一般关注两个指标:paused时间和free百分比。如果paused超过10ms,或者free低于20%,就该检查内存问题了。

6.5 知识体系总览

下面这张图总结了本章的核心概念和它们之间的关系:

Dalvik GC优化知识体系 GC优化目标 Concurrent GC Partial GC Young Generation 写屏障 + 并发标记 短暂停顿(2ms+3ms) 只扫描Young Generation 避免Full GC 对象存活率低 回收频率高、成本低 GC日志分析:读懂系统的反馈

这张图把三个核心概念串起来了。Concurrent GC解决的是停顿时间问题,Partial GC解决的是扫描范围问题,Young Generation解决的是对象管理问题。三者配合,才能让GC既快又准。

嗯,到这里你应该对Dalvik的GC优化有了一个整体的认识。记住,GC不是敌人,而是帮你管理内存的管家。理解它的工作方式,你就能写出更高效、更流畅的Android应用。


公众号:蓝海资料掘金营,微信deep3321