本文转载自微信公众号「Java极客技术」,作者鸭血粉丝。转载本文请联系Java极客技术公众号。
作为一个 Java 开发,在面试的过程中垃圾回收器是经常会被问到的一个问题,随着 Java 的发展,垃圾回收器也经历了很多的发展。大家熟知的垃圾回收器主要有下面几种。
- Serial 单线程新生代复制算法的垃圾回收器;
- SerialOld 垃圾回收器,是一种单线程老年代标记整理算法;
- ParNew 垃圾回收器,是 Serial 的多线程实现,采用复制算法实现;
- Parallel Scavenge 垃圾回收器,是一种高效的多线程复制算法;
- ParallelOld 垃圾回收器,是 Parallel Scavenge 的一种老年代的多线程标记整理算法;
- CMS 垃圾回收器,是一种多线程标记清除算法,后面会详细介绍;
- G1 垃圾回收器,是一种高吞吐量的垃圾回收器。
回收算法
在介绍垃圾回收器之前,我们先了解一下垃圾回收器背后的算法,每个垃圾回收器都是具体算法的实现,不同的垃圾回收器只是背后的算法不同而已,下面就先简单介绍下具体的算法。
标记清除
标记清除算法是一种先标记,后清除的算法,在第一次扫描的时候先标记出所有需要清理的内存,将所有需要回收的内存都标记过后,一次性清理掉。这种算法简单但是效率低,而且内存碎片化严重。内存一旦碎片化严重的话,就会浪费内存,无法分配较大的对象。
复制算法
复制算法的实现方式比较简洁明了,就是霸道的把内存分成两部分,在平时使用的时候只用其中的固定一份,在当需要进行 GC 的时候,把存活的对象复制到另一部分中,然后将已经使用的内存全部清理掉。这种算法可以解决碎片化的问题,但是缺点也很明显,就是浪费内存,有一半的内存都不能使用。
标记整理算法
既然标记清除和复制算法各有优缺点,那自然的我们就想到是否可以把这两种算法结合起来,于是就出现了标记整理算法。标记阶段是标记清除算法一样,先标记出需要回收的部分,不过清除阶段不是直接清除,而是把存活的对象往内存的一端进行移动,然后清除剩下的部分。
标记整理的算法虽然可以解决上面两个算法的一些问题,但是还是需要先进行标记,然后进行移动,整个效率还是偏低的。
分代回收算法
分代回收算法是目前使用较多的一种算法,这个不是一个新的算法,只是将内存进行的划分,不同区域的内存使用不同的算法。根据对象的存活时间将内存的划分为新生代和老年代,其中新生代包含 Eden 区和 S0,S1。在新生代中使用是复制算法,在进行对象内存分配的时候只会使用 Eden 和 S0 区,当发生 GC 的时候,会将存活的对象复制到 S1 区,然后循环往复进行复制。当某个对象在进行了 15 次GC 后依旧存活,那这个对象就会进入老年代。老年代因为每次回收的对象都会比较少,因此使用的是标记整理算法。
垃圾回收器
上面虽然提到了好几个垃圾回收器,但是目前主流的垃圾回收器只有 CMS 和 G1。下面就跟大家聊下这两个垃圾回收器。
CMS 垃圾回收器
CMS 全称 Concurrent Mark Sweep 并发标记清除垃圾回收器。CMS 是一种以获取最短停顿时间为目的的垃圾回收器。提到停顿时间,我们都知道任何垃圾回收器在进行工作的时候都会出现 STW,Stop the World 停止用户进程,这对业务来说只很难接受的,但是现在市面上所有的垃圾回收器都无法避免这个问题,只能最大化的去优化,从而降低停顿的时间。
CMS 虽然被称为是并发的垃圾回收器,但是也并不是完全并发的,从名字上我们可以看到是采用标记-清除算法来实现的,整个实现过程分为五个步骤:
- 初始标记:暂停所有线程,从来可达性分析来标记对象,这也是 CMS 垃圾回收器第一个 STW 的时候;
- 并发标记:并发标记的时候 GC 线程和用户线程是同时存在的,这个过程中会记录所有可达的对象,但是这个过程结束过后由于用户线程一直在运行所以还会产生新的引用更新,也就是需要下一步了;
- 并发预清理:这个阶段用户线程和 GC 线程同时运行,GC 线程会进行一下预清理的动作;
- 重新标记:重新标记这个阶段会暂停用户线程,将上一步并发标记过程中用户线程引起的更新进行修正,这个时间会比初始标记时间长,但是会比并发标记时间短一点;
- 并发清除:在所有需要清理的对象都被标记完过后就会执行最后一步清理的动作。清理的时候用户线程是可以继续运行的,GC 线程只清理标记的区域。
G1 垃圾回收器
G1 全称 Garbage-First 是一种面向服务器的垃圾回收器,通过将堆内存划分为多个 Region 来实现可预测的停顿时间模型。在 G1 当中,新生代和老年代已经不再是物理隔离,而都是被划分一个个 Region 区域。正是由于这种可预测的时间停顿模型让 G1 成为了一个高吞吐量的垃圾回收器。G1 能充分利用 CPU,多核环境下可以缩短 STW 的时间。
G1 垃圾回收器的整个实现过程分为四个步骤:
- 初始标记:通过可达性分析标记 GC Roots 的直接关联对象,这个阶段与 CMS 一样需要 STW;
- 并发标记:并发标记是通过 GC Roots 找到存活的对象,这个阶段 GC 线程是与用户线程同时运行的,并且这个阶段的时间比初始标记长;
- 最终标记:最终标记跟 CMS 的重新标记一样,也是为了修正并发标记过程中因用户线程继续运行而导致产生新的引用更新;同样的这里也需要 STW;
- 筛选回收:筛选回收这里会对每个 Region 的回收成本进行排序,根据用户期望的停顿时间来制定收回计划,这也就是可预测的停顿时间模型的体现之处,这个阶段 GC 线程是与用户线程同时运行的。
总结
虽然说 Java 开发不用程序员去手动创建和回收内存,但是了解和掌握垃圾回收器是每个 Java 程序员必须要掌握的,不仅仅是面试的过程中会被问到,对自己的职业发展也是很有帮助的。本文是阿粉自己学习和整理的,部分资料参考网络上,分享给大家,帮助大家一起成长。