JDK17于2021年9月14日正式发布,这是继JDK11发布3年以来的又一个长支持(LTS)的java版本,其带来了14项JEP(JDK增强建议)更新,包括10个新功能以及2个功能的删除和2个功能的弃用。
本文将针对部分重点功能做介绍,其余没有说到的新功能请自行百度。
spring官方已经宣布,Spring Framework 6和Spring Boot 3将在2022年第四季度达到生产可用的发布标准,且运行时至少需要JDK17环境,因此我们为了以后技术的更新,需要了解和学习JDK17。
根据官方介绍,JDK17共有14个JEP:
其中 407 和 410 为删除,398 和 411 为弃用,其余都为新功能。
介绍这个更新之前,需要了解一些背景:
IEEE(电气和电子工程协会)为浮点计算和以各种格式存储浮点值制定了一种标准,包括单精度(32位,用于java)float、双精度(64位,用于java)double,另外一些硬件还提供扩展精度格式,
以提供更高的精度和更大的指数范围。在这样的架构下,使用扩展格式计算中间结果可能更有效,还能避免可能发生的舍入错误、上溢和下溢,但是会导致程序在此类架构上产生不同的输出,且x87浮点架构在x86机器上使用扩展精度代价很昂贵。
在JVM1.2之前浮点计算是严格要求的,也就是说浮点值的计算结果都必须表现的和标准一样,这使得在常见的x87浮点指令集平台上在需要的地方发生溢出的代价变得昂贵。
JVM1.2开始,默认情况下允许中间计算超出IEEE32位和64位格式相关标准指数范围,这样一来在x87平台上,溢出和下溢可能不会在预期的地方发生,而是产生重复性更低更具有意义的结果。
在没有上溢和下溢的情况下,如果需要得到重复的结果,java提供了一个当前已过时且未使用的关键字 strictfp ,strictfp修饰符确保浮点计算在所有平台发生上溢和下溢的地方相同,且中间值表示为IEEE单精度和双精度值。
该关键字可用于类、接口、非抽象方法。在方法上添加时内部所有计算都使用严格的浮点数学计算;在类上添加时类中所有计算都使用严格的浮点数学计算
例如:
public strictfp class SFPClass {
// ......
}
public class testClass {
public double strictfp calculate() {
// ......
}
}
由于当下支持SSE2指令集的x86处理器不再需要x87浮点指令集,因此JDK17再次严格要求所有浮点计算,恢复了1.2之前的语义。
JDK17之前的随机数API没有统一的接口,要实现自己的随机算法页比较麻烦。
JDK7为伪随机数生成器(PRNG)提供新的接口类型和实现,使程序使用各种PRNG算法更加容易,更好的支持流式编程。
提供了一个新的接口 RandomGenerator ,为所有PRNG算法提供统一的API,同时提供了一个新的类 RandomGeneratorFactory 来构造各种 RandomGenerator 实例。
例:
随机数生成器统一由 RandomGeneratorFactory 生成
public static void main(String[] args) {
//生成10个10以内的随机数
RandomGeneratorFactory<RandomGenerator> L128X1024MixRandom = RandomGeneratorFactory.of("L128X1024MixRandom");
RandomGenerator randomGenerator = L128X1024MixRandom.create(System.currentTimeMillis());
for (int i = 0; i < 10; i++) {
System.out.println(randomGenerator.nextInt(10));
}
}
7
8
0
7
4
0
6
0
8
1
通过 RandomGeneratorFactory.of(“随机数生成算法”) 方法获得生成器
RandomGeneratorFactory.of("L32X64MixRandom")
RandomGeneratorFactory.all() 方法可以获得所有随机数生成算法,输出所有随机算法名称:
public static void main(String[] args) {
RandomGeneratorFactory.all().forEach(e -> System.out.println(e.group() + "-" + e.name()));
}
LXM-L32X64MixRandom
LXM-L128X128MixRandom
LXM-L64X128MixRandom
Legacy-SecureRandom
LXM-L128X1024MixRandom
LXM-L64X128StarStarRandom
Xoshiro-Xoshiro256PlusPlus
LXM-L64X256MixRandom
Legacy-Random
Xoroshiro-Xoroshiro128PlusPlus
LXM-L128X256MixRandom
Legacy-SplittableRandom
LXM-L64X1024MixRandom
结果可以看到原来的 Random 算法也支持。
都是针对MacOS做的优化更新,不做过多介绍。
382是使用Apple Metal替代OpenGL渲染库,因为Apple在MacOS 10.14中弃用了OpenGL取而代之的是Metal框架。
391是将JDK移植到新架构的macOS/AArch64。
Applet API实际上是早就过时的,因为所有浏览器都已删除或计划删除java浏览器插件,API早在JDK9就已经被标记为启用,JDK17正式删除。
简而言之就是java内部的大部分类,除了关键的内部API,如sun.misc.Unsafe类,都进行强封装,默认情况下不允许开发人员利用反射等手段去访问内部非public的类、成员变量等,使java更加安全。
但是可以通过设置参数–add-export或–add-opens来指定哪些类可以被访问。
JDK17为switch支持模式匹配,当前还只是预览版,可能在后续JDK版本中会扶正,也可能删除。
switch在JDK12和switch13中连续得到了语法加强,此次更新更是完善了switch的使用,使其更加美观和简洁。
JDK11以前:
public static void JDK11_switch() {
String day = "MONDAY";
switch (day) {
case "MONDAY":
System.out.println(1);
break;
case "TUESDAY":
System.out.println(2);
break;
case "WEDNESDAY":
System.out.println(3);
break;
case "THURSDAY":
System.out.println(4);
break;
case "FRIDAY":
System.out.println(5);
break;
case "SATURDAY":
System.out.println(6);
break;
case "SUNDAY":
System.out.println(7);
break;
default:
System.out.println(0);
break;
}
}
JDK12简化了写法:
public static void JDK12_switch() {
String day = "MONDAY";
switch (day) {
case "MONDAY" -> System.out.println(1);
case "TUESDAY" -> System.out.println(2);
case "WEDNESDAY" -> System.out.println(3);
case "THURSDAY" -> System.out.println(4);
case "FRIDAY" -> System.out.println(5);
case "SATURDAY" -> System.out.println(6);
case "SUNDAY" -> System.out.println(7);
default -> System.out.println(0);
}
}
JDK13在简化的基础上增加了返回值:
public static void JDK13_switch() {
String day = "MONDAY";
int i = switch (day) {
case "MONDAY" -> 1;
case "TUESDAY" -> 2;
case "WEDNESDAY" -> 3;
case "THURSDAY" -> 4;
case "FRIDAY" -> 5;
case "SATURDAY" -> 6;
case "SUNDAY" -> 7;
default -> 0;
};
System.out.println(i);
}
从JDK17以前,switch不支持instanceof,如果有多个instanceof只能用if-else来表达:
public static void JDK17_before_instanceof_switch(Object o) {
//o instanceof Integer i 为JDK16新特性
if (o instanceof Integer i) {
System.out.println(i);
} else if (o instanceof Long l) {
System.out.println(l);
} else if (o instanceof Double d) {
System.out.println(d);
} else if (o instanceof String s) {
System.out.println(s);
} else {
System.out.println("UNKNOWN");
}
}
JDK17之后,switch开始支持instanceof,简化写法:
public static void JDK17_instanceof_switch(Object o) {
switch (o) {
case Integer i -> System.out.println(i);
case Long l -> System.out.println(l);
case Double d -> System.out.println(d);
case String s -> System.out.println(s);
default -> System.out.println("UNKNOWN");
}
}
RMI(远程方法调用)是实现RPC(远程过程调用)的java api。由于web技术的发展,有关过滤请求、身份认证、安全性等问题都已经在web服务领域得到解决,但这些机制都不在RMI的激活模型中,
即便是不要激活机制也不影响RMI的其他部分,还能减少维护成本,所以在JDK15中把RMI的激活机制标记为弃用,JDK17中正式删除。
密封类(Sealed Classes)是JDK15引入的,在JDK17正式转正。
密封类可以对继承或者实现他们的类进行限制。
例如:
Person类被 sealed 修饰,只允许(permits)Male和Female类继承,继承的类必须有 final 或者 no-sealed 来修饰。
Function接口被 sealed 修饰,只允许(permits)Male和Female类实现,实现的类必须有 final 或者 no-sealed 来修饰。
public static void main(String[] args) {
Male male = new Male();
male.eat();
Female female = new Female();
female.eat();
}
static sealed class Person permits Male, Female {
}
static final class Male extends Person implements Function {
@Override
public void eat() {
System.out.println("eat 1");
}
}
static non-sealed class Female extends Person implements Function {
@Override
public void eat() {
System.out.println("eat 2");
}
}
sealed interface Function permits Male, Female {
void eat();
}
AOT(Ahead Of Time 运行前编译)即jaotc,可以将java代码编译成二进制,JVM直接用这些二进制,而不是在运行时再花时间用JIT(Just in Time 即时编译)编译。
AOT优点在于占用内存低,启动速度快,缺点是在程序运行前编译会使程序的安装时间增加,牺牲java的一致性;JIT优点在于吞吐量高,可以根据当前情况实时编译生成最优机器指令,
缺点是编译需要占用运行时资源,启动速度较慢。
这是oracle自己做的实验性质的东西,由于使用实在太少,并且第三方有成熟的虚拟机(官方推荐 GraalVM ),所以就删除了。
SecurityManager 安全管理器是一个允许应用程序实现安全策略的类,但是这么多年很少被使用,为了减少维护工作,在JDK17中标记为弃用,将来会和Applet API一起删除。
这个新功能与JNI(java本地接口)有关,JNI允许java程序与程序以外的代码或数据做交互,常见的JNI即Thread类里的start()方法。JNI只能和以C和C++语言编写的库进行交互,
在通用性上有所不足,且JNI无法监控JVM以外的代码运行情况,外部可以通过getStaticField等函数访问JDk内部,甚至改变在final修饰下的字段值,本质上JNI是不安全的。
因此java开发人员觉得有一个更加安全易用的,基于java模型的JNI替代API,就有了这个孵化器,说不定在后面哪个版本的JDK中就孵化成功了。
这是一个矢量API,用于矢量计算,日常开发中不会用到。
这个新特性是为了反序列化的安全性考虑的,由于反序列化的数据内容决定了创建的对象、字段值以及他们之间的引用关系。多数情况下了流中的字节是从未知、不受信任或未经身份验证的客户端接收的,
因此可能会有利用反序列化攻击程序的情况发生,为了避免这类问题JDK9新增了反序列化过滤器,JDK17在此基础上又新增了基于特定上下文的反序列化过滤器,可以通过JVM范围的过滤器工厂配置特定于上下文和动态选择的反序列化过滤器。
有史以来最快的JAVA
JDK17号称有史以来最快的java,现在实际测试一下运行速度:
分别采用oracle JDK8,open JDK11,open JDK17做测试,对比一下差距。
测试环境硬件条件:Mac Book;CPU Intel Core i7 6核12线程;32G内存
java version "1.8.0_301"
Java(TM) SE Runtime Environment (build 1.8.0_301-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.301-b09, mixed mode)
openjdk version "11.0.2" 2019-01-15
OpenJDK Runtime Environment 18.9 (build 11.0.2+9)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.2+9, mixed mode)
openjdk version "17.0.1" 2021-10-19
OpenJDK Runtime Environment (build 17.0.1+12-39)
OpenJDK 64-Bit Server VM (build 17.0.1+12-39, mixed mode, sharing)
1、循环输出10000000(一千万)次
public static void main(String[] args) {
long begin = System.currentTimeMillis();
//循环一千万次
for (int i = 0; i < 10000000; i++) {
System.out.println("第" + i + "次循环");
}
long end = System.currentTimeMillis();
System.out.println("共耗时:" + (end - begin) + "毫秒");
}
JDK8:
...
第9999996次循环
第9999997次循环
第9999998次循环
第9999999次循环
共耗时:28593毫秒
JDK11:
...
第9999996次循环
第9999997次循环
第9999998次循环
第9999999次循环
共耗时:25196毫秒
JDK17:
...
第9999996次循环
第9999997次循环
第9999998次循环
第9999999次循环
共耗时:10722毫秒
不同版本对比:
JDk版本 | 耗时(毫秒) | 提升(相较于参与测试的上一版本) |
---|---|---|
1.8 | 28593 | - |
11 | 25196 | 11%+ |
17 | 10722 | 57%+ |
可以看到在循环输出一千万次测试中,JDK11比JDK8时间缩短了11%+,JDK17比JDK11时间缩短了57%+。
2、反射的方式给简单Bean的字段set值100000000(一亿)次
public class TestBean {
private String name;
private Integer age;
...
}
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
TestBean bean = new TestBean();
long begin = System.currentTimeMillis();
//循环100000000次
for (int i = 0; i < 100000000; i++) {
Class clazz = Class.forName("com.xia.java17.bean.TestBean");
Method method = clazz.getMethod("setAge", Integer.class);
method.invoke(bean,18);
}
long end = System.currentTimeMillis();
System.out.println("共耗时:" + (end - begin) + "毫秒");
}
JDK8:
共耗时:90207毫秒
JDK11:
共耗时:67667毫秒
JDK17:
共耗时:37551毫秒
不同版本对比:
JDk版本 | 耗时(毫秒) | 提升(相较于参与测试的上一版本) |
---|---|---|
1.8 | 90207 | - |
11 | 67667 | 25%- |
17 | 37551 | 45%- |
可以看到在反射循环一亿次set值测试中,JDK11比JDK8时间缩短了接近25%,JDK17比JDK11时间缩短了接近45%。
3、GC效率
public class TestBean {
private String name;
private Integer age;
...
}
public static void main(String[] args) {
List<TestBean> beans = new ArrayList<>();
//循环10000000次
for (int i = 0; i < 10000000; i++) {
TestBean bean = new TestBean("测试", 18);
beans.add(bean);
}
}
VM参数设置:-Xms1024m -Xmx1024m -XX:+PrintGC/-Xlog:gc -XX:+UseG1GC
使用默认的G1(低延迟垃圾回收器),这里只选取有用的日志内容
-Xms1024m 起始堆内存大小1024m
-Xmx1024m 最大堆内存大小1024m
-XX:+PrintGC/-Xlog:gc 输出GC日志 JDK11后-XX:+PrintGC参数弃用,建议使用-Xlog:gc
-XX:+UseG1GC 使用G1垃圾回收器
JDK8:
[GC pause (G1 Evacuation Pause) (young) 1024K->440K(1024M), 0.0015700 secs]
[GC pause (G1 Evacuation Pause) (young) 1464K->1026K(1024M), 0.0025434 secs]
[GC pause (G1 Evacuation Pause) (young) 2050K->1850K(1024M), 0.0027888 secs]
[GC pause (G1 Evacuation Pause) (young) 2874K->2970K(1024M), 0.0028041 secs]
[GC pause (G1 Evacuation Pause) (young) 3994K->3707K(1024M), 0.0018124 secs]
[GC pause (G1 Evacuation Pause) (young) 5356K->5142K(1024M), 0.0038780 secs]
[GC pause (G1 Evacuation Pause) (young) 7104K->7540K(1024M), 0.0050190 secs]
[GC pause (G1 Evacuation Pause) (young) 8564K->9203K(1024M), 0.0050022 secs]
[GC pause (G1 Evacuation Pause) (young) 11M->11M(1024M), 0.0054110 secs]
[GC pause (G1 Evacuation Pause) (young) 12M->13M(1024M), 0.0048150 secs]
......
[GC pause (G1 Evacuation Pause) (young) 435M->437M(1024M), 0.0072393 secs]
[GC pause (G1 Evacuation Pause) (young) 494M->494M(1024M), 0.0302716 secs]
[GC pause (G1 Evacuation Pause) (young) 496M->497M(1024M), 0.0059715 secs]
[GC pause (G1 Evacuation Pause) (young) 499M->500M(1024M), 0.0069864 secs]
[GC pause (G1 Evacuation Pause) (young) 502M->504M(1024M), 0.0062188 secs]
[GC pause (G1 Evacuation Pause) (young) 506M->506M(1024M), 0.0058288 secs]
[GC pause (G1 Evacuation Pause) (young) 508M->509M(1024M), 0.0059459 secs]
[GC pause (G1 Evacuation Pause) (young) 511M->512M(1024M), 0.0070257 secs]
[GC pause (G1 Evacuation Pause) (young) 514M->515M(1024M), 0.0055958 secs]
总耗时 864.5835 毫秒。
平均一次GC时间为 7.145318毫秒。
JDK11:
[0.115s][info][gc] GC(0) Pause Young (Normal) (G1 Evacuation Pause) 1M->0M(1024M) 1.342ms
[0.152s][info][gc] GC(1) Pause Young (Normal) (G1 Evacuation Pause) 1M->1M(1024M) 1.348ms
[0.156s][info][gc] GC(2) Pause Young (Normal) (G1 Evacuation Pause) 2M->1M(1024M) 1.937ms
[0.160s][info][gc] GC(3) Pause Young (Normal) (G1 Evacuation Pause) 2M->2M(1024M) 1.978ms
[0.163s][info][gc] GC(4) Pause Young (Normal) (G1 Evacuation Pause) 3M->3M(1024M) 1.571ms
[0.168s][info][gc] GC(5) Pause Young (Normal) (G1 Evacuation Pause) 5M->5M(1024M) 3.936ms
[0.175s][info][gc] GC(6) Pause Young (Normal) (G1 Evacuation Pause) 7M->7M(1024M) 5.235ms
[0.180s][info][gc] GC(7) Pause Young (Normal) (G1 Evacuation Pause) 8M->8M(1024M) 4.173ms
[0.184s][info][gc] GC(8) Pause Young (Normal) (G1 Evacuation Pause) 11M->11M(1024M) 3.195ms
[0.189s][info][gc] GC(9) Pause Young (Normal) (G1 Evacuation Pause) 12M->12M(1024M) 3.616ms
......
[1.345s][info][gc] GC(229) Pause Young (Normal) (G1 Evacuation Pause) 405M->405M(1024M) 4.301ms
[1.350s][info][gc] GC(230) Pause Young (Normal) (G1 Evacuation Pause) 406M->406M(1024M) 3.782ms
[1.356s][info][gc] GC(231) Pause Young (Normal) (G1 Evacuation Pause) 407M->407M(1024M) 5.548ms
[1.362s][info][gc] GC(232) Pause Young (Normal) (G1 Evacuation Pause) 408M->408M(1024M) 5.292ms
[1.367s][info][gc] GC(233) Pause Young (Normal) (G1 Evacuation Pause) 409M->409M(1024M) 3.786ms
[1.372s][info][gc] GC(234) Pause Young (Normal) (G1 Evacuation Pause) 410M->411M(1024M) 4.766ms
[1.377s][info][gc] GC(235) Pause Young (Normal) (G1 Evacuation Pause) 412M->412M(1024M) 4.031ms
[1.382s][info][gc] GC(236) Pause Young (Normal) (G1 Evacuation Pause) 413M->413M(1024M) 4.430ms
[1.388s][info][gc] GC(237) Pause Young (Normal) (G1 Evacuation Pause) 414M->414M(1024M) 5.147ms
[1.393s][info][gc] GC(238) Pause Young (Normal) (G1 Evacuation Pause) 415M->415M(1024M) 4.637ms
总耗时 989.583毫秒。
平均一次GC时间为 4.140514644毫秒。
JDK17:
[0.106s][info][gc] GC(0) Pause Young (Normal) (G1 Evacuation Pause) 1M->1M(1024M) 1.591ms
[0.111s][info][gc] GC(1) Pause Young (Normal) (G1 Evacuation Pause) 2M->2M(1024M) 1.644ms
[0.115s][info][gc] GC(2) Pause Young (Normal) (G1 Evacuation Pause) 2M->2M(1024M) 2.039ms
[0.118s][info][gc] GC(3) Pause Young (Normal) (G1 Evacuation Pause) 3M->3M(1024M) 1.325ms
[0.120s][info][gc] GC(4) Pause Young (Normal) (G1 Evacuation Pause) 4M->4M(1024M) 1.452ms
[0.126s][info][gc] GC(5) Pause Young (Normal) (G1 Evacuation Pause) 6M->5M(1024M) 3.681ms
[0.132s][info][gc] GC(6) Pause Young (Normal) (G1 Evacuation Pause) 7M->7M(1024M) 3.927ms
[0.136s][info][gc] GC(7) Pause Young (Normal) (G1 Evacuation Pause) 8M->8M(1024M) 3.406ms
[0.142s][info][gc] GC(8) Pause Young (Normal) (G1 Evacuation Pause) 11M->11M(1024M) 3.917ms
[0.147s][info][gc] GC(9) Pause Young (Normal) (G1 Evacuation Pause) 12M->12M(1024M) 4.013ms
......
[1.142s][info][gc] GC(228) Pause Young (Normal) (G1 Evacuation Pause) 405M->405M(1024M) 5.909ms
[1.149s][info][gc] GC(229) Pause Young (Normal) (G1 Evacuation Pause) 406M->407M(1024M) 5.323ms
[1.154s][info][gc] GC(230) Pause Young (Normal) (G1 Evacuation Pause) 408M->408M(1024M) 4.636ms
[1.161s][info][gc] GC(231) Pause Young (Normal) (G1 Evacuation Pause) 409M->409M(1024M) 5.259ms
[1.165s][info][gc] GC(232) Pause Young (Normal) (G1 Evacuation Pause) 410M->410M(1024M) 3.431ms
[1.170s][info][gc] GC(233) Pause Young (Normal) (G1 Evacuation Pause) 411M->411M(1024M) 3.796ms
[1.174s][info][gc] GC(234) Pause Young (Normal) (G1 Evacuation Pause) 412M->412M(1024M) 3.912ms
[1.179s][info][gc] GC(235) Pause Young (Normal) (G1 Evacuation Pause) 413M->413M(1024M) 4.170ms
[1.184s][info][gc] GC(236) Pause Young (Normal) (G1 Evacuation Pause) 414M->414M(1024M) 3.653ms
[1.187s][info][gc] GC(237) Pause Young (Normal) (G1 Evacuation Pause) 415M->415M(1024M) 2.958ms
总耗时 795.046毫秒。
平均一次GC时间为 3.340529412毫秒。
不同版本对比:
JDk版本 | 总耗时(毫秒) | 平均耗时(毫秒) | 总耗时提升(相较于参与测试的上一版本) | 平均耗时提升(相较于参与测试的上一版本) |
---|---|---|---|---|
1.8 | 864.5835 | 7.145318 | - | - |
11 | 989.583 | 4.140514644 | -14%+ | 42%+ |
17 | 795.046 | 3.340529412 | 20%- | 19%+ |
对比后发现JDK11的GC总耗时高于JDK8,可能是由于GC完全发生在新生代,G1的并行full GC特性没有体现出来,但是平均耗时比JDK8少42%,JDK17不管是总耗时还是平均耗时,时间都缩短了不少。
4、多线程执行10000000(一千万)次输出,并收集结果
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executor = Executors.newScheduledThreadPool(6);
List<Future> futures = new ArrayList<>();
long begin = System.currentTimeMillis();
//循环10000000次
for (int i = 0; i < 10000000; i++) {
String finalI = String.valueOf(i);
Future future = executor.submit(() -> {
System.out.println("这是第" + finalI + "次输出");
return 1;
});
futures.add(future);
}
for (Future future : futures) {
future.get();
}
executor.shutdown();
long end = System.currentTimeMillis();
System.out.println("共耗时:" + (end - begin) + "毫秒");
}
JDK8:
...
这是第9999998次输出
这是第9999989次输出
这是第9999978次输出
这是第9999999次输出
共耗时:67473毫秒
JDK11:
...
这是第9999968次输出
这是第9999951次输出
这是第9999999次输出
这是第9999993次输出
共耗时:61542毫秒
JDK17:
这是第9999996次输出
这是第9999993次输出
这是第9999990次输出
这是第9999999次输出
共耗时:40210毫秒
不同版本对比:
JDk版本 | 耗时(毫秒) | 提升(相较于参与测试的上一版本) |
---|---|---|
1.8 | 67473 | - |
11 | 61542 | 9%- |
17 | 40210 | 35%- |
对比后发现JDK17在多线程的表现上也是最好的。
综上4种测试都说明了JDK17的确是目前速度最快的JDK版本,值得我们考虑升级。
最后放上规划调度引擎 OptaPlanner 项目负责人对 JDK 17、JDK 16 和 JDK 11 的性能基准测试进行了对比
####总结
基于 OptaPlanner 用例,这些基准测试表明:
对于 G1GC(默认),Java 17 比 Java 11 快 8.66%,比 Java 16 快 2.41%
对于 ParallelGC,Java 17 比 Java 11 快 6.54%,比 Java 16 快 0.37%
Parallel GC 比 G1 GC 快 16.39%
简而言之,最新的 JDK 更快,高吞吐量垃圾回收器比低延迟垃圾回收器更快。