第2章:内存泄漏基础概念
2.1 核心概念速览
理解内存泄漏前,必须先掌握Java/Android虚拟机中对象存活的判定机制。本章从四个基石概念出发:
- GC Root —— 垃圾回收器(GC)判定对象存活的“根”引用点
- 强引用与弱引用 —— 引用类型对对象生命周期的影响
- 不可达对象 —— 真正可被回收的对象状态
- 内存泄漏定义 —— 应用层面的“垃圾”无法被回收的后果
这些概念是LeakCanary检测内存泄漏的理论基础,掌握它们才能理解检测算法的工作原理。
2.2 图解:从GC Root到内存泄漏的完整路径
2.3 逐个击破
2.3.1 GC Root —— 垃圾回收的“根”
GC Root是垃圾回收器遍历对象图的起始点。只有从GC Root出发通过强引用链可达的对象才会被视为“存活”。
类比: 如果把Java堆看作一棵大树,GC Root就是树根。只有从树根长出枝条(引用链)能到达的树叶(对象)才是“活”的,否则就是落叶等着被扫走。
常见的GC Root类型包括:
- 活跃线程(Thread)
- 方法区中的静态变量(static field)
- JNI全局引用(Global JNI Ref)
- 当前被Native代码持有的对象引用
- 正在执行的方法局部变量(栈帧中的引用)
2.3.2 强引用与弱引用
| 引用类型 | 对GC的影响 | 典型用途 | 是否导致泄漏 |
|---|---|---|---|
| 强引用(Strong Reference) | 阻止GC回收,即使内存不足,宁可OOM也不回收 | 普通对象引用(new、字段赋值) | 是的,最容易引起泄漏 |
| 弱引用(Weak Reference) | 不阻止GC回收,只要GC发现对象仅被弱引用持有,就会立即回收 | 缓存(如WeakHashMap)、监听器解耦 | 不会,除非同时还有强引用链 |
| 软引用(Soft Reference) | 内存充足时保留,内存不足时回收 | 敏感缓存(如图片缓存) | 极少数情况可能延迟回收 |
2.3.3 不可达对象
如果一个对象从所有GC Root出发,不存在任何强引用链可以到达它,该对象就是“不可达对象”。不可达对象是GC即将回收的候选对象。注意:弱引用、软引用、虚引用均不构成强引用链,因此仅被这些引用持有的对象都是不可达的。
2.3.4 内存泄漏定义
在Java/Android中,内存泄漏是指:那些已经不再需要的对象,由于仍然存在一个或多个从GC Root出发的强引用链而无法被GC回收,从而导致堆内存被无效占用、可用内存逐渐减少,最终可能触发OutOfMemoryError(OOM)。
常见误区: “弱引用一定会导致对象被回收?” 不对。弱引用本身不阻止回收,但若该对象同时被一个强引用所引用(即存在强引用链),弱引用不会改变其可达性。弱引用只在对象仅被弱引用持有时才辅助回收。
2.4 应用边界:内存泄漏 vs. 垃圾回收
- 正常回收:对象不可达 → GC回收 → 内存释放。
- 内存泄漏:对象不可用(业务上已不需要)但可达(强引用链存在)→ GC无法回收 → 内存持续占用。
- LeakCanary 检测原理:观察一个对象在预期应该被销毁(例如Activity.onDestroy()后)是否仍然被强引用链可达,从而判定泄漏。
2.5 练习题
- 选择: 以下哪个引用类型一定不会导致内存泄漏?
A. 强引用 B. 软引用 C. 弱引用 D. 以上都可以 - 判断: 一个对象被WeakReference包裹,就不可能再造成内存泄漏。 (对/错)
- 场景分析: 某个Activity的onDestroy()执行后,其mTextView字段仍然被一个静态变量持有。请指出:GC Root是什么?引用链是什么?是否泄漏?
2.6 小结
- GC Root是判定对象存活的起点,强引用链是存活的关键。
- 弱引用、软引用、虚引用不影响GC对不可达对象的回收。
- 内存泄漏 = 业务上不再需要的对象 + 仍有强引用链可达。
- LeakCanary正是基于“预期不可达的对象却仍然可达”来定位泄漏。