2. 内存泄漏基本概念与Android内存模型归纳总结
做 Android 开发几年,你要是没被内存泄漏坑过,那运气真不错。我身边不少同事,包括我自己,都曾在内存泄漏上栽过跟头——尤其是做相机相关功能的时候,一拍照内存就涨,拍着拍着 App 就挂了。
内存泄漏到底是怎么回事?Android 的内存模型又长什么样?这章咱们把这两个基础问题彻底聊透。
2.1 什么是内存泄漏
内存泄漏,说白了就是——对象已经「死」了,但 GC 却收不了它。
正常情况下,一个对象不再被任何引用指向,GC 就会把它回收。但如果某个本应被释放的对象,还被一个长期存活的对象(比如 static 变量)强引用着,GC 就认为它「还在用」,于是它就赖在堆里不走。
我刚开始做 Android 时,对这事理解得很浅。觉得不就是对象没释放嘛,至于那么大惊小怪?直到有一次我负责的 Camera 应用在连续拍照后直接闪退,dump 下来的 hprof 文件里发现了几百个 Surface 对象排着队等着被回收……嗯,从那以后我再也不敢小看内存泄漏了。
2.2 内存泄漏 vs 内存溢出
这两个概念经常被混为一谈,其实它们的关系更像是「因」与「果」。
| 概念 | 定义 | 关系 |
|---|---|---|
| 内存泄漏(Leak) | 对象无法被回收,占用内存持续增长 | 是「因」 |
| 内存溢出(OOM) | 可用内存不足,无法分配新对象 | 是「果」 |
你想想看,内存不会凭空消失。每次泄漏一点点,日积月累,堆里就塞满了「僵尸对象」。等到系统想分配一块新内存时,发现凑不够了——OOM 就来了。
2.3 Android 内存模型概览
Android 的内存模型,我习惯把它拆成两大部分来理解:Java 堆和原生堆。这两块的管理方式完全不同,搞混了就容易出问题。
- Java 堆(Java Heap)
由 ART / Dalvik 虚拟机管理。对象实例、数组都在这里分配。GC 负责自动回收。 - 原生堆(Native Heap)
通过 malloc / new 直接分配。Bitmap 的像素数据、OpenGL 纹理、Surface 的 Buffer 都存放在这里。不受 GC 管理,必须手动释放。
这里有个关键点:GC 只管 Java 堆,管不了 Native 堆。很多人在 CameraX 开发中遇到的泄漏,其实都出在 Native 堆上。
2.4 常见内存泄漏模式
在实际项目中,我总结了下面几种最常见的「泄漏套路」。你看看自己踩过几个?
- 静态引用持有 —— static 变量引用了 Activity 或 Context,导致整个界面无法释放。
- 内部类隐式引用 —— 匿名内部类或非静态内部类,悄悄持有外部类的引用。
- 资源未关闭 —— Cursor、File、InputStream、MediaCodec 等用完后没 close。
- Handler / 定时任务 —— 延迟的 Message 或 Runnable 持有外部引用,Activity 退出了还赖着不走。
- 注册未解绑 —— BroadcastReceiver、ServiceConnection、ContentObserver 注册了没取消。
用一段简单的代码来展示「静态引用 + 内部类」的典型场景:
public class CameraActivity extends AppCompatActivity {
private static Handler sHandler = new Handler();
private static Runnable sRunnable = new Runnable() {
@Override
public void run() {
// 这个 Runnable 隐式持有外部类引用
// 而外部类又被 static 的 Handler 持有
// 导致 Activity 退出后无法被 GC 回收
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
sHandler.postDelayed(sRunnable, 30000);
}
}
2.5 内存泄漏的危害
危害其实就三点,但每一点都够你喝一壶的:
- OOM 崩溃 —— 最直接的后果,用户直接看到「抱歉,应用已停止运行」。
- 应用卡顿 —— GC 被频繁触发,每次 GC 都会暂停应用线程(STW),界面掉帧、响应变慢。
- 进程被 LMK 杀死 —— 系统内存紧张时,Low Memory Killer 会优先干掉占用内存最大的进程,就是你。
做个简单计算:每次泄漏 2 MB,用户正常使用 10 分钟泄漏 20 次,那就是 40 MB。中低端机型的总堆大小可能也就 128 MB,再来几个大 Bitmap,OOM 几乎是必然的。