2、GC机制基础:Java/Kotlin垃圾回收机制(GC)的工作原理,以及GC如何引发内存抖动

好,咱们直接切入正题。内存抖动这玩意儿,说白了就是GC在频繁搞事情。但你要想治它,得先搞懂GC到底是怎么工作的。我见过太多开发同学,一遇到内存抖动就盲目加缓存、改数据结构,结果越改越糟。嗯,咱们先把地基打牢。

2.1 垃圾回收的核心逻辑:谁在干活?

Java/Kotlin的GC,本质上就是一个“自动管家”。它帮你盯着堆内存,发现哪些对象没人用了,就悄悄回收掉。我个人习惯把GC的工作拆成三步:

  1. 标记:找出哪些对象是“活的”,哪些是“死的”。
  2. 清除/整理:把死对象的内存腾出来,或者把活对象挪到一起。
  3. 压缩:避免内存碎片化,让后续分配更顺畅。

你想想看,如果这个管家每隔几秒才来打扫一次,那没问题。但如果它每秒钟要打扫十几次,那你的App就会频繁“卡顿”——这就是内存抖动的本质。

核心观点:GC本身不是问题,问题在于GC触发的频率太高。

2.2 分代回收:年轻人和老年人的区别

JVM把堆内存分成了几个区域,最核心的就是新生代老年代。为什么这么分?因为大部分对象都是“朝生暮死”的。

区域 特点 GC类型
新生代(Young Gen) 对象刚创建,存活时间短 Minor GC(频繁但快)
老年代(Old Gen) 对象熬过了多次GC,存活时间长 Major GC / Full GC(慢,卡顿明显)

我在项目中遇到过最典型的场景:某个页面疯狂创建临时对象,比如在onDraw里new Paint(),或者在循环里拼接字符串。这些对象瞬间填满新生代,触发Minor GC。如果Minor GC回收不掉,它们就会晋升到老年代,最终引发Full GC——那一下卡顿,用户能明显感觉到。

我的经验:Android上最怕的就是“频繁的Minor GC + 偶尔的Full GC”。前者让帧率不稳,后者直接掉帧。

2.3 GC如何引发内存抖动?

好,现在咱们把GC和内存抖动串起来。内存抖动的典型路径是这样的:

  1. 你的代码在短时间内大量创建对象(比如循环里new对象)。
  2. 新生代迅速被填满,触发Minor GC。
  3. GC执行时,所有线程都要暂停(Stop-The-World,简称STW)。
  4. GC结束后,对象被回收,但紧接着下一轮循环又开始创建新对象。
  5. 如此反复,GC被高频触发,App频繁卡顿。

说白了,就是“创建对象 → 触发GC → 暂停 → 继续创建 → 再触发GC”的恶性循环。我曾经调试过一个视频播放器,滑动列表时帧率从60fps掉到20fps,用Profiler一看,GC每秒触发了30多次。嗯,罪魁祸首是列表的ViewHolder里每次bind数据都new了一个Bitmap。

避坑指南:我曾经以为只有大对象才会引发GC问题,后来发现大量小对象同样致命。比如在for循环里用String拼接,每次循环都产生一个StringBuilder和String对象,累积起来GC压力非常大。

2.4 不同Android版本GC的差异

这里有个细节要注意:不同Android版本的GC实现不一样。早期(4.x及以下)用的是Dalvik虚拟机,GC是单线程的,STW时间很长。后来ART(5.0+)引入了并发GC,STW时间大幅缩短。但ART也不是万能的——它只是让单次GC更快,如果GC频率太高,累积的暂停时间依然可观。

版本 GC特点 对抖动的影响
Android 4.x (Dalvik) 单线程,STW时间长 抖动明显,卡顿严重
Android 5.0+ (ART) 并发GC,STW时间短 抖动依然存在,但单次卡顿减轻
Android 8.0+ (ART优化) 并发标记,更精细的暂停 高频GC仍会导致帧率波动

你想想看,就算ART把单次GC从100ms优化到10ms,如果每秒触发30次,那每秒还是有300ms的暂停。所以,减少GC触发次数才是治本之道。

2.5 如何判断是否发生了内存抖动?

我一般用Android Studio的Memory Profiler和CPU Profiler配合来看。具体操作不展开,但有几个关键指标:

  • Allocation Tracker:看单位时间内对象分配的数量和大小。如果曲线像心电图一样剧烈波动,基本就是抖动了。
  • GC事件频率:Profiler里会显示GC的触发时间。如果1秒内出现多次GC,那肯定有问题。
  • 帧率曲线:GC导致的卡顿会直接反映在帧率上,掉帧的地方往往对应着GC事件。

一个小技巧:我习惯在代码里加一些日志,打印GC的触发时间和频率。虽然有点暴力,但在线上环境排查问题时特别有用。

2.6 总结:GC与内存抖动的因果关系

最后咱们捋一下:GC是Android内存管理的基石,它本身不是坏事。但当你代码中频繁创建临时对象时,GC就会被高频触发,导致App卡顿——这就是内存抖动。说白了,内存抖动的根源是“不合理的对象分配模式”,而GC只是背锅侠

下一章我会讲如何用工具定位这些“不合理的分配”,以及具体的修复方案。嗯,先消化一下这部分内容,有问题随时问。