2、GC机制基础:Java/Kotlin垃圾回收机制(GC)的工作原理,以及GC如何引发内存抖动
好,咱们直接切入正题。内存抖动这玩意儿,说白了就是GC在频繁搞事情。但你要想治它,得先搞懂GC到底是怎么工作的。我见过太多开发同学,一遇到内存抖动就盲目加缓存、改数据结构,结果越改越糟。嗯,咱们先把地基打牢。
2.1 垃圾回收的核心逻辑:谁在干活?
Java/Kotlin的GC,本质上就是一个“自动管家”。它帮你盯着堆内存,发现哪些对象没人用了,就悄悄回收掉。我个人习惯把GC的工作拆成三步:
- 标记:找出哪些对象是“活的”,哪些是“死的”。
- 清除/整理:把死对象的内存腾出来,或者把活对象挪到一起。
- 压缩:避免内存碎片化,让后续分配更顺畅。
你想想看,如果这个管家每隔几秒才来打扫一次,那没问题。但如果它每秒钟要打扫十几次,那你的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和内存抖动串起来。内存抖动的典型路径是这样的:
- 你的代码在短时间内大量创建对象(比如循环里new对象)。
- 新生代迅速被填满,触发Minor GC。
- GC执行时,所有线程都要暂停(Stop-The-World,简称STW)。
- GC结束后,对象被回收,但紧接着下一轮循环又开始创建新对象。
- 如此反复,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只是背锅侠。
下一章我会讲如何用工具定位这些“不合理的分配”,以及具体的修复方案。嗯,先消化一下这部分内容,有问题随时问。