JVM 内存分代与垃圾回收 (GC) 机制详解
引言:理解基石,方能构建高楼
Java 和 Android 开发的便利性很大程度上得益于 JVM (Java Virtual Machine) 提供的自动内存管理和垃圾回收 (GC) 机制。然而,理解这些底层机制并非可选,而是编写健壮、高性能应用的基础。不恰当的对象引用可能导致内存泄漏,进而引发应用卡顿甚至崩溃 (OOM)。
本文将首先坚实地讲解 JVM 的内存分代模型与核心的垃圾回收原理,然后基于此背景,引出 Android 开发中常见的内存泄漏问题,最后深入剖析业界流行的内存泄漏检测框架 LeakCanary 的工作原理,并介绍其基本使用方法。
第一部分:JVM 内存区域与分代模型
JVM 在运行时会将其管理的内存划分为不同的区域,其中与对象实例存储最相关的是堆 (Heap)。为了优化垃圾回收效率,HotSpot JVM(Android ART 虚拟机也借鉴了类似思想)通常会对堆内存进行分代管理。
分代的核心依据是弱分代假说 (Weak Generational Hypothesis):
- 绝大多数对象都是“朝生夕死”的。
- 熬过越多次垃圾收集过程的对象就越难以消亡。 基于此,将堆分为不同区域,存放不同生命周期的对象,并采用不同的 GC 策略,可以显著提高回收效率。
堆内存主要分为以下两代:
1. 新生代 (Young Generation / New Generation)
- 用途: 绝大多数新创建的对象首先在这里分配。
- 特点: 对象生命周期短,GC 发生频繁但速度快(称为 Minor GC 或 Young GC)。
- 内部结构:
- 伊甸园区 (Eden Space): 新对象的出生地。
- 幸存者区 (Survivor Space): 分为两个等大的区域,From Survivor (S0) 和 To Survivor (S1)。
- GC 流程 (基于复制算法):
- Eden 区满,触发 Minor GC。
- 将 Eden 区和 From Survivor 区中的存活对象复制到 To Survivor 区。
- 清空 Eden 和 From Survivor 区。
- 交换 From 和 To Survivor 的角色。
- 对象每在 Survivor 区躲过一次 Minor GC,年龄加 1。达到晋升阈值 (Tenuring Threshold) 时,会被移动到老年代。
- 若 To Survivor 区不足以容纳所有存活对象,部分对象会直接晋升老年代。
2. 老年代 (Old Generation / Tenured Generation)
- 用途: 存放生命周期较长的对象(从新生代晋升而来)或一些无法在新生代分配的大对象。
- 特点: 对象生命周期长,GC 频率低,但单次耗时长(称为 Major GC 或 Full GC)。Full GC 通常会清理整个堆(包括新生代)甚至元空间,暂停时间(STW)较长。
- GC 算法: 通常采用标记-清除 (Mark-Sweep) 或 标记-整理 (Mark-Compact) 算法及其变种。
3. (非堆区) 元空间 (Metaspace) / 永久代 (PermGen)
- 用途: 存储类的元信息、常量池、静态变量等(JDK 版本不同,存储内容有差异)。
- 演进: JDK 8+ 使用元空间 (Metaspace),位于本地内存 (Native Memory),取代了之前的永久代 (PermGen)(位于 JVM 内存)。这解决了 PermGen 大小固定易 OOM 的问题。
- GC: 元空间本身也有 GC,其空间不足可能触发 Full GC。
第二部分:JVM 垃圾回收 (GC) 核心机制
GC 的目标是自动找出并回收不再使用的内存。





