标记-清除算法(Mark-Sweep) 是一种经典的垃圾回收算法,它通过两阶段的过程来回收不再使用的对象,具体步骤为:
- 标记阶段(Mark Phase):遍历所有对象,标记出所有存活的对象。
- 清除阶段(Sweep Phase):回收那些未被标记的对象,即那些不再被任何引用引用的对象。
1. 标记-清除算法的工作原理
标记-清除算法分为两个阶段,分别是 标记阶段 和 清除阶段。
1.1 标记阶段
- 在标记阶段,垃圾回收器会从根对象(例如静态变量、栈上的局部变量等)开始,沿着引用链追踪所有存活的对象。
- 所有被追踪到的对象都会被标记为“存活”(即:这部分对象是仍然被引用的)。
1.2 清除阶段
- 一旦标记阶段完成,垃圾回收器就会遍历整个堆空间,清除那些未被标记的对象,这些对象就是不再有任何引用的垃圾对象。
- 被清除的对象会被从内存中释放,所占的内存空间变为可用。
2. 标记-清除算法的优缺点
2.1 优点
- 实现简单:标记-清除算法的实现逻辑相对简单,容易理解,基本上是通过两次扫描来完成。
- 无需移动对象:该算法不涉及对象的移动,因此不会产生像 标记-压缩(Mark-Compact)算法那样的“移动”开销。
2.2 缺点
- 产生内存碎片:由于清除阶段只是简单地删除垃圾对象,堆空间中可能会留下很多空闲的、分散的内存区域。这些空闲区域无法被有效利用,导致内存碎片的出现。长期运行下去,可能导致堆内存无法得到有效使用。
- 效率低:垃圾回收过程中需要遍历堆的所有对象,即使某些对象仍然活跃且不需要回收。这会导致回收过程效率较低。
- 全堆扫描:标记-清除算法需要对堆中的所有对象进行扫描,这对大堆内存应用可能造成较大的性能负担。
3. 标记-清除算法的伪代码
以下是标记-清除算法的伪代码,展示了标记和清除两个阶段的过程:
// 标记阶段
for (each root in roots) {
mark(root);
}
function mark(object) {
if (object is not marked) {
mark(object);
for (each reference in object.references) {
mark(reference);
}
}
}
// 清除阶段
for (each object in heap) {
if (object is not marked) {
free(object);
}
}
在标记阶段,垃圾回收器从根对象开始,递归地标记所有能够到达的对象。然后,在清除阶段,垃圾回收器遍历堆内存,将那些未被标记的对象删除。
4. 标记-清除算法的改进
标记-清除算法虽然简单有效,但它的缺点也十分明显,尤其是 内存碎片 的问题。因此,很多现代 JVM 都不完全依赖标记-清除算法,而是进行了改进或结合其他算法,以提高性能和减少碎片。
4.1 标记-压缩(Mark-Compact)
-
标记-压缩算法(Mark-Compact)是在标记-清除算法的基础上进行了改进。在标记阶段标记出存活对象之后,它不会直接清除未标记的对象,而是将所有存活的对象压缩到堆的一个区域内,这样就消除了内存碎片的问题。
-
优点:消除了内存碎片,提高了堆空间的利用率。
-
缺点:对象的移动增加了额外的性能开销,尤其是在需要移动大量对象时,可能会导致较高的 CPU 开销。
4.2 复制算法(Copying)
-
复制算法通过将堆分为两部分,一个用于存放活动对象,另一个用于存放可回收的对象。每次垃圾回收时,它只回收活动区域的对象,并将活动对象复制到另一块内存区域,从而避免了内存碎片。
-
优点:内存碎片问题得到了较好的解决,且复制的过程比较简单。
-
缺点:需要额外的内存空间来存放副本,堆的大小需要是双倍的。
5. JVM 中的标记-清除算法
在实际的 JVM 实现中,标记-清除算法并不常单独使用,而是通常作为其他垃圾回收策略的基础,结合其他技术一起工作。比如,JVM 中的 Serial GC 和 Parallel GC 都采用了标记-清除算法的思想,但在性能上做了一些改进,以减少内存碎片和提高效率。
- Serial GC:采用标记-清除算法来进行垃圾回收,但它的效率相对较低。
- Parallel GC:通过并行化垃圾回收过程,来减少垃圾回收的时间,但也使用标记-清除算法的思想。
- CMS GC:采用并发标记-清除算法,旨在减少垃圾回收的停顿时间。
6. 总结
标记-清除算法(Mark-Sweep) 是一种简单有效的垃圾回收算法,通过两阶段的标记和清除来释放无用对象占用的内存。它的优点是实现简单,但由于产生内存碎片和效率较低,现代 JVM 更多使用更复杂的垃圾回收算法(如标记-压缩、复制算法等)来解决这些问题。
尽管如此,标记-清除算法仍然是很多垃圾回收器和回收策略中的重要基础,尤其是在对内存管理要求不高或内存较小的应用场景中,依然具有一定的适用性。