第1章 GC概述与背景

做Android性能优化这些年,我遇到过很多类似的问题:界面突然掉帧、滑动卡顿、动画不连贯。用Systrace或Profiler一抓,十有八九都能看到GC的踪影。说白了,GC(垃圾回收)是Java虚拟机自动管理内存的机制,但它的副作用——停顿——会直接影响UI的渲染节奏。这一章我们先打好基础,搞清楚GC到底是什么,它又是怎么拖慢帧率的。

1.1 什么是垃圾回收

你想想看,Java里new一个对象,我们只管用,用完了不用管。JVM会定期扫描堆内存,找出那些“没人引用”的对象,然后回收它们占用的空间。这个过程就叫垃圾回收(GC)。

听起来挺智能,对吧?但问题在于,GC在“找垃圾”和“清垃圾”的时候,会暂停应用线程。这个暂停时间可能只有几毫秒,也可能几十甚至上百毫秒。对于UI线程来说,每一帧只有16.6ms(60fps)可以干活,一旦GC暂停超过几毫秒,那帧就画不完,掉帧就来了。

我在项目中遇到过这样一个案例:某社交App的消息列表滑动卡顿,用Android Studio的Memory Profiler一看,每2~3秒就触发一次GC,每次停顿大约10~15ms。用户感觉就是“一卡一卡”的。嗯,这其实就是典型的频繁GC导致的帧率抖动。

1.2 为什么GC会导致帧率下降

原因其实不复杂,就三点:

  • STW(Stop The World)暂停:GC进行时,大多数垃圾回收器都需要暂停所有应用线程,包括UI线程。暂停期间,无法渲染任何东西。
  • 频繁触发:如果内存分配速率很高,或者堆内存不足,GC会频繁启动。每次暂停虽然短,但累积起来就占用了大量时间。
  • 并发GC的副作用:像ART的Concurrent Mark Sweep虽然大部分阶段是并发的,但最后的重标记或清理阶段仍然需要短暂停顿。而且并发阶段会消耗CPU资源,也可能导致渲染线程抢不到时间片。

关键数据:60fps下每帧可用时间16.6ms。如果GC暂停占用了10ms,那么留给渲染的时间只剩6.6ms。一旦布局、绘图、动画等任务再消耗一些,超时几乎是必然的。

1.3 Android中的GC实现

Android从4.4开始,ART逐渐取代Dalvik。两者在GC策略上有明显区别,我整理了一张简表:

特性 Dalvik ART(Android 5+)
主要回收器 Mark-Sweep(标记-清除) 并发标记清除(CMS)+ 部分压缩
暂停特点 暂停时间较长,但单次GC开销稳定 暂停时间更短,但可能多次小停顿
内存碎片 易产生碎片,需要手动触发压缩 有压缩机制,碎片较少
并发能力 大部分阶段都需要STW 标记阶段并发,只有少量STW

说实话,ART的引入确实改善了很多,但并不意味着就不会有GC卡顿。我自己做过对比测试:同样的App在Android 4.4上滑动比Android 6.0卡,但Android 6.0上偶尔也会出现“掉帧脉冲”。根本原因还是——内存分配太猛了。

1.4 GC的类型与暂停时间

根据触发条件和回收范围,GC主要分这么几种:

  • Minor GC / Young GC:发生在新生代(大多数对象朝生夕死),很快,通常1~3ms。
  • Major GC / Old GC:回收老年代对象,稍慢,5~15ms。
  • Full GC:回收整个堆,还可能包括方法区。这个最慢,可以到几十甚至上百毫秒。
  • 并发GC(如CMS / G1的并发阶段):不完全STW,但会有“并发模式失败”导致退化为Serial GC的风险。

举个实际例子。我在分析某视频App的卡顿时,发现它的Full GC平均暂停时间达到了45ms。这意味着在动画的中间过程,整整跳过了2~3帧。用户看到的画面就是“瞬移”。

避坑指南:我曾经调试过一个直播App,频繁触发GC的原因是Bitmap对象分配过多且不复用。日志里看到连续十几次Minor GC之后,一次Full GC直接把帧率从60拉到15。后来我们改用inBitmap复用Bitmap,GC频率下降了80%。

1.5 频繁GC的典型场景

根据我的经验,以下场景最容易引发频繁GC,从而导致帧率下跌:

  1. 大量短生命周期对象:比如在循环或高频回调中new对象(字符串拼接、临时集合)。
  2. Bitmap过量分配:图片不缩放、不复用、不及时recycle。
  3. 内存泄漏:Activity无法释放导致老年代不断膨胀,最终触发Full GC。
  4. 频繁创建Handler消息:每个Message都携带大对象。
  5. 自定义View中频繁创建Drawable或Paint。

注意:并非所有GC都会导致掉帧。低频率的Minor GC(每5秒一次,暂停仅1~2ms)影响很小。真正要命的是“高频率 + 长暂停”的组合。我们后续章节会教你如何用工具区分这两种情况。

好了,GC的基础概念和背景说清楚了。接下来让我们用一张图把这一章的核心知识点串起来:

GC与帧率关系知识体系 垃圾回收(GC) 内存频繁分配 内存泄漏 大对象(Bitmap等) Minor GC(暂停短) Major GC(暂停中) Full GC(暂停长) STW暂停 CPU资源消耗 帧率下降 / 卡顿 优化方向: 减少对象分配 复用资源 触发原因组 GC类型组 GC对渲染的影响组