引子:先厘清一个被广泛误传的事实
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、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
ini
# 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
ini
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
vbnet
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 新增的。
当前是警告,未来是异常。 为此官方提供了两个选项:
bash
ini
# 把非法修改当作错误,立即暴露问题------官方建议现在就用它跑一遍现有代码
--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 定位"是谁改的":
bash
ruby
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
arduino
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
perl
grep -r "java.applet|javax.swing.JApplet" src/
四、预览与孵化:三条还在路上、但值得关注的长线
Java 26 的 5 项非正式特性,方向高度一致------都在解决"Java 代码太啰嗦"或"某类能力还不够强"的问题。它们的共同意义是:
提前看清 Java 未来几年的样子。
1. 原始类型模式匹配(JEP 530,第四次预览)
这是本版唯一一项语言语法类特性,且仍在预览。 它要解决一个从泛型时代就存在的割裂:为什么 int 不能直接参与模式匹配?
java
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
arduino
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,第六次预览)
核心思想一句话说清:
把一组相关的并发子任务视为单一工作单元,统一管理其生命周期、异常与取消。
java
scss
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
arduino
// 单个惰性常量:首次 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 表面,让这个容器只能以安全的方式被使用。
注意第 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
ini
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 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"。
升级前检查清单
bash
css
# 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 编号和归属版本------这个习惯的价值,远大于记住任何一个具体特性。