JDK 26 新特性详解

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: ...
                    ^^^  这是逻辑索引

工作方式:

  1. JVM 启动时,后台线程逐一流式地(streaming)从缓存**物化(materialize)**对象到堆中
  2. 物化时把逻辑索引转换为实际内存地址
  3. 应用首次使用某个类时,与该后台线程同步,确保相关 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 缓存:

  1. GC 无关(可流式)的基线缓存
  2. 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 相关的脚本互操作)

为什么移除:

  1. 所有主流浏览器已移除 NPAPI 插件支持(Chrome 2015、Firefox 2017、Safari 2018、Edge 2020)
  2. Applet 在技术上已无法运行
  3. 保留 API 只有维护成本,无实际价值
  4. 阻碍了 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 第十一次孵化

六、升级注意事项

  1. Applet API 已移除 :检查是否有 java.applet.* / javax.swing.JApplet 的引用

    • 这类代码在技术上早已无法运行,但可能存在于遗留代码库中
    • 若构建仍然需要,考虑仅保留编译期 stub
  2. final 字段反射修改警告(JEP 500)

    • Jackson / Gson / Hibernate / Mockito 等可能产生警告
    • 检查依赖库的新版本
    • 提前测试 --final-field-mutation=deny
  3. HTTP/3 需要显式启用

    • 默认仍是 HTTP/2(有意保守)
    • 推荐用 ALT-SVC 发现模式(兼容性最好)
    • 仅支持默认 SSL provider(SunJSSE)
  4. AOT 缓存现在支持任意 GC

    • 可放心用 ZGC + AOT 缓存组合
    • 注意 GC 无关格式需要额外 CPU 核心
  5. StableValue 改名为 LazyConstant

    • 若在 JDK 25 中预览使用过,需迁移
  6. 结构化并发仍是预览:API 仍会变化


七、参考资源


← JDK 25 | 返回索引 | JDK 27 →

相关推荐
SL_staff2 小时前
3分钟完成业务规则调整:告别代码修改,实现拖拽式逻辑配置
java·低代码·全栈
Flynn Liang2 小时前
让 AI 查订单、查库存、发消息:Spring AI 自定义工具(Tool)实战与工具描述工程
java·人工智能·spring·函数调用·springai·tool·自定义工具
Rain的Java大神之路2 小时前
我,13年Java老兵,建了个免费网站,公开叫板“Java已死”:没有广告,没有付费墙,151个页面全是踩坑干货
java·后端·程序员
萧瑟余晖2 小时前
Spring Integration企业集成概览详解
java·后端·spring
这个昵称不容易3 小时前
SpringBoot 墙绘交易平台毕设:128个文件154个接口含论文,带运行截图
java·springboot·毕设
SimonKing3 小时前
告别纸质合同,使用DocuSeal搭建私有电子签平台
java·后端·程序员
七夜zippoe3 小时前
Spring AI 函数调用:让 Java 方法变成 Agent 的工具(实战全流程)
java·人工智能·spring·ai·agent·函数调用
萧瑟余晖3 小时前
Spring Batch 高级特性与实践详解
java·spring·batch
她说..3 小时前
常见设计模式-观察者模式
java·spring boot·spring·观察者模式·设计模式