Android 内存管理基础:Java 堆、GC 机制、内存分配与回收原理
说到 Android 的内存管理,Java 堆是你绕不开的一个概念。它是虚拟机管理的最大的那块内存区域。说白了,你 new 出来的每一个对象,最后都在堆里安了家。
我刚开始做 Android 开发时,对堆的理解比较粗浅。直到有一次在数据保护模块里遇到了莫名其妙的 ANR,才逼着我从头把这块啃了一遍。今天我就把其中的关键点跟你聊聊。
1. Java 堆——对象的大本营
Java 堆是运行时数据区,专门存放对象实例。它被分成了三块:年轻代、老年代 和 元空间。
- 年轻代:又细分为 Eden 区和两个 Survivor 区(S0、S1)。新对象优先在 Eden 区分配。
- 老年代:存放经过多次 GC 仍然存活的对象。
- 元空间:存放类的元数据。注意,它不在堆内,而是使用本地内存。
我刚开始接触这部分时,总觉得搞两个 Survivor 区有点多余。后来踩过坑才明白,这是为了减少内存碎片。你想想看,如果没有 Survivor 区的周转,每次 GC 都得做大量的复制和整理,效率低不说,碎片问题也会越来越严重。
对象分配的基本流程是这样的:
- 新对象优先在 Eden 区分配
- Eden 区满了之后触发 Minor GC
- 存活的对象被移到 S0 或 S1 区
- 每经历一次 GC 存活,对象年龄 +1
- 年龄达到阈值(默认 15),晋升到老年代
个人习惯:我在分析堆内存问题时,会先用 adb shell dumpsys meminfo 看一下各区域的使用情况。特别是老年代的占用率,如果持续增长,基本就能判断出存在对象泄漏。
2. GC 机制——自动回收的艺术
GC(垃圾回收)是 Java 的看家本领。但自动化不代表你不需要了解它。恰恰相反,很多 ANR 问题就是 GC 搞出来的。
GC 主要分两种:
| 类型 | 发生区域 | 频率 | 速度 | 影响 |
|---|---|---|---|---|
| Minor GC | 年轻代 | 高 | 快 | 基本无感 |
| Major / Full GC | 老年代 + 年轻代 | 低 | 慢 | 可能导致卡顿 |
GC Roots 是什么?它是判断对象是否存活的起点。比如线程栈上的局部变量、静态变量、JNI 引用等。GC 从这些根节点出发,遍历所有可达的对象。不可达的,就会被回收。
我曾经在开发数据保护模块时,遇到过一个诡异的 ANR。明明代码逻辑没问题,但线上就是频繁报卡顿。后来抓了几份 hprof 堆转储文件一查,发现有一个静态集合在不断膨胀,里面存了大量已经用不到的回调对象。GC 没法回收它们,老年代越来越满,Full GC 越来越频繁,主线程就被频繁的 STW(Stop The World)给卡住了。
嗯,这里要注意:对象泄漏不一定会直接报 OOM,但它会让 GC 变得异常活跃,最终导致 ANR。 这是我们在做数据保护场景调优时,最容易忽略的一点。
避坑指南:我曾经以为只要内存没爆就不用管 GC。后来发现,GC 线程占用的 CPU 时间一旦超过 10%,主线程的响应时间就会明显恶化。建议你在 Trace 文件里多留意 GC_CONCURRENT 和 GC_FOR_ALLOC 的出现频率。
3. 内存分配与回收原理
这块是实操中必须掌握的内容。我把它分为分配策略和回收算法两部分来讲。
分配策略:
- 指针碰撞:堆内存规整时使用。简单说就是维护一个指针,分配时直接移动指针。效率高。
- 空闲列表:堆内存不规整时使用。需要维护一个空闲块列表,分配时查找合适的块。速度慢一些。
你想想看,如果频繁发生内存碎片,指针碰撞就没法用了,只能退化成空闲列表。所以减少碎片,不仅是内存利用率的问题,也直接影响分配性能。
回收算法:
| 算法 | 原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 标记清除 | 先标记存活对象,再清除未标记的 | 实现简单,不移动对象 | 产生大量内存碎片 | 老年代(CMS) |
| 标记整理 | 标记存活对象后,向一端移动 | 消除碎片,内存规整 | 移动对象有额外开销 | 老年代(Parallel Old) |
| 复制算法 | 将内存分为两块,只用一块,GC 时把存活对象复制到另一块 | 无碎片,效率高 | 浪费一半空间 | 年轻代 |
分代回收的思想,说白了就是:不同代,用不同的算法。 年轻代存活对象少,适合用复制算法;老年代存活对象多,适合用标记清除或标记整理。
我记得有一次项目里,老年代用了 CMS 回收器,结果碎片化严重,最后被迫切换到标记整理才解决。所以选算法这件事,真不能只看理论,得结合实际场景去调。
核心要点:理解 Java 堆的分代结构、GC 的触发条件、以及各种回收算法的优缺点,是分析对象泄漏和 ANR 的基础。没有这个基础,后面所有的调优方法都是空中楼阁。
好了,以上就是 Android 内存管理的基础内容。下面这张图把三个部分的关系串了起来,你可以对照着回顾一下。