引子:先厘清一个被广泛误传的事实
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 将在未来成为默认值。
实操建议(这是本项特性最应该落到行动上的一点):
-
把 Java 26 加进 CI 矩阵 ,主要目的日志 grep
has been mutated reflectively; -
自己代码里的告警自己改;来自库的告警,去查是否有适配 Java 26 的新版本;
-
用 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)的关键变更:
- 重命名
StableValue<T>→LazyConstant<T>; - 移除低层方法
setOrThrow、trySet、orElseSet,只保留接受 "计算函数" 的工厂方法; - 工厂方法移入
java.util.List和java.util.Map接口,增强可发现性; - 禁止
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)"------ 实践可行,但无保证。
现在就该做的事
- 把 Java 26 加进 CI 矩阵 ,主要目的是提前暴露 JEP 500 的 final 字段告警。这是本文中唯一一项 "即使你不升级也会影响你" 的改动 ------ 一旦部署环境升级,依赖 final 字段反射的库就会开始刷告警。
- 用
--illegal‑final‑field‑mutation=deny跑一遍测试,把未来的异常提前变成现在的失败。官方明确建议这么做。 - 你的代码里如果有字符串模板:它从未进入正式版且已在 JDK 23 撤回。如果因为看到错误资料而打算采用,请直接放弃,改用其他方式构建 SQL/HTML/JSON。
- 如果你还在 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开始真正不可变。
三个值得优先关注的点:
- JEP 522(G1 同步优化)------ 默认回收器的免费提速,升级即得。
- JEP 516(AOT 缓存支持任意 GC)------ 冷启动优化不再需要牺牲 GC 选择权。
- JEP 500(final 语义收紧)------ 从警告到异常只差一个版本,现在就该排查。
而对于那三项被误传为 "Java 26 特性" 的旧功能,最务实的做法是记住它们的真实归属:
- 字符串模板 → 从未正式化,JDK 23 已撤回;
- 虚拟线程去钉住(JEP 491) → JDK 24 已交付;
- FFM API(JEP 454) → Java 22 已交付。
技术信息的可靠性,最终取决于是否能追溯到一手信源。遇到任何 "Java 新特性" 的文章,第一件事是打开 openjdk.org 核对 JEP 编号和归属版本------ 这个习惯的价值,远大于记住任何一个具体特性。