功能处于孵化或预览阶段是什么意思?
实际上,这是向 Java 编程语言添加新功能的新过程,Java 社区使用这种过程来在 API 和工具处于早期实验阶段时从社区获得反馈(孵化功能)或已经完全指定但尚未永久的阶段(预览功能)。
预览功能是 Java 语言、Java 虚拟机或 Java SE API 的新功能,已经完全指定和实现,但尚未成为永久性的。它在 JDK 功能发布中提供,以便开发人员根据实际使用情况提供反馈,这可能导致其在未来的 Java SE 平台中成为永久功能。
孵化功能是一个 API 或工具,体量不小,正在开发中,最终将包含在 Java SE 平台或 JDK 中。该 API 或工具尚未完全验证,因此希望推迟标准化或最终确定,以便在少量功能发布中获得更多经验和反馈。
Java 22 新功能
由于 Java 22 不是 LTS 版本,了解哪些功能是非预览(或非孵化)的对想使用最新永久功能的 Java 开发人员来说非常重要。以下是该版本的功能及其状态列表:
- (✅ 永久) G1 的区域固定
- (✅ 永久) 外部函数和内存 API
- (✅ 永久) 未命名变量和模式
- (✅ 永久) 启动多文件源代码程序
- (🔍 预览) super(…) 之前的语句
- (🔍 预览) 类文件 API
- (🔍 第二次预览) 字符串模板
- (🔍 预览) 流收集器
- (🔍 第二次预览) 结构化并发
- (🔍 第二次预览) 隐式声明类和实例主方法
- (🔍 第二次预览) 作用域值
- (🧪 第七次孵化) 向量 API
如上所述,此版本只有 4 个永久性功能,可以预期它们将在下一个 LTS 版本中出现。
1- G1 的区域固定
G1(Garbage First)垃圾收集器自 JDK 9 起成为默认垃圾收集器。这个变化是为了提供更好的整体性能和更可预测的应用程序暂停时间,相较于之前的默认垃圾收集器 Parallel GC。
它首先关注垃圾最多的区域,使用并发和并行处理,并提供更可预测的暂停时间。
通过 JEP 423(G1 的区域固定),Java 22 引入了对 G1 垃圾收集器(GC)的增强。这个新功能旨在显著减少与 Java 本地接口(JNI)关键区域相关的延迟问题。
JNI(Java Native Interface)通过提供获取和释放 Java 对象的直接指针的函数,促进与 C 和 C++ 等非托管编程语言的互操作性。这些函数对内的代码在关键区域内运行,访问 Java 对象的时间被称为关键对象。传统上,G1 会在这些关键区域内禁用 GC,以防止移动对象,从而导致显著的延迟问题,如线程饥饿和应用程序阻塞。
区域固定方法通过将关键对象固定在其位置,确保 GC 能继续进行而不受阻碍,从而解决了这些问题。
总而言之,G1 的区域固定允许垃圾收集器更有效地处理固定对象,提高性能,减少 GC 暂停,并改善 Java 应用程序的内存管理效率。
2- 外部函数和内存 API
经过超过 10 年的工作,通过在 Java 22 中最终确定 JEP 454(外部函数和内存 API),我们现在有了一种比 JNI 更好的方法和 API 来与 Java 运行时之外的代码和数据进行交互。
我认为这是 Java 22 引入的最重要和最革命性的永久功能。该 API 包含两个部分:外部函数 API,它提供了一种高效调用外部函数的方法,以及内存 API,它提供了对外部内存的安全访问,例如 JVM 不管理的内存。
为了更清晰地说明,请看这段来自 bdd 博客的示例代码,展示了如何使用 FFM API 调用外部 C 函数:
void main(String[] args) {
// 第一步:查找外部函数
Linker linker = Linker.nativeLinker();
SymbolLookup stdlib = linker.defaultLookup();
MemorySegment strlenAddress = stdlib.find("strlen").orElseThrow();
// 第二步:定义输入/输出并创建方法句柄
FunctionDescriptor descriptor = FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS);
MethodHandle strlen = linker.downcallHandle(strlenAddress, descriptor);
// 第三步:管理堆外内存
try (Arena offHeap = Arena.ofConfined()) {
// 第四步:在堆外内存中使参数 C 兼容
MemorySegment funcArg = offHeap.allocateFrom(args[0]);
// 第五步:调用函数
long len = (long) strlen.invoke(funcArg);
}
}
如你所见,FFM 方法比 JNI 方法更易读,后者首先强迫我们使用 native
关键字定义本地方法,然后需要更多步骤来从我们的 Java 代码中调用外部函数。
外部函数和内存 API(FFM)在 Java 中相对于传统的 Java 本地接口(JNI)提供了多项显著优势。它用一种可读的纯 Java API 替代了复杂和脆弱的 JNI 方法,这使得开发人员在处理本地方法时更加轻松。该 API 处理多种内存类型中的结构化和非结构化数据,并通过防止跨线程的使用后释放错误来确保健全性。
3- 未命名变量和模式
未命名变量是 Java 中另一个期待已久的功能。如果你用过 Scala,这个功能你会很熟悉。通过最终确定这个功能,我们现在可以拥有未命名变量和未命名模式,这些可以在需要变量声明或嵌套模式但从未使用时使用。这种变量和模式用下划线字符 _
表示。
未命名变量
以下几种声明可以引入命名变量或未命名变量:
- 块中的局部变量声明语句
- try-with-resources 语句的资源规范
- 基本 for 循环的头部
- 增强型 for 循环的头部
- catch 块的异常参数
- lambda 表达式的形式参数
当你声明一个未命名变量时,在初始化后你无法访问或修改它。让我们看一些代码示例:
// 带副作用的增强型 for 循环:
static int count(Iterable<Order> orders) {
int total = 0;
for (Order _ : orders) // 未命名变量
total++;
return total;
}
// 赋值语句:
Queue<Integer> q = ... // x1, y1, z1, x2, y2, z2, ...
while (q.size() >= 3) {
var x = q.remove();
var y = q.remove();
var _ = q.remove(); // 未命名变量
... new Point(x, y) ...
}
// catch 块:
String s = ...
try {
int i = Integer.parseInt(s);
} catch (NumberFormatException _) { // 未命名变量
System.out.println("Bad number: " + s);
}
// 在 try-with-resources 中:
try (var _ = ScopedContext.acquire()) { // 未命名变量
// 不使用获取的资源 ...
}
// 参数无关的 lambda:
stream.collect(Collectors.toMap(String::toUpperCase,
_ -> "NODATA")) // 未命名变量
未命名模式变量
未命名模式变量可用于:
在类型模式中(包括 var
类型模式,无论该类型模式出现在顶层还是嵌套在记录模式中)
switch (ball) {
case RedBall _ -> process(ball); // 未命名模式变量
case BlueBall _ -> process(ball); // 未命名模式变量
case GreenBall _ -> stopProcessing(); // 未命名模式变量
}
switch (box) {
case Box(RedBall _) -> processBox(box); // 未命
case Box(BlueBall _) -> processBox(box); // 未命名模式变量
case Box(GreenBall _) -> stopProcessing(); // 未命名模式变量
case Box(var _) -> pickAnotherBox(); // 未命名模式变量
}
多模式的 case
标签:
switch (box) {
case Box(RedBall _), Box(BlueBall _) -> processBox(box);
case Box(GreenBall _) -> stopProcessing();
case Box(var _) -> pickAnotherBox();
}
在未命名模式中:
if (r instanceof ColoredPoint(Point(int x, int y), _)) { ... x ... y ... }
欲了解更多描述和示例代码,请访问 官方 JEP 页面。
使用未命名变量和模式的可能问题
使用未命名变量和模式的潜在风险是,从旧版本升级的开发人员如果代码中包含下划线字符 _
的变量,可能会遇到编译时错误或警告。此外,开发人员可能需要更新静态分析工具,以识别 _
的新角色,并避免错误地标记其未使用。
4- 启动多文件源代码程序
这个功能是 JShell 的一个补充功能,也是 Java 11 中引入的启动单文件源代码程序功能的补充。
启动多文件源代码程序功能允许开发人员在一次 Java 启动器调用中编译和运行多个 Java 源文件。
这意味着我们现在可以在不手动编译它们的情况下执行跨多个源文件的程序。
这个增强功能简化了从源代码直接运行复杂 Java 应用程序的工作流程,提高了开发效率和便利性。它还将使从小型程序向大型程序的过渡更加渐进,使开发人员能够在项目早期阶段避免配置构建工具。
小结
网上有许多关于 Java 22 新功能的文章,如字符串模板、结构化并发或向量 API,但所有这些有趣和奇妙的功能都处于预览或孵化状态。本文回顾了那些不在预览或孵化状态的功能。对我来说,外部函数和内存 API(FFM)是最令人兴奋和革命性的功能。
你可以在 Oracle 的 官方博客文章 或 OpenJDK 网站上的 JDK 22 官方页面 阅读完整的发布报告和更多信息。