Java 26 新特性概览:一次稳健的迭代与未来的奠基
Java 26 如期而至。虽然它不像某些版本那样带来颠覆性的语言级变革,但它更像是一位严谨的工程师,专注于性能优化、现代化改造以及为未来更宏大的项目(Project Valhalla)铺平道路。
本次更新涵盖了 10 个 JEP(JDK Enhancement Proposal),我们可以将其分为三类:4 个正式特性、5 个预览特性和 1 个重要的弃用准备。让我们深入探讨这些变化,看看它们将如何影响我们日常的开发工作。
正式特性:稳定落地的生产力工具
这些是经过多轮预览和反馈后,终于可以放心在生产环境中使用的功能。
JEP 516: Ahead-of-Time Object Caching with Any GC
这是什么? Java 24 引入的 AOT 对象缓存功能现在摆脱了 GC 的束缚,可以与任何 GC(包括 ZGC 和 Shenandoah)协同工作。
解决了什么问题?
之前的 AOT 缓存实现与特定 GC 的对象格式紧密耦合,导致在使用 ZGC、大堆(>32GB)或禁用压缩对象指针(-XX:-UseCompressedOops)时无法启用,限制了其应用范围。
核心原理:
新方案不再使用硬编码的内存地址来引用缓存对象,而是采用了一套逻辑索引系统。这层抽象解耦了对象缓存与 GC 的内存布局,实现了通用性。使用 ZGC、堆大于 32GB 或 -XX:-UseCompressedOops 时自动启用,也可以通过 -XX:+AOTStreamableObjects 强制启用。
我的观点: 这是一个典型的"幕后英雄"式改进。对于追求极致启动性能的应用(例如 Serverless、CLI 工具)来说,AOT 缓存是关键。JEP 516 将这一优势从 G1/Parallel GC 扩展到了所有现代 GC,使得更多采用新技术的应用能够受益。这是一个让 Java 在云原生时代更具竞争力的重要步骤。
JEP 522: G1 GC — Improve Throughput by Reducing Synchronization
这是什么? 通过精巧的设计,减少了 G1 垃圾回收器中应用线程(mutators)和 GC 线程之间的同步开销。
带来了什么好处? 直接的性能提升!官方预期:
- 对于引用密集型应用,吞吐量提升 5-15%
- 在 x64 架构上,由于寄存器压力减小,还有额外的约 5% 提升
这一切的代价仅仅是每 GB 堆内存增加约 2MB(0.2%)的额外开销,可以说是稳赚不赔。
核心原理: G1 使用 Card Table 来跟踪哪些内存区域(Card)被修改,以便在并发标记阶段进行处理。之前,应用线程和 GC 线程会争用同一个 Card Table,造成同步瓶颈。JEP 522 引入了第二个 Card Table,让应用线程写入一个,GC 线程处理另一个,巧妙地避免了直接冲突。
我的观点: 这是 Java 平台"免费午餐"的又一绝佳范例。G1 作为默认 GC,其性能的任何提升都会惠及海量应用。开发者无需修改任何代码,只需升级 JDK 版本,就能享受到更高的吞吐量。这充分体现了 JVM 平台持续自我优化的价值。
JEP 517: HTTP/3 for the HTTP Client API
这是什么?
Java 11 引入的标准化 java.net.http.HttpClient 终于原生支持 HTTP/3 协议。
为什么重要? HTTP/3 基于 QUIC 协议(构建于 UDP 之上),相比 HTTP/2(基于 TCP),它解决了队头阻塞问题,连接建立更快,并且在网络环境不稳定(如移动网络)时表现更佳。目前约三分之一的网站已支持 HTTP/3。
如何使用? 你需要显式选择使用 HTTP/3,并且有四种协议协商策略可选:
// 需要显式请求 HTTP/3
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_3)
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://example.com"))
.build();
// 客户端会尝试通过 ALPN 或 HTTPS 记录来协商 HTTP/3
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println("Response protocol version: " + response.version());
四种协商策略:
- 先尝试 HTTP/3,超时后回退到 HTTP/2/1.1
- HTTP/3 与旧协议同时竞速
- 先用 HTTP/2/1.1,发现服务器支持后切换
- 强制仅使用 HTTP/3(不支持则失败)
我的观点: 这是一个与时俱进的必要升级。虽然许多高性能场景可能已经在使用 Netty 等第三方库,但标准库提供开箱即用的 HTTP/3 支持,对于大量普通应用和希望减少外部依赖的场景来说意义重大。它确保了 Java 在网络编程领域始终保持现代化。
JEP 504: Remove the Applet API
古老的 Applet API 被正式从 JDK 中移除。
历史回顾:
- Java 9:标记为 Deprecated
- Java 17:标记为 Deprecated for Removal
- Java 26:正式移除
移除的内容包括整个 java.applet 包、java.beans.AppletInitializer 和 javax.swing.JApplet。
我的观点: 移除 Applet API 是一个清理历史包袱的正确决定,它使 JDK 更轻量、更安全。对于绝大多数开发者来说,这不会有任何影响,除非你还在维护博物馆级别的代码。
预览特性:塑造 Java 的未来
这些功能已经可用,但需要通过 --enable-preview 标志启用。它们是 Java 团队邀请社区参与反馈、共同打磨未来 Java 的重要途径。
JEP 530: Primitive Types in Patterns, instanceof, and switch(Fourth Preview)
这是什么?
模式匹配现在可以直接用于 int, double, boolean 等原始类型,而不再局限于引用类型。这意味着 instanceof 和 switch 都可以与原始类型一起使用了。
代码示例:
// 预览功能,需要 --enable-preview
static String formatPrimitive(Object obj) {
return switch (obj) {
// 直接匹配原始类型!
case int 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 -> obj.toString();
};
}
// instanceof 也支持原始类型
if (roomSize instanceof byte r) {
System.out.println("Room size fits in a byte: " + r);
}
我的观点: 它补全了模式匹配的一块重要拼图,让其在处理混合类型数据时(例如解析 JSON、处理数据库记录)的表达能力和一致性大大增强。这是 Java 语言现代化演进中非常扎实的一步。
JEP 525: Structured Concurrency(Sixth Preview)
这是什么? 一种全新的并发编程模型,旨在通过将并发任务的生命周期与代码的语法结构绑定,来简化并发程序的编写、调试和维护。
核心思想: 如果一个任务分裂成多个子任务,那么它必须等待所有子任务完成后才能继续。这创建了一个清晰的父子任务层次结构,杜绝了线程泄漏,并极大地简化了错误处理和任务取消。
本轮新变化:
引入了 onTimeout() 方法;allSuccessfulOrThrow() 返回 List 而非 Stream。
代码示例:
// 预览功能,需要 --enable-preview
void handleOrder() throws InterruptedException, ExecutionException, TimeoutException {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Future<String> userFuture = scope.fork(this::findUser);
Future<Integer> orderFuture = scope.fork(this::fetchOrder);
scope.joinUntil(Instant.now().plusSeconds(5));
scope.throwIfFailed();
String user = userFuture.resultNow();
int order = orderFuture.resultNow();
System.out.println("Order " + order + " for user " + user);
}
}
我的观点:
结构化并发是自 java.util.concurrent 包以来 Java 并发领域最重要的变革。它从根本上改变了我们思考并发的方式,从"管理线程"转向"管理任务"。虽然经过了六轮预览,但这种谨慎是必要的。它正在变得越来越成熟,离最终定稿不远了。
JEP 526: Lazy Constants(Second Preview)
这是什么? 提供了一种标准、安全、高效的方式来定义延迟初始化的不可变值。本轮从 "Stable Values" 改名为 "Lazy Constants",更直观地反映了其核心特性:懒加载 + 不可变性。
新的 LazyConstant<T> 类保证即使在并发访问下也只初始化一次,并且不允许 null 作为计算值。同时支持 List.ofLazy() 和 Map.ofLazy() 等集合工厂方法。
我的观点: 这是一个看似小众但实则非常实用的特性。经典的"延迟初始化单例"模式充满了陷阱(线程安全、双重检查锁定失效等)。Lazy Constants将这个常见的、容易出错的设计模式提升为了一个简单、可靠的语言构件,对于编写高性能且健壮的库和框架尤其有价值。
其他预览/孵化特性
- JEP 524: PEM Encodings of Cryptographic Objects(Second Preview): 提供了一套标准 API 用于解析和生成 PEM 格式的密钥和证书,新增了
PEMEncoder、PEMDecoder、PEMrecord 等类,减少了对 Bouncy Castle 等第三方库的依赖。 - JEP 529: Vector API(Eleventh Incubator): 这个"老朋友"还在孵化中。它旨在利用 CPU 的 SIMD 指令集来加速向量化计算,是 Java 在高性能计算、机器学习等领域发力的关键。它的最终形态高度依赖于 Project Valhalla 的进展,本轮无变化。
值得关注的弃用
JEP 500: 让 final 真正成为 final
这是什么?
当代码试图通过反射来修改一个 final 字段时,JDK 会发出警告。
为什么这么做?
final 关键字是 Java 语言中一个重要的契约,它保证了字段的不可变性。JVM 和 JIT 编译器会基于这个契约进行大量优化。通过反射打破这个契约是一种危险的"黑魔法",它不仅可能导致程序行为诡异,还会阻碍未来的性能优化。
影响:
目前只是一个警告,但这是一个明确的信号:在未来的 Java 版本中,这种行为很可能会被彻底禁止并抛出 IllegalAccessException。可以通过 --enable-final-field-mutation=module1,module2 显式允许特定模块的 final 字段修改。依赖这种技巧的框架(如一些旧版的序列化库)需要尽快调整实现。
我的观点: 拨乱反正,正本清源。这是一个对 Java 平台长期健康发展至关重要的举措。它加强了语言的语义一致性,为 JIT 编译器开启了更大的优化空间。开发者应该立即检查自己的代码库,消除对这种不安全行为的依赖。
总结与展望
Java 26 是一次典型的"承上启下"的版本。
承上,它将 AOT 缓存、G1 GC 优化、HTTP/3 支持等功能正式交付,实实在在地提升了现有应用的性能和现代化水平。
启下,它通过多轮预览,持续打磨着结构化并发、模式匹配等将深刻改变 Java 编程范式的重量级特性。
更重要的是,我们能清晰地看到 Project Valhalla 的身影。无论是漫长孵化的 Vector API,还是新颖的 Lazy Constants,它们都在等待 Valhalla 带来的值类型(Value Types)和原始对象(Primitive Objects)来释放全部潜力。Valhalla 将是继泛型之后,对 Java 平台最深刻的一次变革,而 Java 26 的许多特性,正是在为这场变革的到来铺设坚实的地基。
总而言之,升级到 Java 26 将会是一次平滑且有益的体验。你不仅能获得免费的性能提升和现代化的网络支持,还能提前体验到那些即将定义下一代 Java 编程风格的强大功能。Java 的演进,稳健而坚定。
参考
- Java 26 is here! — Hanno's Blog