新增特性:
1、instanceof的模式匹配 (JDK 16)
这个特性增强了instanceof
运算符,允许在检查一个对象的类型时直接进行模式匹配,这使得代码更加简洁和易于理解。
通常我们使用instanceof时,一般发生在需要对一个变量的类型进行判断,如果符合指定的类型,则强制类型转换为一个新变量
private static void oldStyle(Object o) {
if (o instanceof Furit) {
Furit furit = (GrapeClass) o;
System.out.println("This furit is :" + furit.getName);
}
}
在使用instanceof的模式匹配后,上面的代码可进行简写。
private static void oldStyle(Object o) {
if (o instanceof Furit furit) {
System.out.println("This furit is :" + furit.getName);
}
}
可以将类型转换和变量声明都在if中处理。同时,可以直接在if中使用这个变量。
private static void oldStyle(Object o) {
if (o instanceof Furit furit && furit.getColor()==Color.RED) {
System.out.println("This furit is :" + furit.getName);
}
}
因为只有当instanceof的结果为true时,才会定义变量furit,所以这里可以使用&&,但是改为||就会编译报错。
2、record关键字(JDK 16)
Records是一种新的类类型,用于创建不可变的数据载体,它简化了数据类的创建,提供了更简洁的语法和自动生成的实现。
假设有一些场景我们只需要对Person的name和age属性进行打印,在有record之后将会变得非常容易。
public static void testPerson() {
// 使用record定义
record PersonRecord(String name,int age){}
PersonRecord p1Record = new PersonRecord("aaa", 18);
PersonRecord p2Record = new PersonRecord("bbb", 19);
System.out.println(p1Record);
System.out.println(p2Record);
}
record也可以单独定义作为一个文件定义,但是因为Record的使用非常紧凑,所以可以直接在需要使用的地方直接定义。
package com.java17.test;
/**
* PersonRecord定义
**/
public record PersonRecord(String name,int age) {
}
record同样也有构造方法,可以在构造方法中对数据进行一些验证操作。
public static void testPerson() {
record PersonRecord(String name, int age) {
// 构造方法
PersonRecord {
System.out.println("name " + name + " age " + age);
if (name == null) {
throw new IllegalArgumentException("姓名不能为空");
}
}
}
PersonRecord p1Record = new PersonRecord("aaa", 18);
PersonRecord p2Record = new PersonRecord("bbb", 19);
}
3、密封类 sealed class (JDK 17)
密封类提供了一种方式来限制一个类可以被哪些其他类继承,这有助于创建更安全和更易于维护的代码。
密封类可以控制有哪些类可以对超类进行继承,在Java 17之前如果我们需要控制哪些类可以继承,可以通过改变类的访问级别,比如去掉类的public,访问级别为默认。比如我们定义了如下的三个类:
public abstract class Furit {
}
public class Apple extends Furit {
}
public class Pear extends Furit {
}
那么我们可以在另一个包中写如下的代码:
private static void test() {
Apple apple = new Apple();
Pear pear = new Pear();
Fruit fruit = apple;
class Avocado extends Fruit {};
}
既可以定义Apple,Pear,也可以将apple实例赋值给Fruit,并且可以对Fruit进行继承。
如果我们不想让Fruit在包以外被扩展,在Java11版本中只能改变访问权限,去掉class的public修饰符。这样虽然可以控制被被继承,但是也会导致Fruit fruit = apple也编译失败。
在Java 17中通过密封类可以解决这个问题:
public abstract sealed class Furit permits Apple,Pear {
}
public non-sealed class Apple extends Furit {
}
public final class Pear extends Furit {
}
在定义Furit时通过关键字sealed声明为密封类,通过permits可以指定Apple,Pear类可以进行继承扩展。
子类需要指明它是final,non-sealed或sealed的。父类不能控制子类是否可以被继承。
private static void test() {
Apple apple = new Apple();
Pear pear = new Pear();
// 可以将apple赋值给Fruit
Fruit fruit = apple;
// 只能继承Apple,不能继承Furit
class Avocado extends Apple {};
}
4、macOS/AArch64部署 (JDK 17)
这意味着 Java 应用程序能够充分利用 M1 芯片(以及未来 Apple Silicon 芯片)的性能,包括更快的处理速度和更低的能耗。
在此之前之前,Java 在 Apple Silicon Macs 上运行需要通过 Rosetta 2 这个转换层,这可能会影响性能。
这个特性对于使用 Java 进行开发的 Mac 用户来说是一个巨大的福音。
5、文本块 (JDK 15)
文本块是一种新的多行字符串文字,它允许开发者以更易读的格式编写字符串,特别是对于正则表达式和HTML/XML内容。
通过文本块语法,类似的字符串处理则会方便很多;通过三个双引号可以定义一个文本块,并且结束的三个双引号不能和开始的在同一行,通过编写 """,来减少转义字符和换行符,达到简化代码和提高代码可读性的目的。代码如下:
public class TextBlocksExample {
public static void main(String[] args) {
String html = """
<html>
<head><title>Example</title></head>
<body>
<h1>Hello, world!</h1>
</body>
</html>
""";
System.out.println(html);
}
}
6、ZGC垃圾回收器从实验性功能更改为正式产品 (JDK 15)
ZGC垃圾回收器从实验性功能更改为正式产品功能,从JDK11引入以来,经过持续的迭代升级,目前已经足够稳定。需要手动开启,开启方式:-XX:+UseZGC,这是一个革命性的垃圾收集器,专为需要处理大规模堆内存(甚至达到数TB级别)的应用而设计,同时保持低延迟。
ZGC 的目标是在处理大规模堆时,将垃圾收集的暂停时间控制在10毫秒以内,这对于高吞吐量和低延迟的应用场景尤为重要。
压测效果
1、JDK17相对于JDK8和JDK11,所有垃圾回收器的性能都有很明显的提升,特别是稳定版的ZGC垃圾回收器
2、不论任何机器配置下,都推荐使用ZGC,ZGC的停顿时间达到亚毫秒级,吞吐量也比较高
压测场景
选择了不同配置的机器(2C4G、4C8G、8C16G),并分别使用JDK8、JDK11和JDK17进行部署和压测。
整个压测过程限时60分钟,用180个虚拟用户并发请求一个接口,每次接口请求都创建512Kb的数据。最终产出不同GC回收器的各项指标数据,来分析GC的性能提升效果。
以下是压测的性能情况:
压测图来源:https://segmentfault.com/a/1190000044386436
7、新的 macOS 渲染管道 (JDK 17)
这个特性为macOS上的Java应用程序提供了新的渲染管道,提高了图形性能。
这个新的渲染管道使用了 Apple 的 Metal API,它是 Apple 为 macOS 设备提供的新一代图形和计算 API,用以替代旧的 OpenGL。
新 macOS 渲染管道的主要优势包括:
-
更高的性能:Metal API 能够提供更高的图形渲染性能,因为它更接近硬件层面,减少了中间层的开销。
-
更好的资源利用:Metal 能够更有效地利用系统资源,包括 GPU 和内存,从而提高应用程序的整体性能。
-
现代图形特性:Metal 支持现代图形技术,如多线程渲染和高级着色语言,这为 Java 应用程序提供了更多的图形功能和更好的视觉效果。
-
更好的集成:由于 Metal 是 macOS 的原生图形 API,使用它可以让 Java 应用程序更好地集成到 macOS 系统中,包括对系统外观和行为的一致性支持。
8、Foreign Function & Memory API (孵化版本) (JDK 17)
这个孵化器API允许Java代码更高效地与本地代码交互,提供了对本地内存的直接访问,这对于性能敏感型应用非常有用。
Foreign Function & Memory API 的主要特点:
-
直接内存访问:API 允许 Java 代码直接访问本地内存,这意味着可以绕过 Java 堆,直接在本地内存中分配和操作数据。这可以减少垃圾收集的开销,并提高内存操作的效率。
-
与本地函数的互操作性:API 提供了一种机制,允许 Java 代码直接调用本地库中的函数。这使得 Java 应用程序能够利用现有的本地库,而无需通过 JNI(Java Native Interface)或其他间接方式。
-
类型安全:API 设计为类型安全的,这意味着它能够确保在 Java 代码和本地代码之间传递的数据类型是正确的。这有助于减少因类型不匹配而导致的错误和崩溃。
-
内存管理:API 提供了一套内存管理工具,允许 Java 代码分配、释放和管理本地内存。这为开发者提供了更多的控制权,同时也要求开发者更加小心地管理内存,以避免内存泄漏。
如何使用 Foreign Function & Memory API:
由于 Foreign Function & Memory API 是一个孵化特性,因此在 JDK 17 中可能还需要一些额外的配置来启用它。以下是一些基本的使用示例:
import jdk.incubator.foreign.*;
// 假设我们有一个本地库中的函数,其签名为 "int add(int, int)"
MemorySession session = MemorySession.openLogical();
FunctionDescriptor descriptor = FunctionDescriptor.of(Widen.class, Int.class, Int.class);
// 加载本地库
Linker linker = Linker.nativeLinker();
MemoryAddress addFunctionAddress = linker.lookup("add").get();
// 创建一个直接调用本地函数的 FunctionHandle
FunctionHandle addFunction = session.downcallHandle(addFunctionAddress, descriptor);
// 调用本地函数
int result = addFunction.invokeExact(2, 3);
System.out.println("Result: " + result);
9、向量 API (二次孵化版本) (JDK 17)
向量API的第二次孵化,提供了对SIMD(单指令多数据)指令的支持,这可以显著提高某些类型的计算性能。
向量 API 的主要特点:
-
性能提升:通过利用 SIMD 指令,向量 API 能够提高数据处理的速度,因为它允许单个 CPU 指令同时处理多个数据。
-
硬件加速:向量 API 能够利用现代处理器的 SIMD 功能,如 Intel 的 SSE 或 AVX,以及 ARM 的 NEON 技术,从而加速计算。
-
安全性:与直接使用 SIMD 指令集相比,向量 API 提供了更安全的编程模型,因为它抽象了底层硬件细节,并提供了跨平台的可移植性。
-
易用性:向量 API 提供了一种更高级、更易于使用的编程接口,使得开发者可以更容易地编写能够利用 SIMD 功能的代码。
如何使用向量 API:
由于向量 API 是一个孵化特性,因此在 JDK 17 中可能需要一些额外的配置来启用它。以下是一些基本的使用示例:
import jdk.incubator.vector.*;
// 假设我们有一个浮点数数组,我们想计算它们的平方和
float[] values = {1.0f, 2.0f, 3.0f, 4.0f};
float sum = 0.0f;
try (var v = Vector.<float[], FloatVector>spec().matcher().best().create()) {
FloatVector v1, v2, v3, v4;
for (int i = 0; i < values.length; i += v.length()) {
v.load(values, i, v1 = v.rebase(0, i));
v.load(values, i + v.length(), v2 = v.rebase(v.length(), i));
v1.fma(v1, v1, v1);
v2.fma(v2, v2, v2);
sum += v1.reduceLanes(VectorOperators.ADD, 0);
sum += v2.reduceLanes(VectorOperators.ADD, 0);
}
}
System.out.println("Sum of squares: " + sum);
10、Switch表达式(JDK 14)
Switch 表达式扩展了传统的 Switch 语句,使其可以用于 lambda 表达式和方法引用中,提供了一种更简洁和表达力更强的方式来处理基于不同情况的逻辑。
Switch 表达式的主要特性:
-
返回值:Switch 表达式可以返回一个值,这使得它在需要根据条件返回不同结果的场景中非常有用。
-
箭头函数:Switch 表达式使用箭头函数(->)来分隔条件和对应的代码块,这使得代码更加简洁。
-
模式匹配:在 JDK 14 中,Switch 表达式支持模式匹配,这允许在 Switch 语句中使用 instanceof 进行模式匹配。
-
更少的代码:Switch 表达式可以减少样板代码,使得代码更加简洁和易于阅读。
Switch 表达式的示例:
以下是使用 Switch 表达式重写传统 Switch 语句的示例:
public class SwitchExpressionExample {
public static void main(String[] args) {
String month = "MARCH";
int numDays = switch (month) {
case "JANUARY", "MARCH", "MAY", "JULY", "AUGUST", "OCTOBER", "DECEMBER" -> 31;
case "APRIL", "JUNE", "SEPTEMBER", "NOVEMBER" -> 30;
case "FEBRUARY" -> 28;
default -> throw new IllegalArgumentException("Invalid month");
};
System.out.println(month + " has " + numDays + " days.");
}
}
在这个示例中,switch
表达式用于根据月份返回对应的天数。如果月份是 "FEBRUARY",则返回 28 天;如果是其他月份,则根据月份的列表返回 30 或 31 天。
11、有用的NullPointerExceptions(JDK 14)
在 Java 中,NullPointerException
是一种常见的运行时异常,它发生在尝试使用 null
引用调用实例方法、访问或修改实例字段或数组元素时。
在 JDK14 之前,当 NullPointerException
发生时,异常堆栈通常只显示 null
引用被解引用的位置,而不显示 null
引用本身的位置。这使得开发者难以追踪 null
值的来源,从而增加了调试的难度。
JDK14 通过以下方式改进了 NullPointerException
:
-
改进的异常消息:当
NullPointerException
发生时,JVM 会尝试提供更详细的信息,包括导致异常的变量名(如果可用)和null
值被解引用的位置。 -
源码引用:如果可能,JVM 会提供源码行号和引用
null
值的变量名,这有助于快速定位问题。 -
更好的调试体验:改进的异常消息使得开发者能够更快地理解问题所在,从而缩短调试时间。
示例
在 JDK14 之前,NullPointerException
的堆栈像这样:
java.lang.NullPointerException
at MyClass.doSomething(MyClass.java:10)
而在 JDK14 之后,堆栈会显示为:
java.lang.NullPointerException: MyObject.myField
at MyClass.doSomething(MyClass.java:10)
at Main.main(Main.java:4)
在这个改进中MyObject.myField
指明了哪个字段是 null
,并且提供了发生异常的源码位置。
12、日期周期格式化 (JDK17)
在Java 17中添加了一个新的模式B,用于格式化DateTime,它根据Unicode标准指示一天时间段。
示例:使用默认的英语语言环境,打印一天的几个时刻
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("B");
System.out.println(dtf.format(LocalTime.of(8, 0)));
System.out.println(dtf.format(LocalTime.of(13, 0)));
System.out.println(dtf.format(LocalTime.of(20, 0)));
System.out.println(dtf.format(LocalTime.of(23, 0)));
System.out.println(dtf.format(LocalTime.of(0, 0)));
输出结果:
in the morning
in the afternoon
in the evening
at night
midnight
如果是中文语言环境,则输出结果为:
上午
下午
晚上
晚上
午夜
13、精简数字格式化支持(JDK17)
在NumberFormat中添加了一个工厂方法,可以根据Unicode标准以紧凑的、人类可读的形式格式化数字。
示例:SHORT格式如下所示:
NumberFormat fmt = NumberFormat.getCompactNumberInstance(Locale.ENGLISH, NumberFormat.Style.SHORT);
System.out.println(fmt.format(1000));
System.out.println(fmt.format(100000));
System.out.println(fmt.format(1000000));
输出格式为:
1K
100K
1M
LONG格式如下所示:
fmt = NumberFormat.getCompactNumberInstance(Locale.ENGLISH, NumberFormat.Style.LONG);
System.out.println(fmt.format(1000));
System.out.println(fmt.format(100000));
System.out.println(fmt.format(1000000));
输出结果为:
1
thousand
100
thousand
1
million
14、增强随机数生成器(JDK 17)
JDK17 引入了对 Java 伪随机数生成器(PRNG)的增强,旨在提供更好的随机性和更快的性能。在 JDK 17 中,这些增强被正式推出,作为 Java 核心库的一部分。
增强伪随机数生成器的主要特点:
-
更新的实现:Java 的伪随机数生成器使用了新的算法实现,比如
Xoroshiro128+
和SplittableRandom
,这些算法在统计随机性测试中表现更好,并且提供了更好的性能。 -
更快的性能:新的 PRNG 实现针对现代处理器进行了优化,能够提供更快的随机数生成速度。
-
更好的随机性:新的算法提供了更好的随机性,这对于需要高质量随机数的应用场景非常重要,如加密、模拟和游戏开发。
-
向后兼容:尽管引入了新的 PRNG 实现,但
java.util.Random
类仍然保持向后兼容,以确保现有代码的正常运行。
如何使用增强的伪随机数生成器:
在 JDK 17 中,你可以使用新的 Xoroshiro128PlusRandom
类来生成高质量的随机数。以下是一个使用示例:
import java.util.Random;
public class EnhancedPRNGExample {
public static void main(String[] args) {
// 使用新的 Xoroshiro128PlusRandom 算法生成随机数
Random random = new Random(new SplittableRandom().nextLong());
// 生成随机整数
int randomInt = random.nextInt();
System.out.println("Random Int: " + randomInt);
// 生成随机浮点数
double randomDouble = random.nextDouble();
System.out.println("Random Double: " + randomDouble);
}
}
在这个示例中,我们使用了 SplittableRandom 类的 nextLong() 方法来生成一个长整型的随机数,然后使用这个随机数作为种子来创建一个新的 Random 实例。
此外,如果你需要生成大量随机数,或者需要并行生成随机数,SplittableRandom 类提供了一种有效的方式来实现这一点,而不会降低随机性。
15、Edwards-Curve数字签名算法(EdDSA)(JDK 15)
这是一种基于 Edwards 曲线的现代数字签名算法。EdDSA 被认为是一种安全、高效且易于实现的签名方案,它提供了很好的安全性和性能,特别是在处理大量数据时。
EdDSA 的主要特点:
-
安全性:EdDSA 提供了强大的安全性,特别是在抵抗侧信道攻击和相关密钥攻击方面表现出色。
-
效率:EdDSA 的签名和验证速度快,计算效率高,这使得它在性能要求较高的应用中非常有用。
-
简洁性:EdDSA 的算法实现相对简单,这降低了实现错误的可能,并且便于审核和验证。
-
灵活性:EdDSA 支持多种曲线,包括 Curve25519 和 Curve448,这些曲线已经被广泛认为是安全的。
如何使用 EdDSA:
以下是如何在 Java 中使用 EdDSA 的示例:
import jdk.incubator.security.signature.EdDSASigner;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Signature;
public class EdDSAExample {
public static void main(String[] args) throws Exception {
// 生成密钥对
KeyPairGenerator kpg = KeyPairGenerator.getInstance("Ed25519");
KeyPair kp = kpg.generateKeyPair();
// 创建签名对象
Signature signature = Signature.getInstance("Ed25519");
// 初始化签名对象
signature.initSign(kp.getPrivate());
// 待签名的数据
byte[] data = "Hello, World!".getBytes();
// 签名数据
signature.update(data);
byte[] signed = signature.sign();
// 验证签名
signature.initVerify(kp.getPublic());
signature.update(data);
boolean valid = signature.verify(signed);
System.out.println("Signature valid: " + valid);
}
}
在这个示例中,我们首先生成了一个 Ed25519 密钥对,然后使用私钥对一段数据进行签名,并使用公钥验证签名的有效性。
16、重新实现旧的Socket API(JDK 13)
重新实现 Socket
API 的主要目标:
-
提高性能:通过减少系统调用和优化缓冲区管理,提升
Socket
的网络性能。 -
减少内存占用:优化内部缓冲区的使用,减少因临时缓冲区分配导致的内存占用。
-
减少延迟:通过减少系统调用和优化缓冲区操作,降低网络通信的延迟。
-
提高可维护性:通过重构代码,使得
Socket
的维护和更新变得更加容易。
如何使用新的 Socket
实现:
在 JDK 13 及更高版本中,开发者可以通过添加系统属性来启用新的 Socket
实现:
java -Dsun.net.useExclusiveBind=true -jar your-application.jar
或者在代码中动态设置
java.security.Security.setProperty("socketUseExclusiveBind", "true");
设置这个属性后,JVM 将使用新的 Socket
实现来处理 TCP 通信。
17、打包工具(JDK 16)
这个工具提供了一种标准化的方法来打包 Java 应用程序,使其可以作为一个独立的、可执行的包进行分发。
打包工具的主要特点:
-
简化的打包过程:提供了一种简单的方式来打包 Java 应用程序,包括应用的所有依赖项。
-
跨平台支持:支持在不同的操作系统上打包和运行 Java 应用程序,包括 Windows、Linux 和 macOS。
-
可执行的分发包:生成的包可以是一个可执行的 JAR 文件或一个包含所有必需组件的目录。
-
支持模块化:支持基于模块的 Java 应用程序,使得模块化应用的打包变得更加容易。
如何使用打包工具:
在 JDK 16 中,可以使用 jpackage
工具来打包 Java 应用程序。以下是一些基本的命令行示例:
打包为可执行 JAR 文件:
jpackage --type jar --input input_dir --output output_dir --name myapp --main-jar myapp.jar --main-class com.example.Main
打包为原生安装程序:
jpackage --type windows --input input_dir --output output_dir --name myapp --main-jar myapp.jar --main-class com.example.Main
在这个示例中,--type
参数指定了打包类型(例如 jar
或 windows
),--input
指定了包含应用程序文件的目录,--output
指定了输出目录,--name
指定了应用程序的名称,--main-jar
指定了包含主类的 JAR 文件,--main-class
指定了应用程序的主类。
注意事项:
-
jpackage
工具在 JDK 16 中是正式功能,但在 JDK 17 中得到了进一步的改进和扩展。 -
生成的安装程序的外观和行为可能会根据选择的打包类型和目标操作系统而有所不同。
-
jpackage
工具可以配置许多其他选项,包括图标、启动器名称、版本信息等。
弃用特性:
1、弃用并禁用偏向锁定(JDK 15)
偏向锁定是一种优化技术,它旨在减少无竞争锁定的开销。这项技术假设一个线程在没有其他线程竞争的情况下,可以持续拥有某个对象的锁,从而避免了在获取锁时执行原子操作的开销。
然而,随着时间的推移,偏向锁定带来的性能提升不再明显。许多现代应用程序使用的是 Java 1.2 引入的非同步集合(如 HashMap
和 ArrayList
),或者 Java 5 引入的并发数据结构,这些数据结构在没有不必要同步的情况下性能更高。此外,围绕线程池队列和工作线程构建的应用程序,在禁用偏向锁定时通常表现更好。
偏向锁定还为同步系统引入了大量复杂的代码,这不仅影响了 HotSpot 虚拟机的其他组件,也增加了理解代码的难度,并阻碍了对同步系统的重构。
在 JDK 15 中,尽管偏向锁定默认被禁用,但用户可以通过 -XX:+UseBiasedLocking
JVM 参数手动启用它。不过,这样做会收到一条弃用警告。与偏向锁定相关的其他 JVM 参数也将被弃用,并在将来的版本中删除。
2、弃用ParallelScavenge + SerialOld GC组合(JDK 14)
这个 GC 组合将并行年轻代 GC(ParallelScavenge)与串行老年代 GC(SerialOld)配对使用。这种组合在实际应用中较为罕见,因为它适用于具有非常大年轻代和非常小老年代的特定部署场景。在这种情况下,由于老年代较小,完整的收集暂停时间可能是可以接受的。然而,这种部署策略非常危险,因为年轻代中对象活跃度的轻微变化可能导致 OutOfMemoryException
,因为老年代比年轻代小得多。与年轻代和老年代都使用并行 GC 算法相比,这种组合的唯一优点是总内存使用量略低,但这种优势(最多约为 Java 堆大小的 3%)并不足以抵消维护此 GC 组合的成本。
任何显式使用 -XX:+UseParallelOldGC
选项的配置都会显示弃用警告。当 -XX:+UseParallelOldGC
独立使用(不带 -XX:+UseParallelGC
)来选择并行年轻代和老年代 GC 算法时,将会显示警告。在没有弃用警告的情况下选择并行年轻代和老年代 GC 算法的唯一方法是仅在命令行上指定 -XX:+UseParallelGC
。
现有的名为“Parallel”的收集器结合了并行年轻代和并行老年代算法,具有几乎相同的行为,应该是直接替代品。因此,对于使用 ParallelScavenge + SerialOld GC 组合的用户来说,建议迁移到 Parallel GC 组合,以避免未来的兼容性问题和性能风险。
移除特性:
1、移除并发标记清除(CMS)垃圾收集器(JDK 14)
CMS 垃圾收集器自 JDK 9 起已被标记为弃用(deprecated),并在 JDK 14 中被正式移除。CMS 垃圾收集器的主要问题是它会产生大量内存碎片,对 CPU 资源非常敏感,并且无法处理浮动垃圾。这些问题可能导致性能下降,甚至在某些情况下触发 Full GC,导致应用程序停顿时间变长。
移除 CMS 垃圾收集器的原因包括:
- 没有可信的贡献者愿意承担 CMS 的维护工作。
- 引入了新的垃圾收集器 ZGC 和 Shenandoah,以及对 G1 垃圾收集器的进一步改进,这些收集器的性能已经足够好,使得 CMS 变得不那么必要。
- 预计未来对现有收集器的改进将进一步减少对 CMS 的需求。
在 JDK 14 中,尝试使用 CMS 垃圾收集器将会导致以下警告信息,并使用默认的垃圾收集器继续执行:
Java HotSpot(TM) 64-Bit Server VM warning: Ignoring option UseConcMarkSweepGC; \
support was removed in <version>
对于之前依赖 CMS 垃圾收集器的用户,建议迁移到 G1 或其他可用的垃圾收集器。G1 垃圾收集器自 JDK 6 以来就被视为 CMS 的预期继承者,并且在 JDK 9 时成为默认的垃圾收集器。G1 通过将堆内存划分为多个区域并优先收集垃圾最多的区域,从而在不牺牲太多吞吐量的情况下,实现了更好的停顿时间控制。
此外,JDK 14 还引入了对 ZGC 垃圾收集器的支持,使其可以在 macOS 和 Windows 上使用,这为需要低延迟垃圾收集的应用程序提供了更多的选择。ZGC 旨在在大堆内存和低延迟要求的场景下提供更好的性能,其目标是实现在任意堆内存大小下都能将垃圾收集的停顿时间限制在十毫秒以内。
2、移除Pack200工具和API(JDK 14)
Pack200 是一种压缩方案,用于压缩 JAR 文件,它在 Java SE 5.0 中 引入。它的目的是减少 Java 应用程序打包、传输和交付的磁盘和带宽需求。开发者使用 pack200
和 unpack200
工具来压缩和解压缩他们的 JAR 文件,同时 java.util.jar
包中提供了相应的 API 支持。
移除 Pack200 的原因主要包括三点:
- 随着网络速度的提升和存储成本的降低,对极端压缩技术的需求减少。
- 新的打包技术(如
jlink
和jmod
)的出现,以及 Java 模块系统的引入,使得 Pack200 的应用场景变得有限。 - Pack200 的维护成本较高,且与其紧密耦合的类文件格式和 JAR 文件格式都已发生了变化,这使得 Pack200 的维护变得更加困难。
对于之前依赖 Pack200 工具和 API 的开发者,建议迁移到其他工具,如 jlink
工具,或者使用 jpackage
工具来创建具有优化外形的特定于应用程序的运行时。这些工具提供了更现代的解决方案,以满足当前和未来的需求。