JVM的垃圾收集算法

56 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 3 天,点击查看活动详情

Java 可以迅速的发展起来,最主要的原因就是因为 Java 可以自动对内存进行管理,这就让我们的关注点放在了业务上,在实现对内存自动管理的基础之上催生了很多的优秀垃圾收集算法及垃圾收集器

本篇文章就来具体讲讲这些算法及收集器

分代收集理论

我们可以将内存看作是一块空白的区域,如下:

image.png

JVM 每创建一个对象都需要在这块空白区域里划分一小块空间来存储这个对象,但是这个对象可能不会永久存在,也许过了一会儿就成了一个无用的对象了,在内存区域被占满的情况下就会触发垃圾回收机制将这些无用的对象给回收掉,如下所示:

image.png

这些未被回收的对象也许在下一次仍然存活,这也就会涉及到一个问题,每当触发一次垃圾回收都会对上一次存活下来的象做一个判断,这无疑是给垃圾回收增加了负担

因为了能让垃圾回收更高效,就将内存空间划分出不同的区域,将一些朝生夕灭的对象集中放在一块,把那些经过几次垃圾回收仍然存活的对象集中放到另外一块,针对不同的区域做不同的回收处理,于是就形成了如今的分代收集理论

现在市面上大多数的垃圾回收器都遵循了这一理论设计原则

Java 将堆内存分为了新生代(Young Generation) 和老年代(Old Generation) 两个区域,新生代存放的就是那些朝生夕灭的对象,而老年代则是存放经过几次垃圾回收后仍然存活的对象

这样每次垃圾回收就只对其中的某一部分区域进行回收,因此才有了后来的 Minor GC、Major GC、Full GC 这样的回收类型

但是在回收的过程中也会有一个问题,那就是回收后内存会产生许多的空间碎片,导致释放后的内存空间不连续,这就会造成内存空间不能完全的有效利用

知道了分代理论之后,我们再来看看具体是如何进行垃圾收集的,这里一共有三种垃圾收集算法--标记-清除、标记-复制、标记-整理

下面我就来一一介绍下这三种收集算法

标记-清除算法

标记-清除算法一共分为两个阶段,即标记和清除,在垃圾回收时会先标记那些需要回收的对象,然后再来清除这些对象,也是也可以反过来,先标记存活的对象,然后再回收未被标记的对象

我们以标记需要被回收的对象为例

如下,深灰色代表存活的对象,浅灰色代表标记后的对象

image.png

这种回收算法是最早出现也是最基础的收集算法,但是它有两个缺点:

1、执行效率不稳定,如果内存里有大量对象是需要被回收的对象,那么在标记和清除时会消耗大量的时间,导致收集的效率会大大降低

2、标记清除后会产生大量的不连续的内存空间,这就导致空间无法完全的得到有效的利用

标记-复制算法

标记-复制算法是为了解决标记清除带来的执行效率问题而提出的,它将可用内存划分为大小相等的两块,每次只使用其中的一块,当一块的内存用完了的时候,他会将存活的对象复制到另外的一块上,再把那些无用的对象清理掉,如下所示:

image.png

虽然标记-复制算法通过对活对象复制的方式避免了因收集带来的空间碎片问题,如果内存中有大量的存活对象,那么在复制的时候也会消耗大量的时间

但是如果我们创建出来的对象大部分都是朝生夕灭的,那么复制的对象也就那么一小部分

这种算法相对于标记清除算法来说简单高效,但是也有其缺点,那就是实际的使用内存空间被缩减为原来的一半,内存空间无法完全被利用起来

现在大部分的垃圾收集器都是使用的这种算法来回收新生代

标记-整理算法

标记-复制算法在存活对象比较多的情况下,执行效率会下降,况且如果不想浪费 50% 的空间的话,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都 100% 存活的极端情况,所以一般老年代不能使用这种算法

标记-整理算法是针对老年代对象的存亡特征提出的,它的标记过程跟标记-清除算法一样,只不过标记完成之后不直接对可回收对象进行清理,而是将存活的对象都向空间的另一端移动,然后再直接清理掉边界以外的内存,如下所示:

image.png

在活对象进行移动的过程中,是一个比较负重的过程,因为活对象内存空间发生变化对应的就需要去更新指针指向,这就需要全部暂停用户线程才能进行,像这样全部暂停用户线程也被称为 “Stop The World”

而暂停所有用户线程就意味着吞吐量会下降,因此在使用这类算法来进行垃圾回收时需要权衡利弊

结束语

码字不易,还希望多多点赞、转发、收藏下