JVM垃圾回收四-回收算法

328 阅读4分钟

简介

在深入研究垃圾收集算法的具体际实现细节之前,首先定义一些必要的术语,帮助我们理解GC收集算法的一些基本原则。具体细节因GC收集器而异,但通常情况下,所有收集器的工作都集中在两个方面:

  • 找出所有还活着的对象
  • 清除剩下的对象——那些被认为已经死亡和不再使用的对象。

第一步中对活动对象的检查,是在收集器中通过一个称为标记的过程实现的。

标记对象

现代JVM中使用的每个GC算法都是从找出所有仍然存在的对象开始的。这个概念最好使用下面这张表示JVM内存布局的图片来解释:

首先,GC将一些特定对象定义为GC Roots。这种GC Roots的例子有:

  • 当前执行方法的局部变量和输入参数
  • 活动线程
  • 加载类的静态字段
  • JNI引用

接下来,GC遍历内存中的整个对象图,从GC Roots 开始,GC Roots 遍历对其他对象(例如实例字段)的引用。GC访问到的每个对象都被标记为活动对象。

在上图中,活动对象用蓝色表示。标记阶段结束后,将标记出所有活动对象。因此,所有其他对象(上图中的灰色数据结构)都不能从GC Roots访问,这意味着应用程序不再使用这些不可访问的对象。这些对象被认为是垃圾,GC应该在下面的阶段中删除它们。

标记阶段有一些重要的方面需要注意:

  • 标记过程中需要需要停止应用程序线程,因为如果对象引用图一直在变化,就不能真正遍历它。当应用程序线程被临时停止,以便JVM可以进行GC活动时,这种情况称为安全点(Safe Point),导致(Stop-The-World)。有很多原因会触发安全点,但是垃圾收集是导致安全点的最常见原因。
  • 这种暂停的持续时间既不取决于堆中对象的总数,也不取决于hea的大小。

标记阶段完成后,GC可以继续下一步,并开始删除不可访问的对象。

删除无用对象

对于不同的GC算法,删除未使用的对象略有不同,但是所有这些GC算法都可以分为三组:Sweep、Compact和Copy。下面将详细地讨论每种算法。

Sweep

Mark and Sweep算法使用最简单的垃圾处理方法,即忽略这些对象。这意味着标记阶段完成后,未访问对象占用的所有空间都被认为是空闲的,因此可以重用这些空间来分配新对象。 zhezhong 方法要求使用一个空闲列表记录每个空闲区域及其大小。当然,空闲列表的管理增加了对象分配的开销。这种方法的另一个弱点是——可能存在大量的空闲区域,但如果没有一个区域大到足以容纳分配,那么分配就会失败(OutOfMemoryError 异常)。

Compact

Mark-Sweep-Compact算法通过将所有被标记的对象移动到内存区域的开始位置,从而解决了Mark and Sweep的缺点。当然,这种方法的缺点是增加了GC暂停时间,因为我们需要将所有对象复制到一个新位置,并更新对此类对象的所有引用。

标记和清除的好处也是显而易见的——在这样一个压缩操作之后,通过指针碰撞,新的对象分配再次变得非常便捷。使用这种方法,空闲空间的位置总是已知的,也不会触发碎片问题。

Copy

Mark and Copy算法非常类似于Mark-Sweep-Compact算法,因为它们也需要重新定位所有活动对象。区别在于:移动的区域标是一个不同的内存区域,作为活动对象的保存地址。Mark and Copy方法具有一些优点,因为Copy可以与Mark在同一阶段同时发生。缺点是需要一个额外内存区域,这个内存区域应该足够大,可以容纳存活的对象。