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

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

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

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

这意味着 Java 26 不会改变你写代码的语法习惯。它的价值集中在两处:跑得更快(JEP 516、522),以及更安全、更规范(JEP 500、517、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 实现细节解耦,同时保证使用缓存不会显著增加启动时间。

# 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)。

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

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 字段修改仍然会成功,但会发出警告:

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 字段修改;
  • 新约束是三个条件的同时满足:setAccessible(true) 成功 + 字段所在包向调用者模块 open + 调用者模块启用了 final 字段修改。后两条是 JDK 26 新增的。

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

# 把非法修改当作错误,立即暴露问题——官方建议现在就用它跑一遍现有代码
--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 定位 "是谁改的":
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 改动只有一行:

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 正式告别浏览器插件时代。 官方给出的收益是 "减小安装包与源代码体积,提升应用的性能、稳定性和安全性"。

迁移检查很简单:

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

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

Java 26 的 5 项非正式特性,方向高度一致 —— 都在解决 "Java 代码太啰嗦" 或 "某类能力还不够强" 的问题。它们的共同意义是:提前看清 Java 未来几年的样子。

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

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

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

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

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

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";
    };
}

第四次预览相对前版的两项改进:一是完善了无条件精确性(unconditional exactness) 的定义;二是在 switch 构造中应用更严格的支配性检查(dominance checks),让编译器能发现更多潜在编码错误。

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

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

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

核心思想一句话说清:把一组相关的并发子任务视为单一工作单元,统一管理其生命周期、异常与取消。

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, ∞) 任意位置 否
// 单个惰性常量:首次 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 表面,让这个容器只能以安全的方式被使用。

注意:官方 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 自动关闭进程:
try (Process p = new ProcessBuilder("ls").start()) {
    int exitCode = p.waitFor();
}
  • Unicode 17.0 支持
  • Duration.MIN / Duration.MAX 常量
  • HTTP Client 支持基于区域的文件上传
  • JavaDoc 深色模式

安全与合规层面:Oracle 官方公告还提到混合公钥加密(HPKE)、后量子就绪的 JAR 签名、以及 CLDR v48 支持。


六、把 Java 26 放进版本序列

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

表格

版本 定位 代表特性
Java 21(LTS) 并发范式确立 虚拟线程正式版、模式匹配
Java 22 互操作打通 FFM API 正式版(JEP 454)
Java 23 GC 默认值调整 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"。

升级前检查清单

# 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 编号和归属版本—— 这个习惯的价值,远大于记住任何一个具体特性。

posted @ 2026-10-12 11:04  codigger  阅读(57)  评论(1)    收藏  举报