JVM垃圾回收(三)- GC算法:基础

GC算法:基础

在介绍GC算法在实际场景中的实现之前,我们先定义一些必要的术语,以及GC算法的基本准则。具体的细节会因收集器的不同而稍有区别,但是基本上来说,所有的收集器会关注以下两个方面:

  1. 找出所有仍然存活的对象
  2. 清除掉其他所有非存活对象(被认为是dead,并且不会再被使用的对象)

在所有的收集器内部,第一步实现的均是:遍历出所有存活的对象。由标记(Marking)进程完成。

 

标记所有可达对象

在JVM中,任何主流GC算法均会以找出所有存活对象为开始。这个概念的解释可以参考下图:

首先,GC定义某些特定的对象为Garbage Collection Roots,一些典型的GC roots如:

  1. 当前执行的方法中的本地变量以及输入参数
  2. 当前活跃的线程
  3. 已载入的类中的静态区域
  4. JNI(Java Native Interface)引用

下一步,GC遍历内存里的整个对象图(whole object graph),从GC roots开始,遍历它们引用的所有对象。任何被GC访问到的对象会被标注为存活。

在上图中,蓝色的圆圈表示存活的对象。当Marking阶段完成时,所有存活的对象会被标注。所有其他对象(上图中的灰色圆圈)便是无法由GC roots访问到的对象,也就是说,你的应用无法再使用这些不可达的对象。这种对象被认为是垃圾,GC在清除它们时会经历以下阶段:

  1. 应用中的线程需要被停止,以便让Marking阶段正常运行。因为应用若是持续运行,则JVM里的对象可能随时在变化,这样就不足以获取到精确的信息。这个场景被称为一个安全点(safe point),它造成的结果便是一个Stop The World pause。Safe point可被不同的原因触发,但是到现目前为止,GC是引入safe point最常见的原因
  2. 这个暂停持续的时间并不取决于堆内存里对象的总数量,也不取决于堆内存的大小。它是由存活的对象个数决定的。所以,增加堆内存的大小并不会直接影响marking阶段的持续时间

 

Removing Unused Objects

不同的GC算法中,清楚unused objects的方式稍有不同。但是所有这类GC算法基本上可以分为以下三组:

  1. Sweeping
  2. Compacting
  3. Copying

Sweeping

Mark and Sweep 算法对于垃圾使用了最简单的方式:忽略掉这些对象。具体的说,就是在marking阶段完成后,所有没被访问到的对象所占据的空间,均被视为可用的空间。并可以被用于为新的对象分配。

这种方式使用到一种名为free-list的结构来记录每个空闲的区域(region)以及它的大小。free-list的管理方法会增加对象分配(object allocation)的开销。并且,它会有一个缺点:可能会存在大量的空闲region,但是如果单个region的空间不足以满足一个allocation,则这个allocation也会失败,并返回OutOfMemoryError的报错。

Compact

Mark-Sweep-Compact算法解决了Mark and Sweep的缺点。它将所有被标注为存活的对象移动到内存区域的起始。这种方法的缺点是增加了每次GC暂停的时间,因为我们需要将所有的对象移动到一个新的地方,并且更新它们的引用。它的优点也显而易见:在这个compacting操作完成后,对于新对象的分配会非常容易。使用这种方式,空余空间的地址随时可获取,并且也不会有碎片的问题。

 

 

Copy

Mark and Copy算法与Mark and Compact非常接近,因为它们都会重新allocate所有存活的对象。比较重要的不同点是:Mark and Copy 重分配的目标空间在一个不同的内存区域。它的优点是可以与Marking阶段并行执行。缺点是它需要一个足够大的额外内存区域,以支持所有的存活对象。

 

References:

https://plumbr.io/handbook/garbage-collection-algorithms

posted @ 2018-12-09 16:20  ZacksTang  阅读(209)  评论(0编辑  收藏  举报