JDK 26 新特性详解
发布日期 :2026 年 3 月 17 日
规范版本 :Java SE 26(JSR 401)
是否 LTS :否
主题:HTTP/3 支持、AOT 对象缓存支持任意 GC、移除 Applet API、延迟常量(第二次预览)
一、版本总览
JDK 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 | 第二次预览(原 Stable Values) |
| 529 | Vector API | 第十一次孵化 |
| 530 | Primitive Types in Patterns | 第四次预览 |
重要变化 :Applet API 彻底移除,Java 与浏览器插件时代的最后联系被切断。
二、核心正式特性
1. JEP 517:HTTP Client API 支持 HTTP/3
背景:
- HTTP/3 是 HTTP/2 的后继,2022 年由 IETF 标准化(RFC 9114)
- HTTP/3 使用 QUIC 传输层协议(基于 UDP),而非 TCP
- QUIC 由 TLS 1.3 保护
HTTP/3 的优势:
| 优势 | 说明 |
|---|---|
| 更快的握手 | QUIC 集成了传输与加密握手,通常只需 1 个往返(1-RTT),甚至 0-RTT |
| 消除队头阻塞 | HTTP/2 在 TCP 层仍有队头阻塞;QUIC 的独立流解决了这个问题 |
| 更好的丢包恢复 | 尤其在丢包率高的网络(移动网络)下表现更好 |
| 连接迁移 | 客户端切换网络(Wi-Fi → 蜂窝)时连接不中断 |
JDK 26 中的 API 变更:
最小改动------只需设置协议版本:
java
import java.net.http.*;
import java.net.URI;
// ① 在 HttpClient 上设置(该 client 的所有请求优先 HTTP/3)
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_3)
.build();
HttpRequest request = HttpRequest.newBuilder(URI.create("https://example.com/"))
.GET()
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
System.out.println(response.version()); // HTTP_3(若服务器支持)
java
// ② 在单个 HttpRequest 上设置
HttpRequest request = HttpRequest.newBuilder(URI.create("https://example.com/"))
.version(HttpClient.Version.HTTP_3)
.GET()
.build();
重要:协议协商与自动降级
java
// 服务器不支持 HTTP/3 时自动降级
HttpResponse<String> resp = client.send(request, BodyHandlers.ofString());
System.out.println(resp.version());
// 可能是 HTTP_3、HTTP_2 或 HTTP_1_1(取决于服务器能力)
四种发现(Discovery)模式:
① 请求级 HTTP/3 (HttpRequest 设置 HTTP_3)
- 直接尝试 HTTP/3 连接
- 无法在合理时间内建立则降级到 HTTP/2 或 HTTP/1.1
② 客户端级 HTTP/3 (HttpClient 设置 HTTP_3,请求未设置)
- 并行尝试 HTTP/3 与 HTTP/2/1.1
- 使用最先成功的连接
③ ALT-SVC 发现(推荐用于广泛兼容)
java
// 用 HTTP/2 发起首个请求,若响应头指示 HTTP/3 可用则后续使用 HTTP/3
HttpRequest request = HttpRequest.newBuilder(URI.create("https://example.com/"))
.setOption(HttpOption.H3_DISCOVERY, HttpOption.Http3DiscoveryMode.ALT_SVC)
.GET()
.build();
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_3)
.build();
④ 仅 HTTP/3(不降级)
java
HttpRequest request = HttpRequest.newBuilder(URI.create("https://example.com/"))
.setOption(HttpOption.H3_DISCOVERY,
HttpOption.Http3DiscoveryMode.HTTP_3_URI_ONLY)
.GET()
.build();
// 若服务器不支持 HTTP/3,请求失败(不降级)
四种模式对比:
| 模式 | 配置 | 行为 | 适用场景 |
|---|---|---|---|
| 请求级 | HttpRequest.version(HTTP_3) |
先试 H3,超时降级 | 已知服务器支持 H3 |
| 客户端级 | HttpClient.version(HTTP_3) |
并行尝试,取最快 | 通用(可能有额外开销) |
| ALT-SVC | H3_DISCOVERY=ALT_SVC + H3 |
先 H2/H1.1,发现后转 H3 | 广泛兼容(推荐) |
| 仅 H3 | H3_DISCOVERY=HTTP_3_URI_ONLY |
只用 H3,失败即失败 | 明确要求 H3 的场景 |
完整示例:
java
import java.net.URI;
import java.net.http.*;
import java.time.Duration;
public class Http3Demo {
public static void main(String[] args) throws Exception {
// 推荐配置:客户端级 HTTP/3 + ALT-SVC 发现
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_3)
.connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.NORMAL)
.build();
HttpRequest request = HttpRequest.newBuilder(URI.create("https://openjdk.org/"))
.setOption(HttpOption.H3_DISCOVERY,
HttpOption.Http3DiscoveryMode.ALT_SVC)
.timeout(Duration.ofSeconds(30))
.GET()
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
System.out.println("Version: " + response.version());
System.out.println("Status: " + response.statusCode());
System.out.println("Body length: " + response.body().length());
}
}
限制与非目标:
| 限制 | 说明 |
|---|---|
| 不支持第三方 SSL provider | 仅支持默认的 SunJSSE |
| 无服务端实现 | 仅客户端 |
| 不提供 QUIC API | QUIC 是内部实现细节 |
java.net.URL 不支持 |
遗留 API 仍只支持 HTTP/1.1 |
| 默认协议不变 | HTTP/2 仍是默认,需显式选择 HTTP/3 |
平台与互操作性:测试覆盖 Netty、quic-go、quiche、Neqo、nghttp3 等服务端实现。
实际影响:
- 现代浏览器大多已默认启用 HTTP/3
- 约 1/3 的网站已部署 HTTP/3
- Java 客户端现在可以享受同样的收益(尤其在高丢包网络下)
2. JEP 516:任意 GC 的 AOT 对象缓存
背景 :JDK 24(JEP 483)引入 AOT 缓存(Project Leyden),但缓存中的 Java 对象是特定于 GC 的格式,导致:
| 训练时 GC | 生产时 GC | 是否可用 |
|---|---|---|
| Serial / Parallel / G1 | Serial / Parallel / G1 | ✅ |
| 任意 | ZGC | ❌ 不可用 |
| ZGC | 任意 | ❌ 不可用 |
问题 :使用 ZGC(低延迟)的应用无法享受 AOT 带来的启动加速,必须在「低停顿」与「快启动」之间二选一。
JDK 26 的解决方案 :引入 GC 无关(GC-agnostic) 的对象缓存格式。
技术原理:
旧的 GC 特定格式(对象引用是 64 位内存地址):
text
String 对象(缓存中):
header: ... | value: 0x4002045278 | coder: ... | hash: ... | hashIsZero: ...
^^^^^^^^^^^^^^^^ 这是内存地址
问题:不同 GC 的引用格式完全不同
- 堆大小策略(Serial/Parallel/G1):<32GB 用压缩指针(32 位),>32GB 用 64 位
- 对象大小策略(G1/ZGC):对象在 Region 中的布局不同
- 元数据位(ZGC):在引用中编码 GC 状态位
新的 GC 无关格式(对象引用是逻辑索引):
text
String 对象(缓存中):
header: ... | value: 5 | coder: ... | hash: ... | hashIsZero: ...
^^^ 这是逻辑索引
工作方式:
- JVM 启动时,后台线程逐一流式地(streaming)从缓存**物化(materialize)**对象到堆中
- 物化时把逻辑索引转换为实际内存地址
- 应用首次使用某个类时,与该后台线程同步,确保相关
Class对象已就位
两种缓存格式的权衡:
| 格式 | 加载方式 | 优势场景 |
|---|---|---|
| GC 特定(mappable) | 直接映射到内存 | 热启动(缓存已在文件系统缓存中) |
| GC 无关(streamable) | 流式读取 + 物化 | 冷启动(缓存需从磁盘读取) |
为什么冷启动更喜欢流式:
- 流式读取可以与内存分配重叠(隐藏部分磁盘 I/O 延迟)
- 代价是需要一个额外的 CPU 核心
JVM 的自动选择启发式:
| 训练环境特征 | 缓存格式 | 假设的生产环境 |
|---|---|---|
使用 ZGC 或 -XX:-CompressedOops 或堆 > 32GB |
GC 无关(流式) | 大型环境,有多余核心 |
使用 -XX:+UseCompressedOops(堆 < 32GB) |
GC 特定(映射) | 受限环境,无多余核心 |
显式控制:
bash
# 强制使用可流式的 GC 无关格式(即使使用 CompressedOops)
java -XX:+AOTStreamableObjects \
-XX:AOTCacheOutput=app.aot \
-cp app.jar com.example.Main
# 使用 GC 无关缓存(ZGC 生产环境)
java -XX:+UseZGC \
-XX:AOTCache=app.aot \
-cp app.jar com.example.Main
JDK 内置的基线缓存:
JDK 26 内置两个基线 AOT 缓存:
- GC 无关(可流式)的基线缓存
- GC 特定(可映射)的基线缓存
JVM 根据实际 GC 配置选择最合适的一个,因此即使用户未提供自定义缓存,也能获得启动改善。
完整工作流:
bash
# ① 训练运行(生成缓存,可用任何 GC)
java -XX:+UseZGC \
-XX:AOTCacheOutput=app.aot \
-cp app.jar com.example.Main --training
# ② 生产运行(使用缓存,用 ZGC 保持低延迟)
java -XX:+UseZGC \
-XX:AOTCache=app.aot \
-Xmx16g \
-cp app.jar com.example.Main
收益:
| 场景 | JDK 25 | JDK 26 |
|---|---|---|
| ZGC + AOT 缓存 | ❌ 不可用 | ✅ 可用 |
| 启动改善(ZGC 场景) | 无 | 20%~40% |
| 训练与生产 GC 不同 | ❌ 需一致 | ✅ 可不同 |
注意:
- GC 无关格式需要额外的 CPU 核心用于后台物化
- 极端的单核受限环境可能更适合 GC 特定格式
- 缓存与 JDK 版本绑定
TLS 1.3 的后量子混合密钥交换(JEP 527,JDK 27)也依赖此基础设施的演进。
3. JEP 522:G1 GC 通过减少同步提升吞吐量
背景 :G1 的某些内部数据结构(如 Region 的分配状态、标记位图、RemSet)使用粗粒度的锁保护。在多核机器上,这成为吞吐量的瓶颈。
变更:通过减少同步(更细粒度的锁、原子操作、无锁数据结构)降低竞争。
技术细节:
- 优化了 G1 的分配路径(TLAB 分配、Region 管理)
- 减少对全局锁的依赖
- 改进 Refinement 线程(处理跨 Region 引用)的并发性
收益:
- 多核机器上吞吐量提升(尤其在高分配速率场景)
- 减少因锁竞争导致的停顿
对用户的影响:
- 纯内部优化,无 API 变化
- 无需额外参数,自动受益
bash
# 无需配置,直接使用 G1(默认)
java -XX:+UseG1GC MyApp
4. JEP 500:Prepare to Make Final Mean Final
背景 :Java 的 final 字段曾被设计为「一旦赋值不可改变」,但由于反射 与 Unsafe 的存在,实际上可以被修改:
java
public class Config {
private final String value = "original";
}
// ❌ 此前可以绕过 final 语义
Field f = Config.class.getDeclaredField("value");
f.setAccessible(true);
f.set(config, "modified"); // 修改了 final 字段!
问题:
- 破坏了
final的语义保证 - 阻碍了 JIT 的优化(
final字段可以被视为常量) - 是安全隐患
JEP 500 的内容 :警告 通过反射修改 final 字段的操作,为将来禁止做准备。
java
// JDK 26 起:打印警告
Field f = Config.class.getDeclaredField("value");
f.setAccessible(true);
f.set(config, "modified");
text
WARNING: Final field value in class com.example.Config
has been mutated reflectively
WARNING: Such mutation will be denied by default in a future release
受影响的场景:
| 场景 | 说明 |
|---|---|
反射修改 final 字段 |
主要通过 Field.set / Field.setAccessible |
Unsafe.putObject 修改 final 字段 |
同样受影响 |
| Record 的组件字段 | 也是 final,修改同样被警告 |
| 枚举常量 | final |
静态 final 常量 |
final |
例外情况(仍然允许):
| 例外 | 说明 |
|---|---|
Field.set 在构造器中 |
允许(配合 setAccessible) |
| 反序列化框架 | 有专门的处理路径 |
Field.set 的「首次赋值」 |
若字段尚未被赋值(如匿名内部的 this$0) |
| 显式启用的兼容模式 | 见下 |
启用/禁用:
bash
# 允许并警告(JDK 26 默认)
java MyApp
# 严格禁止(提前适配)
java --final-field-mutation=deny MyApp
# 完全允许(不推荐,用于过渡)
java --final-field-mutation=allow MyApp
受影响的库与框架:
| 库 | 用途 | 影响 |
|---|---|---|
| Jackson | 反序列化到 final 字段 |
高 |
| Gson | 同上 | 高 |
| Hibernate | ORM 实体 | 中 |
| Mockito | Mock final 类/字段 |
中 |
| JUnit | 注入 final 字段 |
中 |
| Spring | 依赖注入 | 中 |
| Lombok | @Value 生成 final 字段 |
低(编译期生成) |
| Gradle / Maven | 构建工具 | 低 |
| GraalVM / Quarkus | 编译期处理 | 低 |
迁移建议:
java
// ❌ 旧方式:反射修改 final 字段
Field field = obj.getClass().getDeclaredField("name");
field.setAccessible(true);
field.set(obj, "newName");
// ✅ 新方式:使用构造器/工厂方法
Config config = new Config("newValue");
// ✅ 或使用可变容器
class Config {
private final AtomicReference<String> name = new AtomicReference<>("original");
}
注意 :JEP 500 在 JDK 26 中只是警告,正式禁止在后续版本。这给了生态迁移时间。
三、移除与弃用
5. JEP 504:移除 Applet API
这是 Java 与浏览器插件时代的最后告别。
演进历程:
| 版本 | JEP | 动作 |
|---|---|---|
| 9 | 289 | 弃用 Applet API(forRemoval = false) |
| 17 | 398 | 弃用(forRemoval = true) |
| 26 | 504 | 彻底移除 |
被移除的 API:
| API | 说明 |
|---|---|
java.applet.Applet |
Applet 基类 |
java.applet.AppletStub |
Applet 存根 |
java.applet.AppletContext |
Applet 上下文 |
java.applet.AudioClip |
音频剪辑 |
java.applet.Applet.AccessibleApplet |
无障碍支持 |
javax.swing.JApplet |
Swing Applet |
java.beans.AppletInitializer |
Applet 初始化接口 |
jdk.jsobject 模块的部分内容 |
JSObject(与 Applet 相关的脚本互操作) |
为什么移除:
- 所有主流浏览器已移除 NPAPI 插件支持(Chrome 2015、Firefox 2017、Safari 2018、Edge 2020)
- Applet 在技术上已无法运行
- 保留 API 只有维护成本,无实际价值
- 阻碍了 JDK 内部的重构
迁移建议:
| 原用途 | 现代替代 |
|---|---|
| 浏览器内运行 Java | 编译为 WebAssembly(TeaVM、CheerpJ) |
| 富客户端应用 | JavaFX / Swing(作为独立应用) |
| Web 应用 | Jakarta EE / Spring Boot (服务端);JSP/JSF/Thymeleaf |
| 浏览器内图形 | HTML5 Canvas / WebGL(通过 cheerpj 或转译) |
| 桌面应用分发 | jpackage(原生安装包) |
| 前端交互 | JavaScript / TypeScript |
注意 :java.beans 包本身没有 被移除,只有 AppletInitializer 接口被移除。
示例:从 Applet 迁移到独立应用
java
// ❌ 已移除的 Applet 写法
import java.applet.Applet;
import java.awt.Graphics;
public class MyApplet extends Applet {
@Override
public void paint(Graphics g) {
g.drawString("Hello", 20, 20);
}
}
java
// ✅ 现代写法:Swing 独立应用
import javax.swing.*;
import java.awt.*;
public class MyApp extends JPanel {
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawString("Hello", 20, 20);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("My App");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new MyApp());
frame.setSize(400, 300);
frame.setVisible(true);
});
}
}
java
// ✅ 或用 JavaFX
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class MyApp extends Application {
@Override
public void start(Stage stage) {
Pane root = new Pane(new Text(20, 20, "Hello"));
stage.setScene(new Scene(root, 400, 300));
stage.setTitle("My App");
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
四、预览与孵化特性
6. JEP 525:结构化并发(第六次预览)
(JDK 19 孵化,20、21、22、23、24、25 预览。)
java
import java.util.concurrent.StructuredTaskScope;
import java.util.concurrent.StructuredTaskScope.Subtask;
Response handle(Request request) throws Exception {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Subtask<User> user = scope.fork(() -> findUser(request.userId()));
Subtask<Order> order = scope.fork(() -> fetchOrder(request.orderId()));
scope.join().throwIfFailed();
return new Response(user.get(), order.get());
}
}
JDK 26 中的改进:
- API 进一步稳定
- 改进与 Scoped Values 的集成
- 完善自定义策略的表达能力
7. JEP 526:延迟常量(第二次预览)
注意:这是 JDK 25 中「Stable Values」(JEP 502)的改名与演进版本。
java
import java.lang.LazyConstant;
// JDK 25 中的名字:StableValue
// JDK 26 中的名字:LazyConstant
static final LazyConstant<Config> CONFIG = LazyConstant.of();
Config getConfig() {
return CONFIG.orElseSet(Application::loadConfig);
}
为什么改名 :名字更准确地反映语义------「最多初始化一次的值」,并且强调 final 语义(与 JEP 500「Make Final Mean Final」呼应)。
核心 API (java.lang.LazyConstant):
| 方法 | 说明 |
|---|---|
LazyConstant.of() |
创建 |
set(T) |
设置值(只能一次) |
orElseSet(Supplier<T>) |
若未设置则原子地设置 |
get() |
读取(未设置抛异常) |
isSet() |
是否已设置 |
orElse(T) |
提供默认值 |
典型用途:
java
// ① 延迟初始化昂贵资源
static final LazyConstant<ConnectionPool> POOL =
LazyConstant.of();
Connection getConnection() {
return POOL.orElseSet(() -> new ConnectionPool(config())).borrow();
}
// ② 每请求缓存(与 Scoped Values 配合)
static final ScopedValue<LazyConstant<String>> FORMATTED =
ScopedValue.newInstance();
String formatted(Request req) {
return FORMATTED.get().orElseSet(() -> expensiveFormat(req));
}
// ③ 与 Gatherers.mapConcurrent 配合(内部实现使用)
与 JEP 500 的关系 :LazyConstant 的 set 是一次性的,JIT 可以把它视为常量(类似 final),从而做常量折叠等优化。
8. JEP 530:原始类型模式匹配(第四次预览)
(JDK 23 JEP 455、24 JEP 488、25 JEP 507 的延续。)
java
static String describe(Object obj) {
return switch (obj) {
case int i when i > 0 -> "positive: " + i;
case int i when i < 0 -> "negative: " + i;
case int _ -> "zero";
case long l -> "long: " + l;
case double d -> "double: " + d;
case float f -> "float: " + f;
case boolean b -> "boolean: " + b;
case char c -> "char: " + c;
case byte b -> "byte: " + b;
case short s -> "short: " + s;
default -> "other";
};
}
JDK 26 中的改进:
- 更完善的支配性检查
- 改进与
final字段(JEP 500)的交互 - 更好的错误消息
9. JEP 524:PEM 编码(第二次预览)
(JDK 25 JEP 470 首次预览。)
java
import java.security.PEMEncoder;
import java.security.PEMDecoder;
// 编码
String pem = PEMEncoder.of().encodeToString(privateKey);
// 解码
PrivateKey key = PEMDecoder.of().decode(pem, PrivateKey.class);
JDK 26 中的改进:
- API 稳定化
- 增加对更多加密对象类型的支持
10. JEP 529:Vector API(第十一次孵化)
继续孵化。JDK 26 中的改进主要是性能优化与新硬件特性支持。
五、JDK 26 特性速查表
| 类别 | JEP | 特性 | 状态 |
|---|---|---|---|
| 移除 | 504 | 移除 Applet API | 移除 |
| 语言 | 500 | 准备让 final 真正 final | 警告 |
| 网络 | 517 | HTTP Client API 支持 HTTP/3 | 正式 |
| 运行时 | 516 | 任意 GC 的 AOT 对象缓存 | 正式 |
| GC | 522 | G1 通过减少同步提升吞吐量 | 正式 |
| 语言 | 526 | 延迟常量 | 第二次预览 |
| 语言 | 530 | 原始类型模式匹配 | 第四次预览 |
| 并发 | 525 | 结构化并发 | 第六次预览 |
| 库 | 524 | PEM 编码 | 第二次预览 |
| 库 | 529 | Vector API | 第十一次孵化 |
六、升级注意事项
-
Applet API 已移除 :检查是否有
java.applet.*/javax.swing.JApplet的引用- 这类代码在技术上早已无法运行,但可能存在于遗留代码库中
- 若构建仍然需要,考虑仅保留编译期 stub
-
final字段反射修改警告(JEP 500)- Jackson / Gson / Hibernate / Mockito 等可能产生警告
- 检查依赖库的新版本
- 提前测试
--final-field-mutation=deny
-
HTTP/3 需要显式启用
- 默认仍是 HTTP/2(有意保守)
- 推荐用 ALT-SVC 发现模式(兼容性最好)
- 仅支持默认 SSL provider(
SunJSSE)
-
AOT 缓存现在支持任意 GC
- 可放心用 ZGC + AOT 缓存组合
- 注意 GC 无关格式需要额外 CPU 核心
-
StableValue改名为LazyConstant- 若在 JDK 25 中预览使用过,需迁移
-
结构化并发仍是预览:API 仍会变化
七、参考资源
- 官方发布页:https://openjdk.org/projects/jdk/26/
- JEP 517(HTTP/3):https://openjdk.org/jeps/517
- JEP 516(AOT 对象缓存):https://openjdk.org/jeps/516
- JEP 504(移除 Applet):https://openjdk.org/jeps/504
- JEP 500(Final 真正 Final):https://openjdk.org/jeps/500
- JEP 522(G1 减少同步):https://openjdk.org/jeps/522
- JEP 526(延迟常量):https://openjdk.org/jeps/526