Java 17 于 2021年09月14日 正式发布,且为长期支持版本(Long-Term-Support - LTS)。
Spring 6.x 和 Spring Boot 3.x 最低支持的 Java 17
,这里敲黑板:Java 17 将是继 Java 8 以来最重要的长期支持版本。
下面为 Oracle Java SE 产品的一些关键产品日期示例(Oracle Java SE Product Releases):
从 Java 10 开始,引入了局部变量类型推断(Local Variable Type Inference)功能,它可以让我们使用关键字 var 声明局部变量(而不是实际类型),而由编译器根据变量初始化的值自动推断出类型。
// Java 8 传统变量声明方法
String str = "pointer";
// Java 10 使用类型推断的变量声明方法
var str = "pointer";
Java 11 允许在 lambda 表达式中使用 var 进行参数声明:
public class VarInLambdaExample {
public static void main(String[] args) {
IntStream.of(1, 2, 3, 5, 6, 7)
.filter((var i) -> i % 2 == 0)
.forEach(System.out::println);
}
}
从 Java 9 开始,G1
是默认的垃圾收集器。与 Parallel GC 相比,它减少了暂停时间,尽管它的总体吞吐量可能较低。G1 的更新包括将未使用的已提交内存返回给操作系统的能力(JEP 346)。
ZGC
垃圾收集器已在 Java 11 中引入,并已在 Java 15 (JEP 377) 中达到产品状态。它旨在进一步减少停顿。从 Java 13 开始,它也可以将未使用的已提交内存返回给操作系统 (JEP 351)。
JDK 14 中引入了 Shenandoah GC
,并在 Java 15 ( JEP 379) 中达到了产品状态。它旨在保持低暂停时间并独立于堆大小。
在 Java 8 中如果您没有手动更改 GC,您仍然使用 Parallel GC。简单地切换到 Java 17 可能会使您的应用程序运行得更快,并具有更一致的方法运行时间。切换到 ZGC 或 Shenandoah 可能会得到更好的结果。
最后,No-Op Garbage Collector(JEP 318),尽管它是一个实验性功能。这个垃圾收集器实际上不做任何工作,因此允许您精确测量应用程序的内存使用情况。如果您想保持尽可能低的内存操作吞吐量,则很有用。
JDK 17 之前,我们可以借助 Random、ThreadLocalRandom 和SplittableRandom 来生成随机数。不过,这 3 个类都各有缺陷,且缺少常见的伪随机算法支持。
Java 17 为伪随机数生成器 (PRNG) 提供新的接口类型和实现,包括可跳转的 PRNG 和另一类可拆分的 PRNG 算法 (LXM)。
PRNGopen in new window 用来生成接近于绝对随机数序列的数字序列。一般来说,PRNG 会依赖于一个初始值,也称为种子,来生成对应的伪随机数序列。只要种子确定了,PRNG 所生成的随机数就是完全确定的,因此其生成的随机数序列并不是真正随机的。
它为所有现有的和新的 PRNG 提供了一个统一的 API。 RandomGenerators 提供名为 ints、longs、doubles、nextBoolean、nextInt、nextLong、nextDouble 和 nextFloat 的方法,以及它们当前的所有参数的变化。从而更容易在应用程序中互换使用各种 PRNG 算法。例如:
public class RandomTest {
public static void main(String[] args) {
testRandomGenerator(new Random());
testRandomGenerator(new SplittableRandom());
testRandomGenerator(ThreadLocalRandom.current());
}
static void testRandomGenerator(RandomGenerator randomGenerator) {
IntStream ints = randomGenerator.ints(50, 0, 10);
int[] randoms = ints.toArray();
for (int i : randoms) {
System.out.println(i);
}
System.out.println("random count = " + randoms.length);
}
}
它用于定位和构造 RandomGenerator 实现的实例:
public class RandomTest {
public static void main(String[] args) {
testRandomGeneratorFactory("Random");
testRandomGeneratorFactory("L128X128MixRandom");
testRandomGeneratorFactory("Xoshiro256PlusPlus");
}
static void testRandomGeneratorFactory(String randomGeneratorName) {
RandomGeneratorFactory<RandomGenerator> factory = RandomGeneratorFactory.of(randomGeneratorName);
// 使用时间戳作为随机数种子
RandomGenerator randomGenerator = factory.create(System.currentTimeMillis());
// 生成随机数
val nextInt = randomGenerator.nextInt(10);
System.out.println(nextInt);
}
}
上述方法还支持如下LXM 系列 PRNG 算法:
以及广泛使用的 PRNG 算法:
Xoshiro256PlusPlus
Xoroshiro128PlusPlus
SplittableRandomGenerator 扩展了 RandomGenerator 并且还提供名为 split 和 splits 的方法。 可拆分性允许用户从现有的 RandomGenerator 生成一个新的 RandomGenerator,这通常会产生统计上独立的结果。
JumpableRandomGenerator 扩展了RandomGenerator 并且还提供名为 jump 和 jumps 的方法。 可跳跃性允许用户跳到中等数量的抽签。
LeapableRandomGenerator 扩展了 RandomGenerator 并且还提供方法名为leap和leaps的方法。 可跳跃性允许用户跳过大量的抽签。
ArbitrarilyJumpableRandomGenerator 扩展了 LeapableRandomGenerator 并且还提供了jump 和 jumps 的方法额外的变体,允许指定任意跳跃距离。
JEP 356: Enhanced Pseudo-Random Number Generators
Applet API 在 Java 17 进行删除。
Applet API 用于编写在 Web 浏览器端运行的 Java 小程序, 在 Java 9 时被标记弃用,但没有删除。
JEP 398: Deprecate the Applet API for Removal
Java 16 中,JEP 394 扩展了 instanceof 操作符以获取类型模式并执行类型匹配。这种适度的扩展可以简化熟悉的 instanceof-and-cast 习惯用法:
// Old code
if (o instanceof String) {
String s = (String)o;
... use s ...
}
// New code
if (o instanceof String s) {
... use s ...
}
Java 17 中对 switch 表达式进行了增强,其中包括对 switch 表达式的模式匹配进行了优化。这些改进可以让开发人员更方便地使用 switch 表达式来进行条件判断和分支控制。
// Old code
static String formatter(Object o) {
String formatted = "unknown";
if (o instanceof Integer i) {
formatted = String.format("int %d", i);
} else if (o instanceof Long l) {
formatted = String.format("long %d", l);
} else if (o instanceof Double d) {
formatted = String.format("double %f", d);
} else if (o instanceof String s) {
formatted = String.format("String %s", s);
}
return formatted;
}
// New code
static String formatterPatternSwitch(Object o) {
return switch (o) {
case Integer i -> String.format("int %d", i);
case Long l -> String.format("long %d", l);
case Double d -> String.format("double %f", d);
case String s -> String.format("String %s", s);
default -> o.toString();
};
}
对于 null
值的判断也进行了优化:
// Old code
static void testFooBar(String s) {
if (s == null) {
System.out.println("oops!");
return;
}
switch (s) {
case "Foo", "Bar" -> System.out.println("Great");
default -> System.out.println("Ok");
}
}
// New code
static void testFooBar(String s) {
switch (s) {
case null -> System.out.println("Oops");
case "Foo", "Bar" -> System.out.println("Great");
default -> System.out.println("Ok");
}
}
JEP 406: Pattern Matching for switch (Preview)
删除远程方法调用 (RMI) 激活机制,同时保留 RMI 的其余部分。RMI 激活机制已过时且不再使用。
JEP 407: Remove RMI Activation
密封类(Sealed Classes)由 JEP 360 提出,并作为预览功能
在 JDK 15 中提供。它们由 JEP 397 再次提出并进行了改进,并作为预览功能在 JDK 16 中提供 。此 JEP 建议在 JDK 17 中最终确定密封类,与 JDK 16 相比没有任何更改。
没有密封类之前,在 Java 中如果想让一个类不能被继承和修改,我们可以使用 final
关键字对类进行修饰。不过,这种方式不太灵活,直接把一个类的继承和修改渠道给堵死了。
密封类可以对继承或者实现它们的类进行限制,这样这个类就只能被指定的类继承。
// 抽象类 Shape 只允许 Circle 和 Circle 继承。
public sealed class Shape permits Circle, Circle {
// Shape 类的定义
}
public final class Circle extends Shape {
// Circle 类的定义
}
public final class Rectangle extends Shape {
// Rectangle 类的定义
}
另外,任何扩展密封类的类本身都必须声明为 sealed
、non-sealed
或 final
。
public final class Circle extends Shape {
}
public non-sealed class Rectangle extends Shape {
}
JEP 409: Sealed Classes
在 Java 9 的 JEP 295 引入了实验性的提前 (AOT) 编译器,在启动虚拟机之前将 Java 类编译为本机代码。
在 Java 17,删除实验性的提前 (AOT) 和即时 (JIT) 编译器,因为该编译器自推出以来很少使用,维护它所需的工作量很大。保留实验性的 Java 级 JVM 编译器接口 (JVMCI),以便开发人员可以继续使用外部构建的编译器版本进行 JIT 编译。
JEP 410: Remove the Experimental AOT and JIT Compiler
安全管理器可追溯到 Java 1.0,多年来,它一直不是保护客户端 Java 代码的主要方法,也很少用于保护服务器端代码。为了推动 Java 向前发展,Java 17 删除了弃用的安全管理器。
JEP 411: Deprecate the Security Manager for Removal
Java 程序可以通过该 API 与 Java 运行时之外的代码和数据进行互操作。通过高效地调用外部函数(即 JVM 之外的代码)和安全地访问外部内存(即不受 JVM 管理的内存),该 API 使 Java 程序能够调用本机库并处理本机数据,而不会像 JNI 那样危险和脆弱。
下面是 FFM API 使用示例,这段代码获取了 C 库函数的 radixsort 方法句柄,然后使用它对 Java 数组中的四个字符串进行排序。
// 1. 在C库路径上查找外部函数
Linker linker = Linker.nativeLinker();
SymbolLookup stdlib = linker.defaultLookup();
MethodHandle radixSort = linker.downcallHandle(
stdlib.lookup("radixsort"), ...);
// 2. 分配堆上内存以存储四个字符串
String[] javaStrings = { "mouse", "cat", "dog", "car" };
// 3. 分配堆外内存以存储四个指针
SegmentAllocator allocator = implicitAllocator();
MemorySegment offHeap = allocator.allocateArray(ValueLayout.ADDRESS, javaStrings.length);
// 4. 将字符串从堆上复制到堆外
for (int i = 0; i < javaStrings.length; i++) {
// 在堆外分配一个字符串,然后存储指向它的指针
MemorySegment cString = allocator.allocateUtf8String(javaStrings[i]);
offHeap.setAtIndex(ValueLayout.ADDRESS, i, cString);
}
// 5. 通过调用外部函数对堆外数据进行排序
radixSort.invoke(offHeap, javaStrings.length, MemoryAddress.NULL, '\0');
// 6. 将(重新排序的)字符串从堆外复制到堆上
for (int i = 0; i < javaStrings.length; i++) {
MemoryAddress cStringPtr = offHeap.getAtIndex(ValueLayout.ADDRESS, i);
javaStrings[i] = cStringPtr.getUtf8String(0);
}
assert Arrays.equals(javaStrings, new String[] {"car", "cat", "dog", "mouse"}); // true
JEP 412: Foreign Function & Memory API (Incubator)
Vector API 最初在 Java 16 孵化并集成,在 Java 17 中进行第二次孵化。这个特性可以让开发人员更方便地使用向量操作来进行数据处理。Vector API 可以提供更高的并行性和更好的性能,从而加速数据处理过程。
// 创建一个 Vector
Vector<Float> v = Vector.of(1.0f, 2.0f, 3.0f, 4.0f);
// 对 Vector 中的元素进行操作
Vector<Float> result = v.map(x -> x * 2).add(Vector.of(1.0f, 1.0f, 1.0f, 1.0f));
// 输出结果
System.out.println(result);
JEP 414: Vector API (Second Incubator)
更多新特性请参考:
JDK 17 – New Features in Java 17
https://openjdk.org/projects/jdk/17/
解决GC毛刺问题——转转搜索推荐服务JDK17升级实践