垃圾收集器
本文参考文章:
(七)JVM成神路之GC分代篇:分代GC器、CMS收集器及YoungGC、FullGC日志剖析引言 在《GC基础篇》中曾 - 掘金
(八)JVM成神路之GC分区篇:G1、ZGC、ShenandoahGC高性能收集器深入剖析引言 在《GC分代篇》中 - 掘金
在如今的官方JDK中,JVM的GC收集器具体实现存在十款,分别为Serial、ParNew、Parallel Scavenge、CMS、Serial Old(MSC)、Parallel Old、G1、ZGC、Shenandoah、Epsilon
等,如下:
在上图中共有十款GC收集器,它们可以根据回收时的属性分为分代和分区两种类型:
- 分代收集器:
Serial、ParNew、Parallel Scavenge、CMS、Serial Old(MSC)、Parallel Old
- 分区收集器:
G1、ZGC、Shenandoah
1、新生代-Serial收集器(单线程)
/ˈsɪriəl/
Serial
是最原始的新生代收集器,同时它属于单线程的GC收集器,所以也被称为串行收集器。顾名思义,它在执行GC工作时,是以单线程运行的,并且该收集器在发生GC时,会产生STW,也就是会停止所有用户线程。但正由于会停止其他用户线程,所以在执行GC时并不会出现线程间的切换。因此,在单核CPU
的机器上,它的清理效率非常高。一般来说,采用Client
模式运行的JVM,选取该款收集器作为内嵌GC是个不错的选择。
Serial
收集器小结:
启动参数:-XX:+UseSerialGC
(开启该参数后,年老代会使用MSC
)。
收集动作:串行GC,单线程。
采用算法:复制算法。
STW:GC过程在STW中执行。
GC发生时,执行过程如下:
因为该款收集器GC过程中是需要全程发生在STW中的,所以基于系统层面来说,对用户体验感欠佳。就好比你在线看片(指电影),看两分钟转几圈,看一段时间后又看圈,反反复复的卡顿….,对于你而言,这显然一件令人难以接受的事情。
2、新生代-ParNew收集器(多线程)
ParNew
收集器是基于Serial
收集器的演进版,从严格意义上来看,它可以被称为Serial
收集器的多线程版本,同样是作用于新生代区域的收集器。在整个实现上,除开GC收集阶段会使用多条线程回收外,其他实现几乎与Serial
收集器大致相同。
ParNew
收集器小结:
启动参数:-XX:+UseParNewGC
。
收集动作:并行GC,多线程。
采用算法:复制算法。
STW:GC过程发生在STW中,采用多线程回收。
GC发生时,执行过程如下:
因为该款收集器与Serial
唯一的不同点就在于使用了多线程,所以GC发生时仍旧会造成程序停顿。但也因为使用了多线程回收,因此能够在很大程度上缩短系统的停顿时间,从而能够带来比Serial
更好的用户体验。
3、新生代-Parallel Scavenge收集器(多线程)
/ˈpærəlel ˈskævɪndʒ/ 陪儿老 死噶win ji
Parallel Scavenge
同样是一款作用于新生代的多线程GC收集器,但与ParNew
收集器不同的是:ParNew
通过控制GC线程数量来缩短程序暂停时间,更关心程序的响应时间,而Parallel Scavenge
更关心的是程序运行的吞吐量,也就是更注重一段时间内,用户代码执行时长与程序执行总时长的占比。
Parallel Scavenge
收集器小结:
启动参数:-XX:+UseParallelGC
。
收集动作:并行GC,多线程。
采用算法:复制算法。
STW:GC过程发生在STW中,采用多线程回收。
GC发生时,执行过程如下:
从上述小结来看,PS
收集器和ParNew
收集器好像并未有太大的区别。但实际上它们两者之间基于的底层GC框架完全不同,同时关注的方向也完全不同。PS
收集器的目标是让程序达到一个可控制的吞吐量(Throughput
),所以PS
也被称为吞吐量优先的垃圾收集器。
4、老年代-Serial Old(MSC)收集器(单线程)
Serial Old(MSC)
与Serial
收集器相同,同样是一款单线程串行回收的收集器,但不同的是:MSC
是一款作用于年老代空间的收集器,它采用标记-整理算法对年老代空间进行回收。同时,该款收集器也可作为CMS
的备用收集器使用。
Serial Old(MSC)
收集器小结:
启动参数:-XX:+UseSerialGC
(开启该参数后,新生代会使用Serial
)。
收集动作:串行GC,单线程。
采用算法:标记-整理算法。
STW:GC过程发生在STW中,采用单线程执行串行回收。
GC发生时,执行过程如下:
Serial Old(MSC)
与新生代收集器Serial
差距不大,回收过程也是采用单线程做串行收集,属于Serial
的年老代版本。
5、老年代-Parallel Old收集器(多线程)
/ˈpærəlel / 陪儿老
Parallel Old
则是Parallel Scavenge
收集器的年老代版本,同样采用多线程进行并行收集,其内部采用标记-整理算法。与新生代的PS
收集器相同的是:PO
同样追求的是吞吐量优先。
Parallel Old
收集器小结:
启动参数:-XX:+UseParallelOldGC
。
收集动作:并行GC,多线程。
采用算法:标记-整理算法。
STW:GC过程发生在STW中,采用多线程回收。
GC发生时,执行过程如下:
PO
作为PS
收集器的年老代版本,其特性与PS
大致相同,所以该款收集器同样适用于注重吞吐量或对CPU资源敏感的系统。
6、CMS 收集器(多线程/并发)
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它非常符合在注重用户体验的应用上使用。
CMS(Concurrent Mark Sweep)收集器是 HotSpot 虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。
从名字中的Mark Sweep这两个词可以看出,CMS 收集器是一种 “标记-清除”算法实现的,它的运作过程相比于前面几种垃圾收集器来说更加复杂一些。整个过程分为四个步骤:
- 初始标记: 暂停所有的其他线程,并记录下直接与 root 相连的对象,速度很快 ;会发生Stop-The-World
- 并发标记: 同时开启 GC 和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以 GC 线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。
- 重新标记: 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短。会发生Stop-The-World
- 并发清除: 开启用户线程,同时 GC 线程开始对未标记的区域做清扫
从它的名字就可以看出它是一款优秀的垃圾收集器,主要优点:并发收集、低停顿。但是它有下面三个明显的缺点:
- 内存碎片:CMS使用了标记-清除算法,在垃圾收集结束之后会出现大量的内存碎片,CMS会在FuIlGC时进行碎片的整理。这样会导致用户线程暂停,可以使用-XX:CMSFullGCsBeforeCompaction=N 参数(默认O)调整N次Full GC之后再整理。
- 产生浮动垃圾:无法处理在并发清理过程中产生的”浮动垃圾”,不能做到完全的垃圾回收。
- 对CPU资源非常依赖:CMS是一款完全基于多线程环境研发的收集器,默认情况下,回收过程中开启的线程数为
(CPU核数+3)/4
,也就代表着:一台八核的机器至少要开启2~3
条GC线程。而当CPU核数少于4
时,CMS的GC线程则会对用户线程性能造成很大影响,因为需要让出一半的CPU运算资源去执行GC回收工作。
CMS 垃圾回收器在 Java 9 中已经被标记为过时(deprecated),并在 Java 14 中被移除
为何不采用标-整算法呢?因为CMS是并发执行的,所以如果将存活对象压缩到内存一端,那么用户线程中的所有对象引用都需改变,实现起来及其复杂且影响效率
因为CMS在回收时会产生浮动垃圾以及内存碎片,所以CMS一般来说都必须要要搭配一款其他的收集器作为后备方案,而可选项有且只有一个:那就是Serial Old(MSC)
,当内存太过碎片化导致无法分配新对象时,或回收一次后存活对象+浮动垃圾占比达到指定阈值时则会触发Serial Old(MSC)
收集器回收。
7、G1 收集器
G1 (Garbage-First) 是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征.
到了JDK1.9时,堆空间慢慢的开始了划时代的改变,在此之前,堆空间的布局都是采用分代存储的方式,无论从逻辑上还是从物理内存上,都是分代的。但是到了Java9的时候,因为默认GC器改为了G1,所以堆中的内存区域被划为了一个个的Region
区。
每个分区都可能是年轻代也可能是老年代,但是在同一时刻只能属于某个代。在运行时,每个分区都会被打上唯一的分区标识。
不过在G1收集器中,年轻代Eden
区、幸存区Survivor
、老年代Old
区这些概念依旧还存在,但却成为了逻辑上的概念,这样做的好处在于:也可以复用之前分代框架的逻辑,同时也满足了Java对象朝生夕死的特性。
G1将Java堆划分为多个大小相等的独立的Region
区域,不过在HotSpot
的源码TARGET_REGION_NUMBER
定义了Region
区的数量限制为2048
个(实际上允许超过这个值,但是超过这个数量后,堆空间会变的难以管理)。
G1中的年老代晋升条件和之前的无差,达到年龄阈值的对象会被转入年老代的Region
区中,不同的是对于大对象的分配,在G1中不会让大对象进入年老代,在G1中由专门存放大对象的Region
区叫做Humongous
区,如果在分配对象时,判定出一个对象属于大对象,那么则会直接将其放入Humongous
区存储。
在G1中,判定一个对象是否为大对象的方式为:对象大小是否超过单个普通
Region
区的50%,如果超过则代表当前对象为大对象,那么该对象会被直接放入Humongous
区。比如:目前是8GB的堆空间,每个Region
区的大小为4MB
,当一个对象大小超过2MB
时则会被判定为属于大对象。如果程序运行过程中出现一个“巨型对象”,当一个Humongous
区存不下时,可能会横跨多个Region
区存储它。
Humongous
区存在的意义:可以避免一些“短命”的巨型对象直接进入年老代,节约年老代的内存空间,可以有效避免年老代因空间不足时的GC开销。
G1收集器具备以下特点:
- 并行与并发:G1 能充分利用 CPU、多核环境下的硬件优势,使用多个 CPU(CPU 或者 CPU 核心)来缩短 Stop-The-World 停顿时间。部分其他收集器原本需要停顿 Java 线程执行的 GC 动作,G1 收集器仍然可以通过并发的方式让 java 程序继续执行。
- 分代收集:虽然 G1 可以不需要其他收集器配合就能独立管理整个 GC 堆,但是还是保留了分代的概念。
- 空间整合:与 CMS 的“标记-清除”算法不同,G1 从整体来看是基于“标记-整理”算法实现的收集器;从局部上来看是基于 “标记-复制”算法实现的。
- 可预测的停顿:这是 G1 相对于 CMS 的另一个大优势,降低停顿时间是 G1 和 CMS 共同的关注点,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为 M 毫秒的时间片段内,消耗在垃圾收集上的时间不得超过 N 毫秒
G1 收集器的运作过程:
- 初始标记(Initial Marking):这是一个短暂的“Stop The World”阶段,用于标记出GC Roots直接可达的对象。
- 并发标记(Concurrent Marking):这是一个较长的阶段,它与应用程序并发执行,用于标记出所有活动的对象。
- 最终标记(Final Marking):这是一个短暂的“Stop The World”阶段,用于更新标记信息,并修正并发标记期间的变化。
- 筛选回收(Live Data Counting and Evacuation):先对各个
Region
区的回收价值和成本进行排序,找出「回收价值最大」的Region
优先回收。,这也是一个“Stop The World”阶段。
G1收集器正是由于「筛选回收」阶段的存在,所以才得以冠名「垃圾优先收集器」。在该阶段中,对各个
Region
区排序后,G1会根据用户指定的期望停顿时间(即-XX:MaxGCPauseMillis
参数设定的值)选择「价值最大且最符合用户预期」的Region
区进行回收,举个例子:
假设此时年老代空间共有800
个Region
区,并且都满了,所以此刻会触发GC。但根据GC的预期停顿时间值,本次GC只能允许停顿200ms
,而G1经过前面的成本计算后,大致可推断出:本次GC回收600
个Region
区恰好停顿时间可控制在200ms
左右,那么最终就会以「回收600
个Region
区」为基准触发GC,这样则能尽量确保GC导致的停顿时间可以被控制在我们指定的范围之内。不过值得注意的是:筛选回收阶段在G1收集器中是会停止所有用户线程后,采用多线程并行回收的。但实际上这个过程中可以与用户线程一起执行做到并发收集的,但因为G1只回收一部分
Region
区,停顿时间是可控的,因此停止用户线程后回收效率会大幅度提高。同时,假设实现并发回收,则又需要考虑用户线程执行带来的一些问题,所以综合考虑,G1中回收阶段采用了发生STW
方案完成(在后续的ZGC、ShenandoahGC
收集器中实现了并发回收)
在G1中不管是新生代还是年老代,回收算法都是采用复制算法,在GC发生时都会将一个Region
区中存活的对象复制到另外一个Region
区内。同比之前的CMS收集器采用的标记-清除算法而言,这种方式不会造成内存碎片,因此也不需要花费额外的成本整理内存。
但自
G1
开始,包括之后的ZGC、ShenandoahGC
收集器,从每个Region
区角度看来是采用的复制算法,但从堆空间整体看来,则是采用了标记-整理算法,这也是所谓的“局部复制,全局标记-整理”。
这两种算法无论是那种都不会造成内存碎片产生,带来的好处是:在为大对象进行内存分配时,不会因为找不到连续的内存空间提前触发下一次GC,有利于程序长期运行,尤其是在大内存情况下的堆空间,带来的优势额外明显。
不过注意:在内存较小的堆空间情况下,CMS的表现会优于G1收集器,平衡点在6~8GB
左右。
适合G1收集器的场景:
- ①堆空间内
50%
以上的内存会被存活占用的应用 - ②分配速度和晋升速度特别快的应用
- ③至少
8GB
以上堆内存的应用 - ④采用原本分代收集器GC时间会长达
1s+
的应用 - ⑤追求停顿时间在
500ms
以内的应用(G1的默认的「停顿时间」目标为200ms
)
8、ZGC 收集器
参考 (八)JVM成神路之GC分区篇:G1、ZGC、ShenandoahGC高性能收集器深入剖析引言 在《GC分代篇》中 - 掘金
在JDK11的时候,Java再次推出一款全新的垃圾回收器ZGC
,它也是一款基于分区概念的内存布局GC器,这款GC器是真正意义上的不分代收集器,因为它无论是从逻辑上,还是物理上都不再保留分代的概念。另外,ZGC与之前的收集器还有一点很大的不同在于:ZGC标记的是指针而并非对象,但最终达到的效果是等价的,因为所有对象以及所有指针都会被遍历。
ZGC
主打的是超低延迟与吞吐量,在实现时,ZGC
也会在尽可能堆吞吐量影响不大的前提下,实现在任意堆内存大小下都可以把垃圾回收的停顿时间限制在10ms
以内的低延迟
Java引入ZGC的目的主要有如下四点:
- ①奠定未来GC特性的基础。
- ②为了支持超大级别堆空间(
TB
级别),最高支持16TB
。 - ③在最糟糕的情况下,对吞吐量的影响也不会降低超过15%。
- ④GC触发产生的停顿时间不会偏差
10ms
。
ZGC的堆内存划分
在ZGC中,也会把堆空间划分为一个个的Region
区域,但ZGC中的Region
区不存在分代的概念,它仅仅只是简单的将所有Region
区分为了大、中、小三个等级,如下:
- 小型区/页(
Small
):固定大小为2MB
,用于分配小于256KB
的对象。 - 中型区/页(
Medium
):固定大小为32MB
,用于分配>=256KB ~ <=4MB
的对象。 - 大型区/页(
Large
):没有固定大小,容量可以动态变化,但是大小必须为2MB
的整数倍,专门用于存放>4MB
的巨型对象。但值得一提的是:每个Large
区只能存放一个大对象,也就代表着你的这个大对象多大,那么这个Large
区就为多大,所以一般情况下,Large
区的容量要小于Medium
区,并且需要注意:Large
区的空间是不会被重新分配的
ZGC的回收过程
ZGC收集器在发生GC时,其实主要操作只有三个:标记、转移与重定位。
- 标记:从根节点出发标记所有存活对象。
- 转移:将需要回收区域中的存活对象转移到新的分区中。
- 重定位:将所有指向转移前地址的指针更改为指向转移后的地址。
ZGC中的一次垃圾回收过程会被分为十个步骤:初始标记、并发标记、再次标记、并发转移准备:[非强引用并发标记、重置转移集、回收无效页面(区)、选择目标回收页面、初始化转移集(表)]、初始转移、并发转移,其中只有初始标记、再次标记、初始转移阶段会存在短暂的STW,其他阶段都是并发执行的。
- ①初始标记
这个阶段会触发STW,仅标记根可直达的对象,并将其压入到标记栈中,在该阶段中也会发生一些其他动作,如重置 TLAB、判断是否要清除软引用等。 - ②并发标记
根据「初始标记」的根对象开启多条GC线程,并发遍历对象图,同时也会统计每个分区/页面中的存活对象数量。
标记栈 - ③再次标记
这个阶段也会出现短暂的STW,因为「并发标记」阶段中应用线程还是在运行的,所以会修改对象的引用导致漏标的情况出现,因此需要再次标记阶段来标记漏标的对象(如果此阶段停顿时间过长,ZGC会再次进入并发标记阶段重新标记)。 - ④非强引用并发标记和引用并发处理
遍历前面过程中的非强引用类型根对象,但并不是所有非强根对象都可并发标记,有部分不能并发标记的非强根对象会再前面的「再次标记」阶段中处理。同时也会标记堆中的非强引用类型对象。 - ⑤重置转移集/表
重置上一次GC发生时,转移表中记录的数据,方便本次GC使用。 - 在ZGC中,因为在回收时需要把一个分区中的存活对象转移进另外一个空闲分区中,而ZGC的转移又是并发执行的,因此,一条用户线程访问堆中的一个对象时,该对象恰巧被转移了,那么这条用户线程根据原本的指针是无法定位对象的,所以在ZGC中引入了转移表
forwardingTable
的概念。 - 转移表可以理解为一个
Map<OldAddress,NewAddress>
结构的集合,当一条线程根据指针访问一个被转移的对象时,如果该对象已经被转移,则会根据转移表的记录去新地址中查找对象,并同时会更新指针的引用。 - ⑥回收无效分区/页面
回收物理内存已经被释放的无效的虚拟内存页面。ZGC是一款支持返还堆内存给物理机器的收集器,在机器内存紧张时会释放一些未使用的堆空间,但释放的页面需要在新一轮标记完成之后才能释放,所以在这个阶段其实回收的是上一次GC释放的空间。 - ⑦选择待回收的分区/页面
ZGC与GC收集器一样,也会存在「垃圾优先」的特性,在标记完成后,整个堆中会有很多分区可以回收,ZGC也会筛选出回收价值最大的页面来作为本次GC回收的目标。 - ⑧初始化待转移集合的转移表
初始化待回收分区/页面的转移表,方便记录区中存活对象的转移信息。 - 注:每个页面/分区都存在一个转移表
forwardingTable
。 - ⑨初始转移
这个阶段会发生STW,遍历所有GCRoots
节点及其直连对象,如果遍历到的对象在回收分区集合内,则在新的分区中为该对象分配对应的空间。不过值得注意的是:该阶段只会转移根对象(也就是GCRoots
节点直连对象)。 - ⑩并发转移
这个阶段与之前的「并发标记」很相似,从上一步转移的根对象出发,遍历目标区域中的所有对象,做并发转移处理。
其实简单来说,ZGC的回收过程可以分为四大阶段:并发标记、并发转移准备、并发转移、并发重映射/定位。
同时ZGC也是一款不分代的收集器,也就代表着ZGC中只存在一种GC类型,同时也不需要记忆集这种概念存在,因为是单代的堆空间,所以每次回收都是扫描所有页面,不需要额外解决跨代引用问题。
ZGC的核心 - 染色指针技术
ColoredPointers
,ZGC的核心技术之一,在此之前所有的GC信息保存在对象头中,但ZGC中的GC信息保存在指针内。同时,在ZGC中不存在指针压缩,因为ZGC中对于指针进行了改造,通过程序中的引用指针来实现了染色指针技术,由于染色指针对于指针的64个比特位全部都使用了,所以指针无法再进行压缩。
染色指针也有很多其他称呼,诸如:颜色指针、着色指针等,其实都是一个意思,无非就是将64位指针中的几位拿出来用于标记对象此时的情况,ZGC中的染色指针用到了四种“颜色(状态)”,分别为:Marked0、Marked1、Remapped、Finalizable
,如下:
Marked1=0010
:标记对象,用于辅助GC。Marked0=0001
:标记对象,用于辅助GC。Remapped=0100
:设置此位的值后,表示这个对象未指向RelocationSet
中(relocation set
表示需要GC的Region
分区/页面集合)。Finalizable=1000
:此位与并发引用处理有关,表示这个对象只能通过finalizer
才能访问。
再来看看ZGC基于染色指针的并发处理过程:
- 在第一次GC发生前,堆中所有对象的标识为:
Remapped
。 - 第一次GC被触发后,GC线程开始标记,开始扫描,如果对象是
Remapped
标志,并且该对象根节点可达的,则将其改为M0
标识,表示存活对象。 - 如果标记过程中,扫描到的对象标识已经为
M0
,代表该对象已经被标记过,或者是GC开始后新分配的对象,这种情况下无需处理。 - 在GC开始后,用户线程新创建的对象,会直接标识为
M0
。 - 在标记阶段,GC线程仅标记用户线程可直接访问的对象还是不够的,实际上还需要把对象的成员变量所引用的对象都进行递归标记。
总归而言,在「标记阶段」结束后,对象要么是M0
存活状态,要么是Remapped
待回收状态。最终,所有被标记为M0
状态的活跃对象都会被放入「活跃信息表」中。等到了「转移阶段」再对这些对象进行处理,流程如下:
- ZGC选择目标回收区域,开始并发转移。
- GC线程遍历访问目标区域中的对象,如果对象标识为
M0
并且存在于活跃表中,则把该对象转移到新的分区/页面空间中,同时将其标识修正为Remapped
标志。 - GC线程如果扫描到的对象存在于活跃表中,但标识为
Remapped
,说明该对象已经转移过了,无需处理。 - 用户线程在「转移阶段」新创建的对象,会被标识为
Remapped
。 - 如果GC线程遍历到的对象不是
M0
状态或不在活跃表中,也无需处理。
最终,当目标区域中的所有存活对象被转移到新的分区后,ZGC统一回收原本的选择的回收区域。至此,一轮GC结束,整个堆空间会正常执行下去,直至触发下一轮GC。而当下一轮GC发生时,会采用M1
作为GC辅助标识,而并非M0
,具体原因在前面分析过了则不再阐述。
染色指针带来的好处
- ①一旦某个分区中的存活对象被移走,该分区就可以立即回收并重用,不必等到整个堆中所有指向该
Region
区的引用都被修正后才能清理。 - ②颜色指针可以大幅减少在GC过程中内存屏障的使用数量,ZGC只使用了读屏障。
- ③颜色指针具备强大的扩展性,它可以作为一种可扩展的存储结构用来记录更多与对象标记、重定位过程相关的数据,以便日后进一步提高性能。
GC组合方案分析
在第二个段落中,我们详细分析了JVM中每款不同的GC收集器,但在实际开发过程中,我们的程序采用哪个组合更好呢?其实并不存在所谓的最好组合,你要选择那套组合作为Java程序的收集器,更多的需根据具体的业务场景来决定。
- 如果你的程序追求低延迟,用户交互度较为频繁,那你可以采用
ParNew + CMS
组合(这也是淘宝早期的选择,但后面采用了自研JVM)。 - 如若你的程序追求高吞吐,后台计算工作较多,那么
Parallel Scavenge
+Parallel Old
这组PS+PO的收集器会更适合你。 - 但你的程序写出来后,更多的情况下部署在单核或双核的机器时,那么最经典的
Serial
+Serial Old
组合绝对是你的最佳选择。
我们再一次将目光聚集在这张图上,需要值得注意的是:在JDK1.8之前,可以采用虚线组合,但在JDK1.8之后,取消了上图中红线的组合,被视为弃用的收集器组合(但如果要用,也是可以用的)。到了JDK1.9时,红线组合被移除,也就代表着在1.9中无法再指定红线组合作为收集器使用。而到了后面的JDK14时,绿线组合也被弃用,同时官方也移除了CMS
收集器,为了给G1
铺路,使用G1
代替了CMS
。