面试题:JVM 常见的垃圾回收算法有哪些?
主要有三种:
- 标记-清除(Mark-Sweep)
- 标记-复制(Mark-Copy)
- 标记-整理(Mark-Compact)
它们解决的都是同一个问题:找到存活对象后,如何处理不再存活的对象所占用的内存。
注意:垃圾回收算法是回收内存的思路,垃圾收集器是具体实现这些思路的程序。
标记-清除算法
基本原理
分为两个阶段:
- 标记:找出所有存活对象并做标记。
- 清除:清理没有被标记的对象所占用的内存。
示意图:
回收前:[A存活] [B垃圾] [C存活] [D垃圾] [E存活]
标记后:[A存活] [B垃圾] [C存活] [D垃圾] [E存活]
清除后:[A存活] [ 空闲] [C存活] [ 空闲] [E存活]优点
- 实现相对简单。
- 不需要移动存活对象。
- 在存活对象较多、移动成本较高的场景中有一定优势。
缺点
容易产生内存碎片。
例如,内存中有很多零散的小空闲区域。即使空闲内存总量足够,也可能没有一块连续空间能满足较大的内存分配请求。
记忆口诀
标记垃圾以外的存活对象,再清理垃圾;不移动对象,但可能产生内存碎片。
标记-复制算法
基本原理
将内存划分为两个区域,通常一次使用一个区域。
- 在当前区域中分配对象。
- GC 时找出存活对象。
- 将存活对象复制到另一个区域。
- 清空原来的区域。
- 后续可以交换两个区域的角色。
示意图:
回收前:当前区域
[A存活] [B垃圾] [C存活] [D垃圾]
复制存活对象:
另一个区域[A存活] [C存活] [ 空闲 ]
清空原区域:
[ 全部空闲 ]优点
- 回收后存活对象集中排列。
- 不容易产生内存碎片。
- 分配内存时可以顺序移动指针,分配过程简单。
缺点
- 需要额外的空间存放复制后的对象。
- 存活对象越多,需要复制的数据通常越多,回收成本也可能越高。
典型应用
传统分代收集设计中的新生代常采用复制思路。
例如,传统新生代通常包含 Eden、Survivor 0 和 Survivor 1。对象在年轻代 GC 中存活时,会被复制到 Survivor 区或晋升到老年代。
注意:实际 JVM 收集器的区域组织和具体实现可能不同,不是所有收集器都严格使用两个等大的空间。
记忆口诀
把存活对象搬到另一边,再清空原区域;回收后整齐,但需要额外空间。
标记-整理算法
基本原理
同样先标记存活对象,但不会把它们复制到另一块独立区域。
- 标记所有存活对象。
- 将存活对象向内存的一端移动。
- 清理边界以外的内存。
示意图:
回收前:[A存活] [B垃圾] [C存活] [D垃圾] [E存活]
整理后:[A存活] [C存活] [E存活] [ 空闲 ]优点
- 回收后存活对象集中。
- 能有效减少内存碎片。
- 不必像复制算法那样始终准备一整块独立的目标空间。
缺点
- 移动对象需要额外开销。
- 对象移动时,需要正确维护相关引用,处理过程相对复杂。
典型应用
传统分代收集设计中,老年代常采用标记-整理思路,因为老年代对象通常存活时间较长,存活对象比例可能较高。
但这只是常见设计,并不是所有老年代收集器都使用标记-整理。
记忆口诀
把存活对象标记出来,移动到一端,清理剩余空间;减少碎片,但需要移动对象。
三种算法对比
| 对比项 | 标记-清除 | 标记-复制 | 标记-整理 |
|---|---|---|---|
| 核心操作 | 清理垃圾 | 复制存活对象 | 移动存活对象 |
| 是否移动对象 | 否 | 是 | 是 |
| 是否容易产生碎片 | 是 | 否 | 否 |
| 额外空间需求 | 通常较少 | 需要目标空间 | 通常不需要独立的等大目标区 |
| 主要开销 | 标记与清理 | 标记与复制 | 标记与移动 |
| 常见适用思路 | 部分收集场景 | 传统新生代 | 传统老年代 |
面试回答模板
问:JVM 常见的垃圾回收算法有哪些?
答:
JVM 常见的垃圾回收算法有标记-清除、标记-复制和标记-整理。
标记-清除先标记存活对象,再清理未存活对象,优点是不需要移动对象,但容易产生内存碎片。
标记-复制将存活对象复制到另一个区域,再清空原区域,优点是回收后内存连续、分配简单,但需要额外空间。
标记-整理先标记存活对象,再将它们移动到内存的一端并清理剩余空间,能够减少内存碎片,但移动对象会带来额外开销。
实际 JVM 会根据不同的内存区域、对象存活情况和收集器设计,选择或组合不同的算法。
需要记住的重点
- 标记-清除:不移动对象,容易产生内存碎片。
- 标记-复制:复制存活对象,需要额外空间。
- 标记-整理:移动存活对象,减少内存碎片。
- 算法没有绝对优劣,需要根据场景选择。
- 垃圾回收算法不等于垃圾收集器。
自测题
题目 1
哪种算法不移动存活对象,但容易产生内存碎片?
答案:标记-清除算法。
题目 2
为什么复制算法通常需要额外空间?
答案:因为需要把存活对象复制到另一个区域,原区域随后才能被整体清空。
题目 3
哪种算法通过移动存活对象来减少内存碎片?
答案:标记-整理算法。
题目 4
标记-复制算法和标记-整理算法的主要区别是什么?
答案:复制算法把存活对象搬到另一个区域;整理算法则把存活对象移动到当前内存区域的一端。