Android 内存管理基础:Java 堆、GC 机制、内存分配与回收原理

说到 Android 的内存管理,Java 堆是你绕不开的一个概念。它是虚拟机管理的最大的那块内存区域。说白了,你 new 出来的每一个对象,最后都在堆里安了家。

我刚开始做 Android 开发时,对堆的理解比较粗浅。直到有一次在数据保护模块里遇到了莫名其妙的 ANR,才逼着我从头把这块啃了一遍。今天我就把其中的关键点跟你聊聊。

1. Java 堆——对象的大本营

Java 堆是运行时数据区,专门存放对象实例。它被分成了三块:年轻代、老年代 和 元空间。

  • 年轻代:又细分为 Eden 区和两个 Survivor 区(S0、S1)。新对象优先在 Eden 区分配。
  • 老年代:存放经过多次 GC 仍然存活的对象。
  • 元空间:存放类的元数据。注意,它不在堆内,而是使用本地内存。

我刚开始接触这部分时,总觉得搞两个 Survivor 区有点多余。后来踩过坑才明白,这是为了减少内存碎片。你想想看,如果没有 Survivor 区的周转,每次 GC 都得做大量的复制和整理,效率低不说,碎片问题也会越来越严重。

对象分配的基本流程是这样的:

  1. 新对象优先在 Eden 区分配
  2. Eden 区满了之后触发 Minor GC
  3. 存活的对象被移到 S0 或 S1 区
  4. 每经历一次 GC 存活,对象年龄 +1
  5. 年龄达到阈值(默认 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 内存管理的基础内容。下面这张图把三个部分的关系串了起来,你可以对照着回顾一下。

Android 内存管理基础框架 Java 堆结构 年轻代(Eden · S0 · S1) | 老年代 | 元空间 内存分配策略 指针碰撞 | 空闲列表 GC 回收机制 Minor GC | Full GC 内存回收算法 标记清除 | 标记整理 | 复制算法 GC Roots ⚠️ 对象泄漏的影响 对象泄漏 → GC 无法回收 → 堆内存持续增长 → 频繁 Full GC → 最终引发 ANR 三大模块的关系:Java 堆是基础 → 分配策略决定对象如何存放 → GC 机制决定何时回收 → 回收算法决定如何回收