1.前言
结合之前介绍的垃圾回收算法,算法只是我们将无效对象回收的一种方式,最终将这些算法运用起来的,是垃圾收集器,随着java应用的业务场景越来越多,java在不停的版本迭代过程中,推出了很多种垃圾收集器,来应对不同的业务以及服务场景。
2.七种垃圾收集器
2.1Serial 收集器(新生代)
概述: Serial
(串行)收集器是最基本、历史最悠久的垃圾收集器,采用"标记-复制"算法负责新生代的垃圾收集。它是Hotspot
虚拟机运行在客户端模式下的默认新生代收集器。
线程: 它是一个单线程收集器。它会使用一条垃圾收集线程去完成垃圾收集工作,并且它在进行垃圾收集工作的时候,必须暂停其他所有的工作线程( "Stop The World" ),直到收集结束。
注意:Stop The World 就是全线暂停简称STM。想象一下,我们在GC过程中,如果其他用户线程仍然持续运行,那么除了已经存在的垃圾对象,会有新的垃圾对象产生,想要在程序运行的过程中,去彻底的回收所有垃圾,几乎是不太可能的,所以GC收集器就产生了这样的一个动作,就是先将应用的其他线程都挂起,全量去进行GC操作,操作完毕之后释放
,这个动作就是我们提到的**Stop The World
**,这个动作的重要性其实是,在这段时间里我们程序的其他的操作都无法进行,在某些场景还可以接受,对于一些特殊场景,延迟低是必须的,这对于我们的停顿时间的要求是非常高的,所以在越来越新版本的GC收集器中,对于时间的管理,都会有不同的做法。
**优点:**Serial收集是简单而高效的 。在单个 CPU 环境下,由于没有线程交互的开销,因此拥有最高的单线程收集效率
2.2. Serial Old 收集器(老年代)
Serial Old
收集器同样是一个单线程收集器,采用"标记-整理"算法负责老年代的垃圾收集。
如果在服务器端使用,它主要有两种用途:
- 在
JDK5
及以前版本,与Parallel Scavenge
收集器搭配使用; - 作为
CMS
收集器发生失败时的后备预案;
**注意:**STM动作并不是随时暂停所有用户线程的,会选择安全点暂停。
2.3.ParNew 收集器(新生代)
ParNew
收集器是第一个多线程的垃圾收集器。它是运行在 Server
模式下的虚拟机的首要选择,可以与 Serial Old
,CMS
垃圾收集器一起搭配工作,采用"标记-复制"算法。
是Serial GC的升级版,明显的提升肯定是GC的时间变短,STW的时间肯定也会相应的变短。
2.4. Parallel Scavenge 收集器(新生代)
是JDK1.8****默认收集器, 也是一款新生代收集器,使用"标记-复制"算法实现的多线程收集器。
与其它收集器目标不同,CMS
等其它收集器目标是尽可能缩短垃圾收集时用户线程的停顿时间。但是Parallel Scavenge
收集器的目标则是达到一个可控制的吞吐量。所谓吞吐量就是处理器用于运行用户代码的时间与处理器总消耗时间的比值。
使用场景: 停顿时间越短就越适合需要与用户交互 的程序,良好的响应速度能提升用户体验。而高吞吐量则可以高效率地利用 CPU 时间,尽快完成程序的运算任务,适合在后台运算而不需要太多交互的任务。
2.5. Parallel Old 收集器(老年代)
是一个多线程的垃圾收集器,使用"标记-整理"算法。在注重吞吐量以及 CPU
资源的场合,都可以优先考虑 Parallel Scavenge
收集器 + Parallel Old
收集器。
2.6. CMS 收集器(老年代)
CMS(Concurrent Mark Sweep)是一种以获取最短回收停顿时间 为目标的收集器,基于"标记-复制"算法实现,是 HotSpot 虚拟机第一款 真正意义上的并发 收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。适合低延迟的场景,保证用户的使用体验。
为什么说是基本上同时工作呢?那就需要了解CMS的工作流程了。
整个过程分为四个步骤:
- 初始标记: 暂停所有的其他线程 ,并记录下直接与
root
相连的对象,速度很快 ; - 并发标记: 同时开启
GC
和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以 GC 线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。 - 重新标记: 重新标记阶段,是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间 一般会比初始标记 阶段的时间长,远远比并发标记阶段时间短
- 并发清除: 开启用户线程,同时
GC
线程开始对未标记的区域做清扫。
优点和缺点:
优点: 并发收集 、 低停顿。
缺点:
影响用户线程的执行效率: 并发标记和并发清除时,是和用户线程一起运行的,收集过程中肯定占用了用户程序的CPU资源。CMS默认启动的回收线程数是(CPU数量+3)/4,当CPU数量在4个以上时,垃圾回收线程占用不少于25%的CPU资源,势必影响用户线程的执行效率。
**浮动垃圾:**在并发清除阶段,用户线程并没有停止,所以还会继续产生新的垃圾,只能等待下一次收集时才能进行回收,这部分垃圾被称为"浮动垃圾"。
空间碎片: 因为CMS收集器是基于"标记-清除"算法实现的,所以在进行大量的垃圾回收时,会产生很多不连续的内存空间。
**注意:**由于垃圾收集阶段用户线程还需要持续运行,所以需要预留足够的内存空间提供给用户线程使用,因此CMS收集器不能像其它收集器那样等到老年代几乎完全被填满了再进行收集。
2.7. G1 收集器(老年代)
2.7.1.什么是G1收集器
G1
( Garbage-First
) 是一款面向服务器的垃圾收集器,主要针对配备多颗处理器、大容量内存的机器。它不再严格按照分代思想进行垃圾回收。G1
采用局部性收集的设计思路和基于**Region
**的内存布局形式。
2.7.2.G1收集器的结构
不同于Serial系列、Parallel系列、CMS系列,它们都是基于把内存进行物理分区的形式把JVM内存分成老年代、新生代、永久代或MetaSpace,这种分区模式下进行垃圾收集时必须对某个区域进行整体性的收集(比如整个新生代、整个老年代收集或者整个堆)。然后以Region为单位自由的组合成新生代、老年代、Eden区、survior区、大对象区(Humonggous Region)
这种思想上的转变和设计,使得G1可以面向堆内存任何部分来组成回收集来进行回收,衡量标准不再是它属于哪个分代,而是哪块内存存放的垃圾最多,回收收益最大,这就是G1收集器的 Mixed GC模式,即混合GC模式。
2.7.3.G1 垃圾收集器工作流程
- 初始标记( Initial Marking**)**:这个阶段仅仅只是标记GC Roots能直接关联到的对象,这阶段需要停顿线程,但是耗时很短。
- 并发标记( Concurrent Marking**)**:从GC Roots开始对堆的对象进行可达性分析,递归扫描整个堆里的对象图,找出存活的对象,这阶段耗时较长,但是可以与用户程序并发执行。
- 最终标记( Final Marking**)**:对用户线程做另一个短暂的暂停,用于处理并发阶段结束后遗留记录。
- 筛选回收 ( Live Data Counting and Evacuation**)** :负责更新
Region
的统计数据,对各个Region
的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划。可以自由选择多个Region
来构成回收集,然后把回收的那一部分Region
中的存活对象==>复制==>到空的Region中,最后对那些Region
进行清空。
2.7.4 .G1 垃圾收集器的特点
并行与并发 :G1
能充分利用 CPU
、多核环境下的硬件优势,使用多个 CPU
来缩短停顿时间。
分代收集 :虽然 G1
可以不要其他收集器就能独立管理整个 GC
堆,但还是保留了分代的概念。
空间整合 :G1
从整体来看是基于"标记-整理"算法实现的收集器,从局部(两个Region
之间)上来看是基于"标记-复制"算法实现的。这意味着G1
运行期间不会产生内存空间碎片,收集后能提供规整的可用内存。
用户指定期望停顿 :设置不同的期望停顿时间,可以让G1
在不同的场景下取得吞吐量和延迟之间的最佳平衡,G1
的默认停顿目标为200
毫秒。
3.总结
从现在往回看,我们会发现每个垃圾收集器都是一个时代的产物。
**第一阶段:**在单核CPU,内存资源稀缺的时代使用的是Serial和Serial Old收集器,对于单核CPU,内存只有几十M的场景Serial的效率是非常高的。
**第二阶段:**进入多核CPU时代后出现了Parallel Scavenge和Parallel Old收集器,利用多线程并行收集极大的提高了垃圾收集的效率,所以在多核CPU场景,内存在几百M到几G的场景Parallel Scavenge和Parallel Old是适用的。
**第三阶段:**随着内存的变大,垃圾收集的过程时间变得越来越长了,BS系统的发展也逐渐开始重视用户体验了,所以就出现了CMS以减少用户线程停顿时间为目的的收集器,CMS通过并发收集减少了用户线程的停顿时间,在多核CPU,并且内存空间几G到几十G的空间、并且注重用户体验的CMS垃圾收集器是适用的。
**第四阶段:**CMS遗留了一些比较致命的问题,所以就有了G1,G1不再对内存进行物理上的分代,而只是进行逻辑上的分区,通过各种机制让垃圾收集变得更智能和可控了,多核CPU,并且内存在10G到上百G的场景G1比较适合。