Java 26 深度解析:一次“性能兑现 + 契约收紧”的版本

19 阅读16分钟

引子:先厘清一个被广泛误传的事实

2026 年 3 月 17 日,JDK 26 正式 GA。它是 Java 半年发布节奏下的又一个非 LTS 版本,官方支持窗口 6 个月,将于 2026 年 9 月被 JDK 27 取代。

这个版本在中文技术圈流传的文章里,被贴上了三个标签:

  • “字符串模板正式转正”
  • “虚拟线程性能翻倍”
  • “FFM API 正式版”

这三个标签全部是错的。

  • 字符串模板(String Templates)在 JDK 21、22 做过两次预览,随后在 JDK 23 被设计团队彻底撤回,至今没有替代方案落地。JDK 26 里根本没有它。
  • 虚拟线程的“去钉住”修复是 JEP 491,在 JDK 24 转正,比 Java 26 早两个版本。
  • FFM API 正式版是 JEP 454,随 Java 22(2024 年 3 月)转正。

之所以先花笔墨澄清这一点,是因为版本归属错误会直接摧毁技术决策:

  • 如果团队为了“用上字符串模板”而升级 Java 26,会发现这个特性根本不存在;
  • 如果以为虚拟线程的钉住问题要等到 26 才修好,就会白白多等一年。

那么 Java 26 真正交付了什么?把官方 10 项 JEP 铺开看,主线其实清晰得多:

一边兑现运行时的性能红利,一边开始收紧语言的完整性与安全契约。


一、Java 26 全景:官方 10 项 JEP

JEP名称状态
500Prepare to Make Final Mean Final正式
504Remove the Applet API正式
516Ahead-of-Time Object Caching with Any GC正式
517HTTP/3 for the HTTP Client API正式
522G1 GC: Improve Throughput by Reducing Synchronization正式
524PEM Encodings of Cryptographic Objects第二次预览
525Structured Concurrency第六次预览
526Lazy Constants第二次预览
529Vector API第十一次孵化
530Primitive Types in Patterns, instanceof, and switch第四次预览

一个关键判断:

10 项里有 5 项是正式特性,其余 5 项仍是预览或孵化。 而 5 项正式特性中,没有一项是新的语言语法——它们全部是库、运行时、网络协议和平台清理层面的改动。

这意味着 Java 26 不会改变你写代码的语法习惯。它的价值集中在两处:

  • 跑得更快:JEP 516、JEP 522
  • 更安全、更规范:JEP 500、JEP 517、JEP 504

二、性能线:两项“什么都不用做就能拿到”的收益

1. G1 GC 同步优化(JEP 522):默认回收器的免费提速

G1 是 HotSpot 的默认垃圾回收器。JEP 522 的核心改动是重新设计 G1 追踪对象引用的方式。

在此之前,应用线程和 GC 线程共享同一份卡表(card table) ,这意味着每次写屏障(write barrier)都要协调同步。改动引入双卡表机制——两侧各持一份,同步开销随之消失,同时写屏障的插入代码也变小了,而 G1 的整体架构保持不变。

对开发者而言,这项改动的价值在于它的零成本属性:

  • G1 本来就是默认回收器,升级 JDK 就是全部操作,不需要任何启动参数或代码改动;
  • 官方对收益的描述是“提升吞吐量”,并指出收益主要出现在引用密集(reference-heavy)的工作负载上。

2. AOT 对象缓存支持任意 GC(JEP 516):冷启动的普适解法

JEP 516 是 Project Leyden 的成果,它增强了 JDK 24 引入的 AOT 缓存能力,使其可以与任意垃圾回收器配合使用——关键在于“任意”,包括低延迟的 ZGC。

之前的限制是 AOT 缓存与 GC 实现耦合:想用缓存加速启动,就要牺牲 GC 的选择自由。解法是把 Java 对象以中立、与 GC 无关的格式顺序加载到内存,而不是映射到某个 GC 专用的格式。这样缓存与 GC 实现细节解耦,同时保证使用缓存不会显著增加启动时间。

bash

# 1. 试运行,生成 AOT 缓存
java -XX:AOTCacheOutput=app.aot -cp app.jar com.example.App

# 2. 正式运行,使用缓存加速启动
java -XX:AOTCache=app.aot -cp app.jar com.example.App

官方定位非常明确:改善启动与预热时间(start-up and warm-up time)。这对 Serverless、微服务弹性伸缩这类对冷启动敏感的场景尤其有意义。

把这两项放在一起看:

  • G1 优化改善稳态吞吐;
  • AOT 缓存改善启动速度。

两者的共同点是不需要改一行代码——这正是运行时优化的理想形态。

三、契约线:Java 开始“当真”的三件事

如果说性能线是“让程序跑得快”,那契约线就是“让程序的承诺算数”。

1. JEP 500:让 final 真正“final”

这是全文对你现有项目影响最直接的一项,因为它会影响你已经依赖的第三方库。

问题背景

final 字段代表不可变状态——一旦在构造器(实例字段)或类初始化器(静态字段)中赋值,就不该被重新赋值。这个预期对正确性推理至关重要,对性能同样重要:JVM 若能信赖 final 字段永不重赋值,就可以做常量折叠(constant folding) 。

但长期以来,深度反射把这一切架空了:

java

Field field = SomeClass.class.getDeclaredField("FINAL_VALUE");
field.setAccessible(true);
field.set(instance, newValue);   // 修改了 final 字段——JVM 允许

官方在 JEP 里的评价相当直白:

“final 字段实际上和非 final 字段一样可变。”

而依赖这种能力的库并不少——Hibernate 的部分配置、Mockito 的 spy 机制、Lombok、各类序列化框架都可能用到。

JDK 26 做了什么

从这一版起,非法的 final 字段修改仍然会成功,但会发出警告:

text

WARNING: Final field f in p.C has been [mutated/unreflected for mutation] by class com.foo.Bar.caller in module N
WARNING: Use --enable-final-field-mutation=N to avoid a warning
WARNING: Mutating final fields will be blocked in a future release unless final field mutation is enabled

几个必须知道的细节:

  • 默认每个模块最多警告一次,写入 stderr。

  • 仅用 --add-opens 无法规避——它还需要模块被显式启用 final 字段修改。

  • 新约束是三个条件的同时满足:

    1. setAccessible(true) 成功;
    2. 字段所在包向调用者模块 open;
    3. 调用者模块启用了 final 字段修改。

    后两条是 JDK 26 新增的。

当前是警告,未来是异常。 为此官方提供了两个选项:

bash

# 把非法修改当作错误,立即暴露问题——官方建议现在就用它跑一遍现有代码
--illegal-final-field-mutation=deny

# 临时豁免,给自己和库争取适配时间(未来规避的是异常)
--enable-final-field-mutation=ALL-UNNAMED

--illegal-final-field-mutation 有四个取值:

取值含义
allow允许且不警告
warn默认,每模块一次警告
debug每次警告并附堆栈
deny抛 IllegalAccessException

deny 将在未来成为默认值。

实操建议

这是本项特性最应该落到行动上的一点:

  1. 把 Java 26 加进 CI 矩阵,用日志 grep has been mutated reflectively;
  2. 自己代码里的告警自己改;来自库的告警,去查是否有适配 Java 26 的新版本;
  3. 用 JFR 定位“是谁改的”:

bash

java -XX:StartFlightRecording:filename=recording.jfr ...
jfr print --events jdk.FinalFieldMutation recording.jfr

一个好消息:Spring Framework 7.0 已经清理了自己的反射用法,框架本身大概率不是告警来源。更可能的“肇事者”是 Hibernate 7.x 和各类测试框架。


2. JEP 517:HTTP Client 原生支持 HTTP/3

JDK 内置的 HttpClient 现在可以走 HTTP/3(基于 QUIC) 。API 改动只有一行:

java

HttpClient client = HttpClient.newBuilder()
        .version(HttpClient.Version.HTTP_3)
        .build();

设计上有两个让人放心的点:

  • HTTP/2 仍是默认版本,不主动指定就不会改变现有行为——迁移风险接近于零;
  • 服务器不支持 HTTP/3 时自动降级,可通过 response.version() 查看实际使用的协议。

HTTP/3 基于 QUIC(跑在 UDP 上),解决了 TCP 层的握手延迟和队头阻塞问题。它的实际价值场景很具体:

  • 高并发外部 API 调用;
  • AI 推理端点;
  • 支付网关集成。

这些场景里,一个慢请求不再拖住同一条连接上的其他请求。

另有一个配套增强:HttpRequest.BodyPublishers.ofFileChannel(...),支持上传文件的指定区域而不必把整个文件读入内存。


3. JEP 504:移除 Applet API

java.applet 包及相关类被彻底移除。这不算新闻——Applet 早在 JDK 17(2021 年)就已标记为待移除,现代浏览器也早已不支持这项技术。

它的意义是象征性的:Java 正式告别浏览器插件时代。 官方给出的收益是“减小安装包与源代码体积,提升应用的性能、稳定性和安全性”。

迁移检查很简单:

bash

grep -r "java.applet|javax.swing.JApplet" src/

四、预览与孵化:三条还在路上、但值得关注的长线

Java 26 的 5 项非正式特性,方向高度一致——都在解决“Java 代码太啰嗦”或“某类能力还不够强”的问题。它们的共同意义是:

提前看清 Java 未来几年的样子。

1. 原始类型模式匹配(JEP 530,第四次预览)

这是本版唯一一项语言语法类特性,且仍在预览。 它要解决一个从泛型时代就存在的割裂:为什么 int 不能直接参与模式匹配?

java

// 旧写法:必须依赖包装类
Object obj = 100;
if (obj instanceof Integer i) {
    System.out.println(i * 2);
}

// 新写法:直接匹配原始类型,无须装箱
if (obj instanceof int i) {
    System.out.println(i * 2);
}

switch 同样受益,可覆盖全量原始类型:

java

String describe(Object obj) {
    return switch (obj) {
        case byte b   -> "Byte: " + b;
        case short s  -> "Short: " + s;
        case int i    -> "Int: " + i;
        case long l   -> "Long: " + l;
        case float f  -> "Float: " + f;
        case double d -> "Double: " + d;
        default       -> "Unknown";
    };
}

第四次预览相对前版的两项改进:

  1. 完善了无条件精确性(unconditional exactness) 的定义;
  2. 在 switch 构造中应用更严格的支配性检查(dominance checks) ,让编译器能发现更多潜在编码错误。

官方目标写得很清楚:消除原始类型在模式匹配、instanceof 和 switch 中造成的摩擦,让 Java 更统一、更具表达力,从而简化集成 AI 推理能力的应用开发。

一点提醒:这个特性从 JDK 23 就开始预览,到 26 已是第四轮(27 继续第五轮),说明它牵扯的边界情况比想象中复杂——这正是“预览”存在的意义。


2. 结构化并发(JEP 525,第六次预览)

核心思想一句话说清:

把一组相关的并发子任务视为单一工作单元,统一管理其生命周期、异常与取消。

java

try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
    Subtask<UserInfo> userTask  = scope.fork(() -> fetchUserInfo(userId));
    Subtask<OrderInfo> orderTask = scope.fork(() -> fetchOrderInfo(userId));

    scope.join();
    scope.throwIfFailed();

    return new DashboardResponse(userTask.get(), orderTask.get());
}
// try-with-resources 自动关闭 scope,未完成的任务被取消

本轮新增:向 StructuredTaskScope.Joiner 接口添加 onTimeout() 方法,允许在超时后返回结果。

它想解决的是传统并发编程里几个反复出现的坑:

  • 线程泄漏;
  • 取消延迟;
  • 异常丢失。

实践中如果你的任务组中有一个失败,其余会自动取消,资源自动释放——这正是“结构化”的含义。


3. 惰性常量(JEP 526,第二次预览)

惰性常量(LazyConstant)是持有不可修改数据的对象,被 JVM 视为真正的常量,因此能享受与 final 字段相同的性能优化。

它和 final 字段的差异在于初始化时机:

更新次数更新位置常量折叠?
final 字段1构造器 / 静态初始化器(急切)是
LazyConstant[0, 1]计算函数(按需)初始化之后:是
非 final 字段[0, ∞)任意位置否

java

// 单个惰性常量:首次 get() 时才执行 lambda
private static final LazyConstant<Logger> LOGGER =
    LazyConstant.of(() -> Logger.create(OrderController.class));

// 惰性列表:元素独立按需初始化,每个只求值一次(即使并发访问)
private static final List<OrderController> ORDERS =
    List.ofLazy(POOL_SIZE, _ -> new OrderController());

// 惰性映射:键在构造时已知,值按需初始化
private static final Map<String, OrderController> CONTROLLERS =
    Map.ofLazy(Set.of("Customers", "Internal", "Testing"), _ -> new OrderController());

第二次预览相对首次(JDK 25 的 Stable Values)的关键变更:

  1. 重命名 StableValue<T> → LazyConstant<T>;
  2. 移除低层方法 setOrThrow、trySet、orElseSet,只保留接受“计算函数”的工厂方法;
  3. 工厂方法移入 java.util.List 和 java.util.Map 接口,增强可发现性;
  4. 禁止 null 作为计算值。

这四条变更指向同一个设计意图:

主动收窄 API 表面,让这个容器只能以安全的方式被使用。

注意第 3 条——官方 JEP 文本里没有 Set.ofLazy(Set 只是作为 Map.ofLazy 的键集合参数出现)。若你看到有文章给出 Set.ofLazy,那是错的。

适用场景:凡是“创建成本高、但可能压根用不到”的对象——日志器、配置对象、连接池元素等。官方特别点出它在 AI 和数据驱动型应用中的价值。


4. 向量 API(JEP 529,第十一次孵化)与 PEM 编码(JEP 524,第二次预览)

向量 API

让开发者用平台无关的方式表达向量计算,在运行时编译为 CPU 最优的 SIMD 指令,加速数据分析、AI 推理和科学计算。

它在 Java 26 是第十一次孵化,且本轮没有 API 变更——因为它正在等待 Project Valhalla 的 Value Classes 落地。

PEM 编码

提供新的编码 API,把代表加密密钥、证书和证书吊销列表(CRL)的对象编码为 PEM(Privacy-Enhanced Mail)格式,并能反向解码。它降低了出错风险、简化合规流程。

第二次预览将 PEMRecord 重命名为 PEM,新增 decode() 方法,并将加密相关方法规整为 encrypt。


五、容易被忽略但有实用价值的小改动

这一版还有一些不挂 JEP 编号、但用得上就回不去的增强:

UUIDv7 进入标准库

UUID.randomUUID() 生成的是 v4 UUID(完全随机)。用作数据库主键时,每次插入都落在 B 树索引的随机位置,造成索引碎片。

Java 26 新增 UUID.ofEpochMillis(long),生成时间有序的 UUID——高位包含毫秒时间戳,新行总是插到索引末尾。

Process 实现 AutoCloseable

终于可以用 try-with-resources 自动关闭进程:

java

try (Process p = new ProcessBuilder("ls").start()) {
    int exitCode = p.waitFor();
}

其他

  • Unicode 17.0 支持;
  • Duration.MIN / Duration.MAX 常量;
  • HTTP Client 支持基于区域的文件上传;
  • JavaDoc 深色模式;
  • 安全与合规层面:混合公钥加密(HPKE)、后量子就绪的 JAR 签名、CLDR v48 支持。

六、把 Java 26 放进版本序列

孤立看 Java 26 会觉得“亮点不多”,放进序列里脉络就清楚了:

版本定位代表特性
Java 21(LTS)并发范式确立虚拟线程正式版、模式匹配
Java 22互操作打通FFM API 正式版(JEP 454)
Java 23GC 默认值调整ZGC 分代成为默认(JEP 474)、字符串模板被撤回
Java 24并发与内存收尾虚拟线程去钉住(JEP 491) 、移除非分代 ZGC(JEP 490)
Java 25(LTS)语法与性能并进Compact Object Headers、Scoped Values 正式版
Java 26性能兑现 + 契约收紧G1 同步优化、AOT 缓存通用化、final 语义收紧、HTTP/3
Java 27默认值大规模切换G1 成为全环境默认 GC(JEP 523)、Compact Object Headers 默认开启
Java 29(LTS)下一个 LTS预计 2027 年 9 月

这里必须明确一件事:

下一个 LTS 是 Java 29,不是 Java 27。

Java 27 于 2026 年 9 月 15 日发布,同样是六个月支持期的非 LTS 版本。这条信息以 Oracle Java SE 支持路线图 为准。


七、该不该升级:一份基于事实的判断

先说结论:

生产环境建议留在 Java 25 LTS;Java 26 的价值在于 CI 验证与特性预研。

不建议上生产的理由

非 LTS 只有 6 个月支持窗口,2026 年 9 月就被 JDK 27 取代。对需要长期安全补丁的关键业务系统,这个跑道不够长。

此外,Spring Boot 4.0.x 官方支持到 Java 25,Java 26 属于“尽力支持(best effort)”——实践可行,但无保证。

现在就该做的事

  1. 把 Java 26 加进 CI 矩阵,主要目的是提前暴露 JEP 500 的 final 字段告警。这是本文中唯一一项“即使你不升级也会影响你”的改动——一旦部署环境升级,依赖 final 字段反射的库就会开始刷告警。
  2. 用 --illegal-final-field-mutation=deny 跑一遍测试,把未来的异常提前变成现在的失败。官方明确建议这么做。
  3. 你的代码里如果有字符串模板:它从未进入正式版且已在 JDK 23 撤回,如果因为看到错误资料而打算采用,请直接放弃,改用其他方式构建 SQL/HTML/JSON。
  4. 如果你还在 Java 21:从 21 到 25 LTS 的累积收益才是真正值得规划的——虚拟线程去钉住(JDK 24)、Compact Object Headers(JDK 25) 、AOT 缓存改进。这条路径的确定性远高于“追 Java 26”。

升级前检查清单

bash

# 1. 检查 final 字段反射修改(JDK 26 起会告警,未来会报错)
#    直接以 deny 模式运行,看是否有 IllegalAccessException
java --illegal-final-field-mutation=deny -jar app.jar

# 2. 检查 Applet 残留(JDK 26 已彻底移除)
grep -r "java.applet|javax.swing.JApplet" src/

# 3. 如果要用预览特性(JEP 526 / 530 / 525 / 524),必须显式开启
javac --release 26 --enable-preview Main.java
java --enable-preview Main

结语:Java 26 的正确读法

如果只记一句话:

Java 26 不是一个“新语法版本”,而是一个“把运行时性能兑现、把语言契约收紧”的版本。它的两个正式性能特性(G1 优化、AOT 缓存通用化)都不需要改代码;它最重要的正式特性(JEP 500)是让 final 开始真正不可变。

三个值得优先关注的点:

  1. JEP 522(G1 同步优化) ——默认回收器的免费提速,升级即得。
  2. JEP 516(AOT 缓存支持任意 GC) ——冷启动优化不再需要牺牲 GC 选择权。
  3. JEP 500(final 语义收紧) ——从警告到异常只差一个版本,现在就该排查。

而对于那三项被误传为“Java 26 特性”的旧功能,最务实的做法是记住它们的真实归属:

  • 字符串模板 → 从未正式化,JDK 23 已撤回;
  • 虚拟线程去钉住(JEP 491) → JDK 24 已交付;
  • FFM API(JEP 454) → Java 22 已交付。

技术信息的可靠性,最终取决于是否能追溯到一手信源。

遇到任何“Java 新特性”的文章,第一件事是打开 openjdk.org 核对 JEP 编号和归属版本——这个习惯的价值,远大于记住任何一个具体特性。