1. 引入
《引入篇》介绍了如何确定一个对象是否为垃圾;《垃圾回收算法》介绍了垃圾回收机制的“指导思想”。本文主要介绍垃圾回收机制具体是如何实现的。
如果说垃圾回收算法是垃圾回收机制的方法论,垃圾回收器则是垃圾回收机制的具体实现。
学习垃圾回收器相关知识,可以从三个问题入手:
- 垃圾回收器有哪些?
- 各类垃圾回收器的工作机制是怎么样的?
- 各类垃圾回收器的优缺点以及适用场景是什么?
在开启垃圾回收器的学习之前,需要对以下概念有一个初步的了解与认识。
1.1 串行,并行,并发,独占
1.1.1 串行
串行(Serial):任务按照顺序依次执行,一个任务完成后才能开始下一个任务。
【例】 小红去超市购物,这个过程有两个步骤:需要先挑选商品,然后排队结账。在这个过程中,挑选商品和结账这两个任务是串行执行的,必须等待挑选商品完成后才能开始结账,不能同时进行。
GC中,串行指的是:所有用户线程停止,单条GC线程回收堆。
1.1.2 并行
并行(Parallel):多个任务同时进行,彼此之间相互独立,互不影响。每个任务都可以独立执行,无需等待其他任务的完成。
【例】 小红和小绿去超市购物,分开挑选商品,然后各自排队结账。在这个过程中,小红和小绿可以同时进行挑选商品和结账的任务,彼此之间互不影响,这就是并行执行。
GC中,并行指的是:所有用户线程停止,多条GC线程回收堆的情况(需多核CPU支持)。
1.1.3 并发
并发(Concurrency): 多个任务在同一时间段内交替执行,虽然宏观上看起来是同时执行的,但实际上是通过任务切换来实现的,并非真正同时进行。
【例】以小红学习为例,小红学习累了就玩会儿手机,玩累了就继续学习。这个过程中,小红不能在某一时刻既学习又玩手机,但是在这个时间段内,她完成了学习和玩手机两项任务。
GC中,并发指的是:同一时间垃圾收集器线程与用户线程都在运行。
1.1.4 独占
GC中,独占(Monopoly)指的是:GC工作时,GC线程会抢占所有资源执行,整个应用程序会被停止。
1.2 吞吐量
吞吐量代表处理任务的能力,用来衡量一段时间内能够完成多少任务,是性能优化中的一个重要指标。
JVM中,吞吐量的计算公式为:吞吐量 =
1.3 停顿时间
GC时会触发STW,停顿时间是指GC收集器在工作时,所有用户线程(整个应用程序)的暂停时间。
1.4 总结
停顿时间越短就越适合需要与用户交互或需要保证服务响应质量的程序,良好的响应速度能提升用户体验;
高吞吐量则可以最高效率地利用处理器资源,尽快完成程序的运算任务。
针对独占类垃圾回收器,由于
GC线程抢占所有资源执行,整个应用程序停止。因此,停顿时间较长,但吞吐量大。 对于并发类的垃圾回收器,GC线程和用户线程交替执行,所以停顿时间会缩短,但是吞吐量会降低。我们的最终目的是追求更高的吞吐量和更短的响应时间。
2. 垃圾回收器分类
垃圾回收器分为分代回收器和分区回收器两类,分代回收器一般是新生代回收器与老年代回收器结合使用。
如果两个收集器之间存在连线,就说明它们可以搭配使用。需要明确的是,万能的回收器是不存在的,所以在实际开发过程中,需要结合具体场景选择合适的收集器。
3. 分代回收器
3.1 新生代回收器
3.1.1 Serial垃圾回收器
3.1.1.1 运行过程
| 收集器 | 启动参数 | 采用算法 | 收集动作 | STW | 适用场景 |
|---|---|---|---|---|---|
Serial收集器 | -XX:+UseSerialGC(开启该参数后,年老代会使用MSC) | 标记-复制算法 | 串行GC,单线程 | GC过程在STW中执行 | 适用于运行在Client模式运行的JVM |
3.1.1.2 优缺点
-
优点:
- 简单高效;
- 内存资源受限情况下,额外内存消耗最少;
- 单核处理器或处理器核心数较少的环境来说, 由于没有线程交互的开销,专心做垃圾收集可以获得最高的单线程收集效率。
-
缺点:
GC过程中是需要全程发生在STW中的,所以基于系统层面来说,对用户来说,体验感欠佳。
3.1.2 ParNew垃圾回收器
3.1.2.1 运行过程
ParNew是Serial收集器的多线程并行版本。
| 收集器 | 启动参数 | 采用算法 | 收集动作 | STW | 适用场景 |
|---|---|---|---|---|---|
ParNew收集器 | 启动参数:-XX:+UseParNewGC | 标记-复制算法 | 并行GC,多线程 | GC过程在STW中执行,采用多线程回收 | 与CMS收集器配合工作 |
3.1.2.2 优缺点
- 优点:
- 使用了多线程回收,因此能够在很大程度上缩短系统的停顿时间,从而能够带来比
Serial更好的用户体验。
- 使用了多线程回收,因此能够在很大程度上缩短系统的停顿时间,从而能够带来比
- 缺点:
- 若是单核的机器上运行时,其效率可能还不如
Serial。
- 若是单核的机器上运行时,其效率可能还不如
3.1.3 Parallel Scavenge 垃圾回收器
| 收集器 | 启动参数 | 采用算法 | 收集动作 | STW | 适用场景 |
|---|---|---|---|---|---|
Parallel Scavenge(吞吐量优先)收集器 | 启动参数:-XX:+UseParallelGC | 标记-复制算法 | 并行GC,多线程 | GC过程在STW中执行,采用多线程回收 | 后台运算不需要太多交互的任务 |
Q: 同样是新生代的多线程
GC收集器,Parallel Scavenge与ParNew的区别是什么?二者关注点不同。
ParNew通过控制GC线程数量来缩短程序的停顿时间,更加关心响应时间;而Parallel Scavenge更关心吞吐量。
3.2 老年代回收器
3.2.1 Concurrent Mark Sweep(CMS)垃圾回收器
3.2.1.1 运行过程
第一款真正意义上支持并发的垃圾收集器,它首次实现了让GC线程与用户线程(基本上)同时工作。追求的目标:获取最短的回收时间。
| 收集器 | 启动参数 | 采用算法 | 收集动作 | STW | 适用场景 |
|---|---|---|---|---|---|
CMS收集器 | 启动参数:-XX:+UseConcMarkSweepGC | 标记-清除算法 | 并行GC,多线程并行执行 | GC过程会发生STW,但并非整个GC过程都在STW中执行,采用多线程回收 | 重视服务的响应速度、系统停顿时间和用户体验的网站或系统 |
回收过程四步走:
- 初始标记(
CMS initial mark):在STW中进行,标记一下GC Roots能直接关联到的对象,速度很快; - 并发标记(
CMS concurrent mark):从根节点出发,对整个堆空间进行可达性分析,找出所有的存活对象。这个过程耗时较长,但是不需要停顿用户线程,可以与GC线程同时执行; - 重新标记(
CMS remark):在STW中进行,修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象,这个阶段的停顿时间通常会比初始标记阶段稍长一些,但也远比并发标记阶段的时间短; - 并发清除(
CMS concurrent sweep):清除标记阶段判断的已经死亡的对象,由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发的。
3.2.1.2 优缺点
- 优点:并发收集、低停顿;
- 缺点:
- 对处理器资源非常敏感;
- 无法处理浮动垃圾;
CMS是基于标记-清除实现的,所以在收集结束时可能会出现大量空间碎片,这将会给大对象分配带来很大麻烦。
3.2.2 Serail Old(MSC)垃圾回收器
Serail Old(MSC)回收器可以理解为Serial回收器的年老代版本。
3.2.2.1 运行过程
| 收集器 | 启动参数 | 采用算法 | 收集动作 | STW | 适用场景 |
|---|---|---|---|---|---|
Serail Old(MSC)收集器 | 启动参数:-XX:+UseSerialGC (开启该参数后,新生代会使用Serial) | 标记-整理算法 | 串行GC,单线程 | GC过程发生在STW中,采用单线程执行串行回收 | Client模式下虚拟机 |
3.2.3 Parallel Old垃圾回收器
Parallel Old回收器可以理解为Parallel Scavenge回收器的年老代版本。
3.2.3.1 运行过程
| 收集器 | 启动参数 | 采用算法 | 收集动作 | STW | 适用场景 |
|---|---|---|---|---|---|
Parallel Old收集器 | 启动参数:-XX:+UseParallelOldGC | 标记-整理算法 | 并行GC,多线程 | GC过程在STW中执行,采用多线程回收 | 注重吞吐量以及CPU资源敏感的系统 |
4. 分区回收器
4.1 G1垃圾回收器
Garbage First(简称G1)回收器,开创了收集器面向局部收集的设计思路和基于Region的内存布局形式。
4.1.1 面向局部收集
在G1之前出现的的垃圾收集器收集的目标是老年代、新生代或者是整个堆。G1可以面向堆内存的任何部分来组成回收集进行回收,衡量标准不再是它属于哪个分代,而是哪块内存中存放的垃圾数量最多,回收收益最大,这就是G1收集器的Mixed GC模式。
4.1.2 基于Region的内存布局
如何实现局部收集呢?G1开创的基于Region的堆内存布局是能实现上述目标的关键,G1将连续的Java堆划分成多个大小相等的独立区域(Region),每个Region可以根据需要,扮演不同角色。收集器根据不同角色的Region采用不同的策略处理。
Humongous:专门用来存储大对象,超过了Region容量的超大对象,将会被存放在N个连续的Humogous Region中;Eden:存放新创建的对象;Survivor:存放经过一次或多次垃圾回收后仍然存活的对象。
4.1.3 运行过程
G1收集器运作过程四步走:
-
初始标记(
Initial Mark):触发STW,使用单条GC线程快速标记GC Roots可以直接关联可达的对象。 -
并发标记(
Concurrent Marking):多条GC线程和用户线程并发执行,从GC Roots开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象。 -
最终标记(
Final Marking):纠正并发标记阶段错标,误标及漏标的对象。 -
筛选回收(
Live Data Counting and Evacuation):对各个Region区的回收价值和成本进行排序,找出回收价值最大的Region优先回收。
| 收集器 | 启动参数 | 采用算法 | 收集动作 | STW | 适用场景 |
|---|---|---|---|---|---|
G1收集器 | -XX:+UseG1GC -XX:+UseStringDeduplication | 局部复制,全局标记-整理算法 | 并行GC,多线程并行执行 | GC过程在STW中执行,采用多线程回收 | 面向服务端应用的回收器,目标为了取代CMS |
参考资料
-
《深入理解Java虚拟机(第3版)》