-
JVM简明笔记4:垃圾回收
1 垃圾回收相关算法
垃圾回收器首先要做的就是,判断一个对象是存活状态还是死亡状态,死亡的对象将会被标识为垃圾数据并等待收集器进行清除。
- 判断一个对象是否为死亡状态的常用算法有两个:引用计数器算法 、可达性分析算法。
- 垃圾回收的常见算法有以下几个:标记-清除算法、标记-复制算法、标记-整理算法。
1.1 引用计数算法(Reference Counting)
在创建对象时关联一个与之相对应的计数器,当此对象被使用时加 1,相反销毁时 -1。当此计数器为 0 时,则表示此对象未使用,可以被垃圾收集器回收。
引用计数算法的优缺点很明显,其优点是垃圾回收比较及时,实时性比较高,只要对象计数器为 0,则可以直接进行回收操作;而缺点是无法解决循环引用的问题。
循环引用示例:
public class RCTest {
static class Test {
public Test object = null;
}
public static void main(String[] args) {
Test object1 = new Test();
Test object2 = new Test();
object1.object = object2;
object2.object = object1;
object1 = null;
object2 = null;
}
}
1.2 可达性分析算法(Reachability Analysis)
指从对象的起点(GC Roots)开始向下搜索,如果对象到 GC Roots 没有任何引用链相连时,也就是说此对象到 GC Roots 不可达时,则表示此对象可以被垃圾回收器所回收。
在Java语言中,可作为GC Roots的对象包括下面几种:
- 栈帧中的局部变量表中的 reference 引用所引用的对象
- 方法区中 static 静态引用的对象
- 方法区中 final 常量引用的对象
- 本地方法栈中 JNI(即Native方法) 引用的对象
- Java虚拟机内部的引用,如基本数据类型对应的 Class 对象,一些常驻的异常对象(比如 NullPointExcepiton、OutOfMemoryError) 等,还有系统类加载器
- 所有被同步锁持有的对象,比如被 synchronized 持有的对象
- 反映Java虚拟机内部情况的 JMXBean、 JVMTI 中注册的回调、 本地代码缓存等
finalize()
当使用可达性分析判断一个对象不可达时,并不会直接标识这个对象为死亡状态,而是先将它标记为“待死亡”状态再进行一次校验。
校验的内容就是此对象是否重写了 finalize() 方法,如果该对象重写了 finalize() 方法,那么这个对象将会被存入到 F-Queue 队列中,等待 JVM 的 Finalizer 线程去执行重写的 finalize() 方法,在这个方法中如果此对象将自己赋值给某个类变量时,则表示此对象已经被引用了。因此不能被标识为死亡状态,其他情况则会被标识为死亡状态。
四种引用类型
- 强引用(Strong Reference):强引用是使用最普遍的引用。如果一个对象具有强引用,即便发生OOM那垃圾回收器绝不会回收它。
- 软引用(Soft Reference):如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
- 弱引用(Weak Reference):用来描述那些非必须对象, 但是它的强度比软引用更弱一些, 被弱引用关联的对象只能生存到下一次垃圾收集发生为止。当垃圾收集器开始工作, 无论当前内存是否足够, 都会回收掉只 被弱引用关联的对象。 在JDK 1.2版之后提供了 WeakReference 类来实现弱引用。 弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
- 虚引用(Phantom Reference):如果一个对象仅持有虚引用,在任何时候都可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用必须和引用队列 (ReferenceQueue)联合使用。
1.3 标记-清除算法(Mark-Sweep)
标记-清除算法属于最早的垃圾回收算法,它是由标记阶段和清除阶段构成的。标记阶段会给所有的存活对象做上标记,而清除阶段会把没有被标记的死亡对象进行回收。而标记的判断方法就是前面讲的引用计数算法和可达性分析算法。
问题: 产生内存空间的碎片化,标记-清除算法执行完成之后会产生大量的不连续内存,这样当程序需要分配一个大对象时,因为没有足够的连续内存而导致需要提前触发一次垃圾回收动作。
1.4 标记-复制算法(Mark-Copy)
将内存分为大小相同的两块区域,每次只使用其中的一块区域,这样在进行垃圾回收时就可以直接将存活的东西复制到新的内存上,然后再把另一块内存全部清理掉。
问题:内存的可用率大幅降低:虽然可以解决内存碎片的问题,但因为需要将内存分为大小相同的两块内存,那么内存的实际可用量其实只有原来的一半。
1.5 标记-整理算法(Mark-Compact)
由两个阶段组成的:标记阶段和整理阶段。标记阶段和标记-清除算法的标记阶段一样,整理阶段不是直接对内存进行清除,而是把所有存活的对象移动到内存的一端,然后把另一端的所有死亡对象全部清除。
2 垃圾回收器
HotSpot 中常使用的垃圾收集器主要包括 7 个:Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS 和 G1(Garbage First)收集器。
2.1 分代收集理论
收集器应该将 Java 堆划分出不同的区域,然后将回收对象依据其年龄(年龄即对象熬过垃圾收集过程的次数) 分配到不同的区域之中存储。
在 Java 堆划分出不同的区域之后,垃圾收集器可以每次只回收其中某一个或者某些部分的区域,所以有了 Minor GC 、Major GC、Full GC 这样的回收类型的划分;也能够针对不同的区域安排与里面存储对象存亡特征相匹配的垃圾收集算法——因而发展出了“标记-复制算法”“标记-清除算 法”“标记-整理算法”等针对性的垃圾收集算法。
2.2 垃圾收集器分类
- 串行回收器(Serial):串行垃圾回收是为单线程环境设计且只使用一个线程进行垃圾回收,会暂停所有的用户线程,不适合交互性强的服务器环境
- 并行回收器(Parallel):多个垃圾收集器线程并行工作,同样会暂停用户线程,适用于科学计算、大数据后台处理等多交互场景
- 并发回收器(CMS):用户线程和垃圾回收线程同时执行,不一定是并行的,可能是交替执行,可能一边垃圾回收,一边运行应用线程,不需要停顿用户线程,互联网应用程序中经常使用,适用对响应时间有要求的场景
- G1回收器:G1垃圾回收器将堆内存分割成不同的区域然后并发地对其进行垃圾回收
-
串行回收器:Serial、Serial Old
-
并行回收器:ParNew、Parallel Scavenge、Parallel Old
-
并发回收器:CMS
-
新生代垃圾收集器:Serial 、 ParNew 、Parallel Scavenge
-
老年代垃圾收集器:Serial Old 、 Parallel Old 、CMS
-
整堆收集器:G1、ZGC、Shenandoah
-
特殊:Epsilon 不进行垃圾回收
组合:
- Serial + Serial Old
- Serial + CMS
- ParNew + Serial Old
- ParNew + CMS
- Parallel Scavenge + Serial Old
- Parallel Scavenge + Parallel Old
- G1
- ZGC
- Epsilon
- Shenandoah
其中: Serial Old 作为 CMS 出现 Concurrent Mode Failure 失败的后备预案
JDK8 废弃:Serial + CMS、ParNew + Serial Old
JDK8 默认:Parallel Scavenge + ParallelOld
JDK9 移除:Serial + CMS、ParNew + Serial Old
JDK9 默认:G1
JDK11 新增:Epsilon (实验)
JDK11 新增:ZGC (实验)
JDK12 新增:Shenandoah(实验)
JDK13 更新:ZGC 支持的最大堆大小从 4TB 增加到 16TB
JDK14 更新:ZGC 支持在 Windows 上作为实验功能
JDK14 弃用:Parallel Scavenge + Parallel Old
JDK14 移除:CMS
JDK15 正式:ZGC 不再标记为实验功能可以在生产环境中使用
GC性能指标
- 吞吐量:即CPU用于运行用户代码的时间与CPU总消耗时间的比值(吞吐量 = 运行用户代码时间 / ( 运行用户代码时间 + 垃圾收集时间 ))。例如:虚拟机共运行100分钟,垃圾收集器花掉1分钟,那么吞吐量就是99%
- 暂停时间:执行垃圾回收时,程序的工作线程被暂停的时间
- 内存占用:Java 堆所占内存的大小
- 收集频率:垃圾收集的频次
2.3 Serial 收集器
单线程收集器,“单线程”的意义不仅仅说明它只会使用一个CPU或一个收集线程去完成垃圾收集工作;更重要的是它在垃圾收集的时候,必须暂停其他工作线程(Stop The World),直到垃圾收集完毕;
对于单CPU环境来说,由于Serial收集器没有线程间的交互,专心做垃圾收集自然可以做获得最高的垃圾收集效率
使用方式:-XX:+UseSerialGC
(图源自《深入理解Java虚拟机(第2版)》)
2.4 ParNew 收集器
ParNew 收集器实质上是 Serial 收集器的多线程并行版本,除了同时使用多条线程进行垃圾收集之外,其余的行为包括 Serial 收集器可用的所有控制参数、 收集算法、 Stop The World、 对象分配规则、 回收策略等都与 Serial 收集器完全一致。
ParNew 收集器在单 CPU 服务器上的垃圾收集效率绝对不会比 Serial 收集器高;但是在多 CPU 服务器上,效果会明显比 Serial 好。
使用方式:-XX:+UseParNewGC
设置线程数: XX:ParllGCThreads
(图源自《深入理解Java虚拟机(第2版)》)
2.5 Parallel Scavenge 收集器
和 ParNew 收集器类似,是一个新生代收集器。使用复制算法的并行多线程收集器。Parallel Scavenge 是 Java1.8 默认的收集器,特点是并行的多线程回收,以吞吐量(Throughput)优先。适合后台运算,交互不多的任务,如批量处理,订单处理,科学计算等。
- Parallel Scavenge 收集器的目标是达到一个可控制的吞吐量
- 自适应调节策略:自动指定年轻代、Eden、Suvisor区的比例
使用方式:-XX:+UseParallelGC
分别是控制最大垃圾收集停顿时间: -XX:MaxGCPauseMillis
吞吐量大小 -XX:GCTimeRatio
设置年轻代线程数 XX:ParllGCThreads
自适应调节年轻代、Eden、Suvisor区的比例 -XX:+UseAdaptiveSizePolicy
2.6 Serial Old 收集器
Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用标记-整理算法。 这个收集器的主要意义也是供客户端模式下的 HotSpot 虚拟机使用。
- 在 JDK1.5 及之前,与 Parallel Scavenge 收集器搭配使用(JDK1.6 后有 Parallel Old 收集器可搭配)
- 作为 CMS 收集器的后备预案,在并发收集发生 Concurrent Mode Failure 时使用
使用方式:-XX:+UseSerialGC
另外:Parallel Scavenge 收集器架构中本身有 PS MarkSweep 收集器来进行老年代收集,并非直接调用 Serial Old 收集器,但 PS MarkSweep 收集器与 Serial Old 的实现几乎是一样的,所以在官方的许多资料中都是直接以 Serial Old 代替 PS MarkSweep 进行讲解。
(图源自《深入理解Java虚拟机(第2版)》)
2.7 Parallel Old 收集器
Parallel Old 是 Parallel Scavenge 收集器的老年代版本,支持多线程并发收集,基于标记-整理算法实现。
JDK1.6 及之后用来代替老年代的 Serial Old 收集器;(在此之前,如果新生代选择了 Parallel Scavenge 收集器,老年代除了 Serial Old(PS MarkSweep) 收集器以外别无选择,其他表现良好的老年代收集器,如 CMS 无法与它配合工作。)
在Server模式,多CPU的情况下;在注重吞吐量以及CPU资源敏感的场景,就有了 Parallel Scavenge 加 Parallel Old 收集器的应用组合;
使用方式:-XX:+UseParallelOldGC
(图源自《深入理解Java虚拟机(第2版)》)
2.8 CMS 收集器
CMS(concurrent mark sweep)是以获取最短垃圾收集停顿时间为目标的收集器,CMS 收集器的关注点尽可能缩短垃圾收集时用户线程的停顿时间,停顿时间越短就越适合与用户交互的程序验,CMS 收集器使用的算法是标记-清除算法实现的;
CMS垃圾收集过程
1)初始标记(Initial-Mark)阶段:这个阶段程序所有的工作线程都将会因为 Stop-the-Wold 机制而出现短暂的的暂停,这个阶段的主要任务标记处 GC Roots 能够关联到的对象。一旦标记完成后就恢复之前被暂停的的所有应用。 由于直接关联对象比较小,所以这里的操作速度非常快。
2)并发标记(Concurrent-Mark)阶段:从 GC Roots 的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长,但是不需要暂停用户线程,用户线程可以与垃圾回收器一起运行。
3)重新标记(Remark)阶段:由于并发标记阶段,程序的工作线程会和垃圾收集线程同时运行或者交叉运行,因此为了修正并发标记期间因为用户继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间通常比初始标记阶段长一些,但也远比并发标记阶段时间短。
4)并发清除(Concurrent-Sweep)阶段: 此阶段清理删除掉标记判断已经死亡的对象,并释放内存空间。由于不需要移动存活对象,所以这个阶段可以与用户线程同时并发运行。
(图源自《深入理解Java虚拟机(第2版)》)
并发可达性分析与三色标记(Tri-color Marking)
可达性分析算法理论上要求全过程都基于一个能保障一致性的快照中才能够进行分析。 垃圾回收器的工作流程大体如下:
- 标记出哪些对象是存活的,哪些是垃圾(可回收);
- 进行回收(清除/复制/整理),如果有移动过对象(复制/整理),还需要更新引用。
三色标记(Tri-color Marking)作为工具来辅助推导,把遍历对象图过程中遇到的对象,按照“是否访问过”这个条件标记成以下三种颜色:
- 白色:尚未访问过。
- 黑色:本对象已访问过,而且本对象引用到的其他对象 也全部访问过了。
- 灰色:本对象已访问过,但是本对象引用到的其他对象尚未全部访问完。全部访问后,会转换为黑色。
假设现在有白、灰、黑三个集合(表示当前对象的颜色),其遍历访问过程为:
- 初始时,所有对象都在 【白色集合】中;
- 将GC Roots 直接引用到的对象 挪到 【灰色集合】中;
-
从灰色集合中获取对象:
- 将本对象 引用到的 其他对象 全部挪到 【灰色集合】中
- 将本对象 挪到 【黑色集合】里面
- 重复步骤3,直至【灰色集合】为空时结束
- 结束后,仍在【白色集合】的对象即为 GC Roots 不可达,可以进行回收
当 Stop The World 时,对象间的引用是不会发生变化的,可以轻松完成标记。 而当需要支持并发标记时,即标记期间应用线程还在继续跑,对象间的引用可能发生变化,多标和漏标的情况就有可能发生。
多标-浮动垃圾
假设已经遍历到E(变为灰色了),此时应用执行了 objD.fieldE = null
:
此刻之后,对象E/F/G是“应该”被回收的。然而因为 E 已经变为灰色了,其仍会被当作存活对象继续遍历下去。最终的结果是:这部分对象仍会被标记为存活,即本轮GC不会回收这部分内存。
这部分本应该回收但是没 有回收到的内存,被称之为“浮动垃圾”。浮动垃圾并不会影响应用程序的正确性,只是需要等到下一轮垃圾回收中才被清除。
漏标
假设GC线程已经遍历到E(变为灰色了),此时应用线程先执行了:
var G = objE.fieldG;
objE.fieldG = null; // 灰色E 断开引用 白色G
objD.fieldG = G; // 黑色D 引用 白色G
此时切回 GC 线程继续跑,因为 E 已经没有对 G 的引用了,所以不会将 G 放到灰色集合;尽管因为 D 重新引用了 G,但因为D已经是黑色了,不会再重新做遍历处理。
最终导致的结果是:G 会一直停留在白色集合中,最后被当作垃圾进行清除。这直接影响到了应用程序的正确性,是不可接受的。
漏标只有同时满足以下两个条件时才会发生:
- 灰色对象断开了白色对象的引用:即灰色对象原来成员变量的引用发生了变化
- 黑色对象重新引用了该白色对象:即黑色对象成员变量增加了新的引用
从代码的角度看:
var G = objE.fieldG; // 1. 读取对象 E 的成员变量 fieldG 的引用值(对象 G)
objE.fieldG = null; // 2. 对象 E 往其成员变量 fieldG 写入 null 值
objD.fieldG = G; // 3. 对象 D 往其成员变量 fieldG 写入 对象G
只要在上面这三步中的任意一步将对象 G 记录起来,然后作为灰色对象再进行遍历即可。
比如放到一个特定的集合,等初始的 GC Roots 遍历完(并发标记),该集合的对象遍历即可(重新标记)。
重新标记是需要 STW 的,因为应用程序一直在跑的话,该集合可能会一直增加新的对象,导致永远都跑不完。当然,并发标记期间也可以将该集合中的大部分先跑了,从而缩短重新标记STW的时间,这个是优化问题了。
CMS收集器三个缺点
-
CMS 收集器对 CPU 资源非常敏感。CMS 默认启动的回收线程数是
(处理器核心数量 + 3) / 4
- CMS 收集器无法处理浮动垃圾,可能出现 Concurrent Mode Failure 失败而导致另一次 Full GC 的产生
-
空间碎片:CMS是一款基于标记-清除算法实现的收集器,所有会有空间碎片的现象
-
开关参数
-XX:+UseCMS-CompactAtFullCollection
(默认是开启的,JDK 9开始废弃),用于在 CMS 收集器不得不进行 Full GC 时开启内存碎片的合并整理过程,由于这个内存整理必须移动存活对象,是无法并发的 -
另外一个参数
-XX:CMSFullGCsBeforeCompaction
(JDK 9开始废弃),这个参数的作用是要求 CMS 收集器在执行过若干次(数量由参数值决定) 不整理空间的 Full GC 之后,下一次进入 Full GC 前会先进行碎片整理(默认值为0,表 示每次进入Full GC时都进行碎片整理)。
-
开关参数
2.9 G1 收集器
- G1 GC 图例来源:Getting Started with the G1 Garbage Collector
- G1 GC 参考资料:Garbage First Garbage Collector Tuning
Garbage First是一款面向服务端应用的垃圾收集器,主要针对配备多核CPU及大容量内存的机器,以极高概率满足GC停顿时间的同时,还兼具高吞吐量的性能特征。
特点
- G1 把内存划分为多个独立的区域 Region
- G1 仍然保留分代思想,保留了新生代和老年代,但他们不再是物理隔离,而是一部分Region的集合
- G1 能够充分利用多CPU、多核环境硬件优势,尽量缩短 STW 的时间
- G1 整体整体采用标记整理算法,局部是采用复制算法,不会产生内存碎片
- G1 的停顿可预测,能够明确指定在一个时间段内,消耗在垃圾收集上的时间不超过设置时间
- G1 跟踪各个Region里面垃圾的价值大小,会维护一个优先列表,每次根据允许的时间来回收价值最大的区域,从而保证在有限事件内高效的收集垃圾
Region区域
G1 不再坚持固定大小以及固定数量的分代区域划分,而是把连续的 Java 堆划分为多个独立区域(Region),每一个 Region 都可以根据需要扮演新生代的 Eden 空间、 Survivor 空间、老年代空间。
① 使用G1收集器时,它将整个 Java 堆划分成约2048个大小相同的独立 Region 块,每个 Region 块大小根据堆空间的实际大小而定,为2的N次幂,即1MB,2MB,4MB,8MB,16MB,32MB。
② 虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region (不需要连续)的集合。通过Region的动态分配方式实现逻辑上的连续。
③ G1垃圾收集器还增加了一种新的内存区域,叫做 Humongous 内存区域。主要用于存储大对象,如果超过 1.5 个 Region,就放到 H 区。一般被视为老年代。
G1 GC过程
G1 提供了两种 GC 模式,Young GC 和 Mixed GC,两种均是完全 Stop The World 的。
- Young GC:选定所有年轻代里的 Region,通过控制年轻代的 Region 个数,即年轻代内存大小,来控制 Young GC 的时间开销。
- Mixed GC:选定所有年轻代里的 Region,外加根据 global concurrent marking 统计得出收集收益高的若干老年代 Region。在用户指定的开销目标范围内尽可能选择收益高的老年代 Region。
初始标记(Initial Mark,STW):和 CMS 一样只标记 GC Roots 直接关联的对象
并发标记(Concurrent Mark):进行 GC Roots Traceing 过程
最终标记(Remark,STW):修正并发标记期间,因程序运行导致发生变化的那一部分对象
筛选回收(Cleanup,STW):根据时间来进行价值最大化收集
(图源自《深入理解Java虚拟机(第2版)》)
G1 Young GC
- 执行 YoungGC 前:堆分为大约2000个区域。最小大小为 1Mb,最大大小为 32Mb。蓝色区域保存老年代对象,绿色区域保存年轻对象。
- 执行 YoungGC 时:将存活的对象(即复制或移动)到一个或多个幸存者区域。如果满足老化阈值,则某些对象将被提升到老年代区域。
- 执行 YoungGC 后:最近升级的对象以深蓝色显示。幸存者区域为绿色。
总结:
- 堆是单个内存空间,分为多个区域。
- 年轻代内存由一组非连续区域组成。
- 年轻一代的垃圾收集器或年轻的 GC 出现 STW 。将停止所有应用程序线程以进行操作。
- 年轻的 GC 使用多个线程并行完成。
- 将活动对象复制到新的幸存者或老年代的地区。
G1 Mix GC
- 初始标记阶段(Initial Marking,STW):存活的对象的初始标记背负在年轻的垃圾收集器上。在日志中,此标记为 GC pause (young)(inital-mark) 。
- 并发标记阶段(Concurrent Marking):如果找到空白区域(如“ X”所示),则在 Remark 阶段将其立即删除。另外,计算确定活跃度的信息。
- 最终标记阶段(Remark,STW):空区域将被删除并回收。现在可以计算所有区域的区域活跃度。
- 筛选回收阶段/复制清理阶段(Copying/Cleanup,STW): G1选择“活度”最低的区域,这些区域可以被最快地收集。然后与年轻的GC同时收集这些区域。这在日志中表示为[GC pause (mixed)] 。因此,年轻代和老年代都是同时收集的。
- 筛选回收阶段-(复制/清理)阶段之后:选定的区域已被收集并压缩为图中所示的深蓝色区域和深绿色区域。
总结
-
并发标记阶段
- 活动信息是在应用程序运行时同时计算的。
- 该活动信息标识在疏散暂停期间最适合回收的区域。
- 像 CMS 中没有清扫阶段。
-
最终标记阶段
- 使用开始快照(SATB)算法,该算法比 CMS 使用的算法快得多。
- 完全回收空区域。
-
筛选回收阶段
- 同时回收年轻一代和老一代。
- 老年代地区是根据其活跃度来选择的
2.10 ZGC、Epsilon、Shenandoah
Epsilon
JDK11 新增的GC,一个处理内存分配但不实现任何实际内存回收机制的GC,一旦可用的Java堆耗尽,JVM将关闭。如果有 System.gc()
调用,实际上什么也不会发生, 因为没有内存回收,这个实现可能会警告用户尝试强制GC是徒劳。
使用方式:-XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC
用途:
- 性能测试(它可以帮助过滤掉 GC 引起的性能假象)
-
内存压力测试(例如知道测试用例应该分配不超过 1GB 的内存,我们可以使用
-XX:+UnlockExperimentalVMOptions –XX:+UseEpsilonGC -Xmx1g
,如果程序有问题则程序会崩溃) - 非常短的 JOB 任务(GC 清理可能会让时间更长)
- VM 接口测试
- Last-drop 延迟&吞吐改进
ZGC
ZGC的设计目标是:支持 TB 级内存容量,暂停时间低(<10ms),对整个程序吞吐量的影响小于15%。 将来还可以扩展实现机制,以支持不少令人兴奋的功能,例如多层堆(即热对象置于 DRAM 和冷对象置于 NVMe 闪存),或压缩堆。
-
JDK11 新增:ZGC (实验)
-XX:+UnlockExperimentalVMOptions -XX:+UseZGC
- JDK13 更新:ZGC 支持的最大堆大小从 4TB 增加到 16TB
- JDK14 更新:ZGC 支持在 Windows 上作为实验功能
-
JDK15 正式:ZGC 不再标记为实验功能可以在生产环境中使用
-XX:+UseZGC
Shenandoah
JDK12 新增的GC,低暂停时间垃圾收集器(实验性)Shenandoah的暂停时间与堆大小无关,这意味着无论堆是200MB还是200GB,都将具有相同的一致暂停时间。它的 evacuation 阶段工作能通过与正在运行中 Java 工作线程同时进行(即并发,concurrent),从而减少 GC 的停顿时间。
使用方法: -XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC
某些 JDK 会在 build 时通过 --with-jvm-features=-shenandoahgc
来禁用 Shenandoah。
来源:
https://www.cnblogs.com/linweiwang/p/15501627.html