垃圾回收器
垃圾收集器在虚拟机规范中并没有过多规定,可以由不同厂商、不同版本的jvm来实现,由于jdk的不断迭代,已经衍生出了众多的GC版本
按线程数分为:串行、并行
按工作模式分为:并发式和独占式
按碎片处理方式:压缩式和非压缩式
衡量垃圾回收器的指标
- 吞吐量:运行用户代码的时间占总运行时间的比例(总运行时间=内存回收时间+程序运行时间)
- 垃圾收集的开销:垃圾收集所用时间占总运行时间的比例
- 暂停时间:执行垃圾回收时,工作线程被占用的时间
- 收集频率:相对于应用程序的执行,收集操作发生的频率 (收集频率低,一次收集的垃圾会更多,暂停的时间更长)
- 内存占用:java 堆区所占内存大小
- 快速:对象从诞生到被回收所经历的时间
这几个指标中,内存占用、吞吐量和暂停时间,三者不可能同时满足,而随着硬件性能的提升,内存占用问题越来越能被容忍,而内存空间越大,吞吐量就越大,但是暂停时间就会更长
所以吞吐量和暂停时间,是矛盾的点
吞吐量和暂停时间
运行用户代码的时间占总运行时间的比例(总运行时间=内存回收时间+程序运行时间)
- 例如总运行时间100分钟,垃圾收集花费1分钟,吞吐量就是99%,这个数字当然希望越大越好
在这种情况下,应用程序能容忍较高的暂停时间,因此,高吞吐量的应用程序有更长的时间基准,快速响应(暂停时间)是不必考虑的
注重吞吐量,一次垃圾回收的时间会更长,但是总的垃圾回收的时间会短一些
暂停时间就是stw停止应用程序线程的时间,暂停时间优先,意味着尽可能的让单次的stw时间最短,这样回收的频率会更高,总的垃圾回收时间会更长一些,对于需要频繁交互的程序暂停时间短(低延迟)体验更好
高吞吐量和低延迟是竞争关系,无法同时满足,所以一个垃圾回收器只能针对于其中一个优先来设计,或者两者采取折中方案
不同垃圾回收器概述
垃圾回收器是java的招牌能力,极大的提高了开发效率
7款经典的垃圾回收器:
串行回收器:Serial、 Serial old (垃圾回收时,只有一个线程进行垃圾回收)
并行回收器:ParNew 、Parallel Scavenge 、 Parallel old (垃圾回收时,有多个垃圾回收线程)
并发回收器:CMS、G1 (垃圾回收线程和工作线程交替执行)
不同的垃圾回收器可以回收的不同区域
不同的jdk版本,垃圾收集器的组合方式
jdk8中默认的垃圾收集器组合为:Parallel Scavenge 和 Parallel old ,也使用 ParNew 和 CMS ,或者Serial 和 Serial old
jdk9默认的是G1
Serial old 是 CMS 的保底机制
因为java的使用场景很多,所以针对不同的场景,会有不同的垃圾回收器,所以没有最好的垃圾回收器,只有最适合的
Serial 和 Serial old
Serial 是最基本、历史最悠久的垃圾回收器,是jdk1.3之前回收新生代的唯一选择,也是hotspot在client模式下默认的新生代垃圾回收器
Serial 收集器采用复制算法,串行回收和STW机制的方式执行内存回收
Serial old 同样采用串行收集器和STW机制,但是内存回收算法采用的是标记-压缩算法
Serial old是client模式下,默认的老年代垃圾回收器,在server模式下Serial old的主要用途是:与新生代的Parallel Scavenge 配合使用,以及作为老年代CMS的后备垃圾收集方案
这两个收集器都是单线程的,单线程的意义不仅仅在于只会有一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾回收时,必须暂停其他所有工作线程,直到它收集结束
Serial的优点在于,简单高效,这里的高效是针对其他单线程收集器来说的,对于单个cpu的环境来说,Serial由于没有线程交互的开销,专心做垃圾收集自然可以获得更高的单线程收集效率,在client模式下是不错的选择
在内存不大的场景下,可以在较短时间完成垃圾回收,只要不是频繁发生,串行垃圾回收器是可以接受的
在hotspot中,使用 -XX:+UseSerialGc 可以指定年轻代和老年代都使用串行的垃圾收集器(Serial 和 Serial old)
现在基本已经不用串行垃圾回收器了,因为很少会有单核cpu了,而且对于交互性较强的应用,这种垃圾收集器是不可接受的
ParNew垃圾回收期
ParNew除了采用并行回收以外,和Serial基本没有区别,都是复制算法和STW机制,可以理解为Serial的多线程版本
Par是Parallel的缩写,New代表只能处理新生代
ParNew是jvm在Server模式下新生代的默认垃圾收集器
ParNew 一般和Serial Old 或者 CMS一起使用,对于新生代,回收次数频繁,使用并行的方式要更高效,对于老年代,回收的次数少,使用串行的方式节省资源(因为不涉及线程的资源)
- ParNew 也并非一定比Serial 更高效,在多CPU的环境下,可以充分利用多核心的优势,能更快的完成垃圾收集,效率更高
- 但是在单CPU的情况下,Serial 不需要频繁的任务切换,可以避免一些额外开销,反而效率更高
基本已经不在使用了
Parallel 与Parallel old
Parallel Scavenge 同样采用复制算法、并行回收和STW机制
和ParNew 不同,Parallel Scavenge 的目标是达到一个可控的吞吐量,也被称为吞吐量优先的垃圾收集器
Parallel Scavenge 的自适应策略也是与Parnew 一个重要区别
高吞吐量可以高效率地利用CPU时间,尽快完成运算任务,主要适合在后台运算而不需要太多交互的任务,常用与服务器
Parallel 收集器在1.6时提供了用于执行老年代垃圾收集的Parallel old 收集器,用来代替 Serial old
Parallel old 采用标记-压缩算法,同样基于并行回收和STW机制
jdk8默认就是Parallel 与Parallel old
参数配置:
-XX:+UseParallelGC 手动指定年轻代使用Parallel垃圾收集器
-XX:+UseParalleloldGC 手动指定老年代使用Parallel Old垃圾收集器
- 上面两个参数,只要开启一个,另一个也默认开启,可以互相激活,jdk1.8默认开启,1.9默认的就是G1了
-XX:ParallelGCTheards 设置年轻代并行收集器的线程数
- CPU数小于8个,XX:ParallelGCTheards 最好与CPU数量相等
- CPU数量大于8个,XX:ParallelGCTheards 的值等于3+[5*CPU数]/8
-XX:MaxGCPauseMillis 设置垃圾收集器的最大停顿时间也就是stw的时间,单位为毫秒,使用这个参数要慎重
- 也只能尽可能的把时间控制在设定的时间内,收集器会动态的调整堆大小或者一些其他参数
-XX:GCtimeRation 垃圾收集器占总时间的比例,用于衡量吞吐量的大小
- 取值范围为0-100,默认是99,也就是垃圾回收时间不超过1%,与前一个参数是有冲突的
-XX:+UseAdaptiveSizePolicy 设置收集器具有自适应调节策略,默认是开启的,能够动态的调整年轻代、老年代的比例,年轻代内伊甸园区、幸存者区的比例,以及晋升老年代的对象年龄等参数
对客户来说,暂停时间更重要,对于服务器来说,吞吐量更重要
CMS垃圾回收器
jdk 1.5 CMS(Concurrent-Mark-Sweep)收集器,这是hotspot虚拟机第一款真正意义上的并发收集器,实现了让垃圾收集线程和用户线程同时工作
CMS 的关注点是尽可能缩短垃圾收集时用户线程的停顿时间,停顿时间越低,与用户交互体验就越好
CMS 使用的算法是标记-清除算法,一样会有stw
CMS的工作过程:
初始标记:工作线程会因为STW机制出现短暂的暂停,这个阶段的目的是标记处GC Roots 能直接关联到对象,由于直接关联的对象少,所以比较块
并发标记:从GC Roots的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长,但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行
重新标记:由于在并发标记阶段中,程序的工作线程会和垃圾收集线程同时运行或者交叉运行,会有一些错误标记,为了修正并发标记期间,因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间会比初始标记阶段稍长,但是比并发标记阶段的时间短
并发清除:清理删除标记阶段判断已近死亡的对象,释放内存空间,因为不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发
目前并没有垃圾收集器可以完全不需要stw,CMS在初始化标记、重新标记这两个阶段仍然需要执行STW机制,但是暂停的时间不会很长,而最耗费时间的并发标记、并发清除阶段都不需要暂停工作线程,所以整体的回收是低停顿的
因为在执行垃圾回收过程中,用户线程并没有中断,所以还需要确保用户线程有足够的内存可用,因此,CMS不能像其他收集器一样等到老年代几乎完全被填满了在收集,而是当堆内存使用率达到某一阈值时,就开始回收,如果预留的内存不足以支持工作线程运行,就会临时启用Serial old 收集器来进行老年代的垃圾回收,所以CMS有一个预备方案Serial old,但是这个预备方案的回收时间会很长
- CMS使用的标记清除算法,虽然更加高效,但是会有内存碎片问题,所以分配内存时不能使用指针碰撞,只能选择空闲列表,
- 因为CMS在清除对象时,工作线程并没有停止,所以,不能使用标记-压缩算法,因为这样会导致对象的地址变更,只能采用标记-清除算法,如果一定要采用标记-压缩算法,那么清除阶段就必须执行STW机制
CMS的优点是:并发收集和低延迟,但是
- CMS会产生内存碎片,并发清除后,在分配大对象的时候,有可能会提前触发FUll GC
- CMS对CPU资源十分敏感,并发阶段虽然不会导致线程的停顿,但是仍然会占用一部分线程,导致应用程序变慢,吞吐量降低,如果CPU的性能较差,延迟会更明显
- CMS无法处理浮动垃圾,
- 浮动垃圾是指,重新标记会去重新确认,原本在并发标记阶段有可能是垃圾的对象,但是它并不能标记出在并发标记阶段,工作线程产生的新的垃圾,这些对象就不能及时回收了,只能等到下一次垃圾回收
CMS的可设置的参数
-XX:+UseConcMarkSweepGC 手动指定使用CMS收集器执行内存回收任务,设置这个参数后,会自动开启ParNew+CMS+Serial old
-XX:CMSInitiatingOccupanyFraction 设置堆内存使用率的阈值,jdk5默认为68%,jdk6及以上版本默认是92%,这个值越大,越不容易触发CMS,可以减少老年代的回收次数,但是发生FUll GC 的概率也越大
-XX:+UseCMSCompactAtFullCollection 指定在执行完Full GC之后,对内存进行压缩整理,可以避免内存碎片问题,但是停顿时间会变得更长
-XX:CMSFullGCsBeforeCompaction 和上一个参数一起使用,设置在执行了多少次Full GC之后对内存空间进行整理
-XX:ParallelCMSThreads 设置CMS的线程数量,默认的线程数量是:(并行的线程数量+3)/4
CMS在jdk9中已经声明后续会移除了,在jdk14中被彻底删除,想要强行使用的话,也不会报错,会以默认的GC方式启动
GC的选择建议:
最小化使用内存和并行的开销,使用Serial GC
最大化吞吐量:Parallel GC
最小化GC停顿时间:CMS GC
G1
随着硬件性能的提升,和业务场景越来越复杂、庞大,前面几款垃圾收集器逐渐不能满足使用要求
G1是jdk4引入的垃圾收集器,在jdk9开始是默认的垃圾回收器,官方希望G1可以在延迟可控的情况下尽可能的提高吞吐量,所以担当起了全功能的垃圾回收器(同时可以应用于新生代和老年代)
G1(Garbage First)
G1是一个并行的回收器,它把堆内存分割为很多不相关的区域,使用不同的region来表示伊甸园区、幸存者区、老年代等
G1会避免在整个java堆空间中进行全区域的垃圾回收,它会根据每个region里面的垃圾堆积的价值大小,也就是回收所获得的空间大小和回收需要的时间,维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的region
因为这种方式的侧重点在于回收垃圾最大价值的区间(region),所以取名为Garbage First,也就是垃圾优先的意思
G1是面向服务端应用的垃圾收集器,主要针对配备多核CPU以及大容量内存的机器,以极高的概率满足GC停顿时间的同时,还兼具高吞吐量的性能特征
G1是jdk4引入的垃圾收集器,jdk7正式启用,在jdk9开始是默认的垃圾回收器,取代了Parallel +Paeallel old 的组合,被称为全能的垃圾收集器
jdk 8中,还不是默认的垃圾收集器,可以使用:-XX:+UseG1GC 来启用
G1的特点
- 兼具了并行与并发
- 并行性:G1在回收期间,可以有多个GC线程同时工作,有效利用多核计算能力,此时用户线程是stw的
- 并发性:G1有与应用程序交替执行的能力,部分工作可以和应用程序同时执行,因此一般来说,不会再整个回收阶段发生完全阻塞应用程序的情况
- 分代收集
- G1依然属于分代型的垃圾回收器,会区分年轻代和老年代,年轻代也依然有伊甸园区和幸存者区,但是从堆结构上看,它不在要求整个伊甸园区、年轻代或者老年代都是连续的,也不在坚持固定大小和固定数量
- 将堆分为若干个区域,这些区域包含了逻辑上的年轻代和老年代
- 和之前的回收器不同,同时兼具了年轻代和老年代
- 空间整合:
- CMS采用的标记-清除算法,所以会有内存碎片问题,它会在进行一定次数的GC后对内存进行一次进行整理
- 而G1是将内存划分为多个region,内存回收也是以region为单位进行的,region之间采用的复制算法,从整体上看可看做是标记-压缩算法,两种算法都可以避免内存碎片,有利于程序长时间的运行,分配大对象时不会因为无法找到连续的内存空间而提前触发GC,堆越大,G1的优势也就越明显
- 可预测的停顿时间模型:(也就是软实时,并不绝对)
- G1相对于CMS的一个优势就是,G1除了追求低停顿外,还能建立可预测的停顿时间模型,能够让使用者明确的指定一个时间长度,来控制垃圾收集的时间(指定时间M毫秒,垃圾回收时间就不得超过N毫秒,M-N就是stw的时间)
- 因为分区的原因,G1可以只选取部分区域进行内存回收,这样就缩小看回收范围,可以控制全局停顿的情况发生
- G1根据region的垃圾堆积价值大小(回收所获得的空间大小以及回收所需要的时间),维护一个优先列表,每次根据允许的时间,优先回收价值最大的region,保证了G1收集器在有限的时间内可以获取尽可能高的收集效率
- 相比于CMS,G1未必能做到CMS在最好情况下的延迟停顿,但是相比于最差情况要好很多
但是G1并不能全方位的强于CMS,比如G1为了垃圾收集所产生的占用和额外执行负载都高于CMS,通常来说,在内存小的应用,CMS的表现要优于G1,G1在大内存应用能发挥出优势,平衡点在6-8G之间
G1参数设置
- -XX:+UseG1GC 手动指定G1回收器执行内存回收任务
- -XX:G1HeapRedionSize 这种每个Region的大小,值必须是2的幂,范围是1到32MB(1,2,4,8…32),目标是根据最小的java堆大小,划分出2048个区域,默认是堆内存的1/2000
- -XX:MaxGCPauseMillis 设置期望达到的最大GC停顿时间,默认是200MS (jvm会尽力达到,但并不保证),这个值也不是越小越好,越小能回收的region就越少,如果工作线程内存占用较快,垃圾回收不及时,就会触发full Gc ,反而延迟会很长
- -XX:ParallelGCThread 设置stw工作线程的数值,最多为8
- -XX:ConcGCThreads 设置并发标记的线程数,将n设置为并行垃圾回收线程数的1/4左右
- -XX:InitiatingHeapOccupancyPercent 设置触发并发GC周期的java堆占用率阈值,超过此值,就触发GC,默认为45
对于G1的调优,可以简单的分为三部,首先开启G1,然后设置堆的最大内存,在设置最大停顿时间,剩下的都可以交给垃圾回收器自主完成
Region
G1将堆空间划分成了2048个大小相同的独立region,region的大小根据堆空间的实际大小而定,整体被控制在1到32MB之间,所有的region大小相同,且在JVM生命周期内不会被改变
虽然保留了新生代、老年代的概念,但是新生代、老年代不在是物理隔离的了,他们都是一部分region的集合,而且不要求连续
一个region有可能属于 Eden、Survivor或者老年代的区域,角色之间可以转换,但是一个region只能属于一个角色
G1 中还增加了一种新的内存区域,叫做 Humongous 内存区域,用于存储大对象,一个对象如果超过一个region的百分之50就会被放到H区
- 因为对于堆中的大对象,默认是直接分配到老年代的,如果只是一个短期存在的大对象,会对垃圾收集造成负面影响,为了解决这个问题,就划分出了H区,专门存储大对象,如果一个H区都装不下一个大对象,就会使用两个连续的H区来存储,所以有时为了能找到连续的H区,不得不启动Full GC,而G1的大多数行为也会把H区当作老年代的一部分看待
图中空白处表示未使用的内存
G1垃圾回收过程
G1 GC主要包含三个环节
- 年轻代 GC (Young GC)
- 老年代并发标记过程 (Concurrent Marking)
- 混合回收(Mixed GC)
如果需要还会执行单线程、独占式、高强度的Full GC ,这是一种保护机制
应用程序分配内存时,发现Eden区用尽时开始年轻代的回收过程,年轻代的收集阶段是一个并行的独占式的收集器,会暂停所有的程序线程,启动多线程执行年轻代回收,然后存活对象会被移动到幸存者区间或者老年区间
当堆内存使用率达到一定值时,默认是45%,就开始老年代并发标记
标记完成就开始混合回收过程,对于一个混合回收期,会从老年区间移动存活对象到原本空闲的区间,这些原本空闲的区间也就变成了老年区间,和年轻代不同,老年代的回收器不需要整个老年代被回收,一次只需要扫描回收一部分老年代的region即可,这里老年代和年轻代是一起被回收的
G1的记忆集
一个对象有可能被不同区域对象引用,例如老年代区的对象可能引用伊甸园区的对象,而想要回收伊甸园区的对象,如果还需要遍历老年代区会十分耗费时间,这个问题在其他的分代垃圾回收器中都存在,只是G1更突出,所以引入记忆集的概念(不止是G1有),jvm就是使用Remembered Set,来避免全局扫描
一个region不可能是孤立的,其中的对象可能被其他任意region中对象引用,而如果扫描整个堆区,会非常浪费时间,所以给每个region都维护了一个记忆集 Rset (Remembered Set)
- 每次Reference类型数据写操作时,都会产生一个Write Barrier (写屏障)暂时中断操作,
- 然后检查将要写入的引用指向的对象是否和该Reference类型数据在不同的region(其他收集器则是检查老年代对象是否引用了新生代的对象),相同的region,就不存在跨代引用,也就不需要记录了
- 如果不同,就把相关引用信息记录到引用指向对象的所在Region对象的记忆集中
- 在GC在枚举根节点时,枚举范围加入记忆集 Remembered Set 就可以保证不进行全局扫描,也不会遗漏
Young GC
Jvm启动的时候,G1就会准备好Eden区,程序在运行过程中会不断的创建对象到伊甸园区,当伊甸园区耗尽,就会启动年轻代的垃圾回收,幸存者区耗尽并不会触发Young GC
Young GC在回收时只会回收伊甸园区和幸存者区,YGC时,G1会停止应用程序的执行,创建回收集(Collection Set),会收集是指需要被回收的内存分段的集合,年轻代回收过程中的回收集包含伊甸园区和幸存者区的所有内存分段
YGC具体大概可以分为:
- 枚举根节点GC Roots 连同RSet 记录的外部引用作为扫描活动的入口
- 更新RSet ,完成后,RSet可以准确的反应老年代对所在内存分段中对象的引用(对于程序中的赋值操作,会保存在一个队列中,在进行年轻代回收的时候,需要根据这个队列来更新RSet,以保证准确性,然而这个操作是同步的,开销会比较大,所以让垃圾回收时再执行,而不是应用程序直接执行)
- 处理Rset ,被指向的伊甸园区对象认为是存活的
- 复制对象,将存活对象复制到对应的位置
- 处理软引用、弱引用、虚引用等,最终伊甸园区数据为空,GC就停止工作
并发标记过程
- 初始标记阶段:标记从根节点的可达对象,这个阶段是回触发STW,并且会进行一次年轻代的GC
- 根区域扫描:扫描幸存者区直接可达的老年代区域对象,并标记被引用的对象,这个过程必须要在YGC之前完成
- 并发标记:在整个堆中进行并发标记,会和应用程序并发执行,这个过程有可能会被YGC中断,在并发标记阶段,会计算每个区域的对象活性(也就是存活对象的比例),如果发现区域对象中的所有对象都是垃圾,那么这个区域会立即被回收
- 再次标记:用于修正并发标记,这个过程也是STW的,G1这里使用的算法是比CMS更快的:初始快照算法(SATB)
- 独占清理:计算各个区域的存活对象和GC回收比例,并进行排序,识别可以混合回收的区域,也是STW的,这个阶段并不会真的回收垃圾
- 并发清理阶段:识别并清理完全空闲的区域
混合回收
当越来越多的对象晋升到old region时,为了避免内存被耗尽,会触发混合回收,也就是Mixed GC,会回收整个Young Region 和一部分的Old region
- 并发标记结束以后,老年代中完全是垃圾的内存分段就被回收了,部分是垃圾的内存分段也被计算了出来,默认情况下,这些老年代的内存分段会分8次
- 混合回收会收集包括八分之一的老年代内存分段,伊甸园区内存分段和幸存者区内存分段,算法和年轻代的回收完全一致,只是多了老年代的内存分段
- 老年代中的内存分段默认8次会被回收,G1会优先回收垃圾多的内存分段,垃圾占比越高,就越容易被回收,并且也还有一个阈值会决定内存分段是否被回收,默认是65%,只有达到了这个指才会被回收,因为采用的是复制算法,存活对象太多,复制的成本越大
- 混合回收也不一定要进行8次,有一个允许堆内存浪费的阈值,默认为10%,意味着如果发现可以回收的垃圾占堆内存的比例低于10%,就不在进行混合回收。因为GC如果花费很长的时间能回收的内存却很少,就很不划算
Full GC
Full GC是单线程的,且会触发STW,性能很差,G1的初衷是要避免Full GC的
导致Full GC的原因有:
- 内存空间不足,例如老年代区不足以存放晋升的对象了
- 并发处理过程完成前空间就耗尽了
G1的优化建议
避免显式的设置年轻代的大小,固定年轻代大小会覆盖暂停时间目标
暂停时间不要太苛刻,G1的目标是90%的时间为应用程序时间,10%为垃圾回收时间,暂停时间太短,会导致可回收的region变少,反而会加速Full Gc 的到来
七种GC的总结
GC的选择推荐:
- 内存小于100M,或者单核、单机且没有停顿时间要求的,使用串行收集器
- 如果多CPU、需要高吞的,选择并行或者JVM自行选择
- 多CPU,追求低停顿时间,使用并发收集器,官方推荐G1
没有最好的垃圾收集器,只有最适合的场景
GC日志的参数
-XX:+PrintGC 输出GC日志
-XX:+PrintGCDetails 输出详细的GC日志
-XX:+PrintGCTimeStamps 输出GC的时间戳,以基准时间的形式
-XX:+PrintGCDateStamps 输出GC的时间戳,以日期形式
-XX:+PrintHeapAtGC 在GC的前后打印出堆的信息
-Xloggc:…/logs/gc.log 日志文件的输出路径
-verbose:gc 打开GC日志,只会显示总的GC堆变化
Full GC:
日志分析工具:GCeasy 官网:
https://gceasy.io/
垃圾回收器的后续发展
GC仍然处于告诉发展阶段,目前默认的G1 也在不断地改动,例如串行的full GC在jdk 10以后,已经是并行运行
Serial GC 虽然比较古老,但是简单的设计和实现未必过时,因为开销小,随着云计算的兴起,反而有了新的舞台
而CMS 因为算法的理论缺陷,虽然依然有很大的用户群体,但是jdk9就已经废弃,jdk14就移除掉了
(以上都是指的hotsopt)
open jdk12引入了 Shenandoah GC 主打低停顿时间 ,是第一款不是oracle开发的垃圾回收器,由RedHat(红帽)开发,Oracle拒绝把其加入到Oracle jdk ,所以目前只存在于Open jdk ,号称垃圾回收的暂停时间和堆的大小无关,但是在高负荷的情况下吞吐量下降比较明显
jdk11新引入了 ZGC : 可伸缩、低延迟的垃圾回收器 ,JDK15已经可以投入使用,目标和 Shenandoah 高度相似,都是想尽可能对吞吐量影响不大的前提下,实现任意堆大小都可以把垃圾收集的停顿时间限制在十毫秒以内 ,任然是基于region的内存布局
阿里巴巴基于G1算法推出了AliGC ,是一个面向大堆的GC