Brandon's Blog

Brandon's Blog

现代 Java 新特新

934
2023-12-03
现代 Java 新特新

前言

Java 8 自 2014 年 3 月18 日发布至今(2023), 这么多年过去了依然是国内使用最广泛的 JDK 版本, 正所谓 "他发任他发, 我用 Java 8", 突出一个字啊! 先来康一康 Java SE RoadMap:

Java 8 之后的10年里 Oracle 先后发布了13个版本, 其中3个 LTS 版本, 从里面的 Release 的功能可以看出来 Java 一直紧跟时代, 变化非常大, 总的来说就是迈向更轻(体积), 更快(性能), 更小(内存占用). 作为一个有灵魂码农, 也不能落后, 可以不用, 但不能不了解.

关于 Java 8 的介绍可以看我的老文章: Java8 Noob Tutorial

Java 9 - 11

Java 9

主要语言变化

新增

  • 模块化系统(Module System): JSR 376

    • Project Jigsaw 的一部分

    • 按需加载, 解决臃肿

    • module-info.java

      • 通过 exports, requires 关键字声明作用域(感觉像 nodejs?)

  • 新版本定义机制: $MAJOR.$MINOR.$SECURITY.$PATCH

更新

  • try-with-resources 语法允许变量使用 final 修饰, 语法升级

  • diamond 语法允许匿名类(如果类型推断的参数类型可表示的话)

  • 接口允许定义 private 方法

  • @SafeVarargs 允许声明在实例 private 方法上

主要 API 变化

引入

  • 进程(Process): JEP 102, 全新 API ProcessHandle 提供更好的管控操作系统

  • 内存(Memory): JEP 193, VarHandle 作为正式 API 替代 Unsafe, 对变量执行原子和内存屏障操作

  • 日志(Logging): JEP 264, 全新日志 API 和服务

    • 现在基本都用 Slf4j 了吧...

  • XML: JEP 268, 添加标准的 XML Catalog API

  • 栈(Stack): JEP 259, 全新栈跟踪工具, StackWalker 替代老的 StackTraceElement 体系

更新

  • 字符串(String): JEP 254, String 底层存储char[] 替换为 byte[]

    • 内存优化, 时间换空间

  • 集合(Collections): JEP 269, 集合接口提供便利的工厂方法, 如, Set.of(...)

  • 并发(Concurrency): JEP 266, CompletableFuture 以及其他并发组件提升

    • Reactive Streams: java.util.concurrent.Flow

  • 编译器(Compiler): JEP 274, 提升 MethodHandle 通用性以及更好地编译优化

  • 注解(Annotation): JEP 277, @Deprecated 注解增加 sinceforRemoval 属性, 丰富 API 淘汰策略

  • 线程(Threading): JEP 285, 新增自选方法 Thread.onSpinWait

  • 对象序列化(Serialization): JEP 290, 新增 API ObjectInputFilter 过滤 ObjectInputStream

  • XML: JEP 255, 更新 Xerces 2.11.0 解析 XML

  • Java Management Extensions (JMX): 支持远程诊断命令

  • 脚本(Scripting):

    • JEP 236, Nashorn 解析器 API 引入

    • JEP 292, 实现 ECMAScript 6 功能

  • 国际化(Internationalization):

    • JEP 267, 支持 Unicode 8.0

    • JEP 252, JDK 8 引入的 XML 形式的 Common Locale Data Repository (CLDR) 作为默认选项

    • JEP 226, 支持 UTF-8 Properties 文件

  • Java Database Connectivity (JDBC):

    • JDBC-ODBC 桥接移除

    • JDBC 4.2 升级

主要 JVM 变化

新增

更新

  • 垃圾回收(Garbage Collection)

  • 统一 JVM 日志: JEP 158

  • 输入/输出(I/O):

    • 减少 <JDK_HOME>/jre/lib/charsets.jar 文件大小

  • 性能提升(Performance)

    • java.lang.String 字节数组性能优化

  • 工具(Tools)

    • Java Plug-in 标记为不推荐使用, 未来版本移除

    • jshell: JEP 222, 增加 Read-Eval-Print Loop

    • jcmd: JEP 228, 增加更多诊断命令

    • jlink: JEP 282, 组装和优化模块以及依赖

    • 多版本发布 JAR 文件: JEP 238

    • 移除指定版本 JRE 启动

    • 移除 HProf Agent: JEP 240

Java 10

主要语言变化

新增

主要 API 变化

更新

  • 通用: Optional 新增方法

    • orElseThrow()方法来在没有值时抛出指定的异常

  • 集合增强

    • List, Set, Map 提供了静态方法copyOf()返回入参集合的一个不可变拷贝

  • java.util.stream.Collectors 中新增了静态方法, 用于将流中的元素收集为不可变的集合

  • Collectors.toUnmodifiableList(), Collectors.toUnmodifiableSet()

  • 安全(Security):

主要 JVM 变化

新增

更新

Java 11(LTS)

主要语言变化

新增

主要 API 变化

引入

更新

主要 JVM 变化

新增

更新

Java 12 - 17

Java 12

主要语言变化

新增

主要 API 变化

  • String 新增了 indent 方法处理缩进

  • Files 新增了 mismatch 来对比两个文件

  • NumberFormat 新增了对复杂的数字进行格式化的支持: getCompactNumberInstance

主要 JVM 变化

新增

更新

Java 13

主要语言变化

新增

主要 API 变化

更新

主要 JVM 变化

更新

Java 14

主要语言变化

新增

主要 API 变化

引入

主要 JVM 变化

更新

Java 15

主要语言变化

引入

更新

主要 API 变化

引入

更新

主要 JVM 变化

更新

Java 16

主要语言变化

引入

更新

引入

主要 JVM 变化

引入

更新

Java 17(LTS)

主要语言变化

引入

更新

主要 API 变化

引入

更新

主要 JVM 变化

引入

更新

Java 18 - 21

Java 18

主要语言变化

更新

主要 API 变化

更新

主要 JVM 变化

更新

Java 19

主要语言变化

更新

主要 API 变化

更新

主要 JVM 变化

引入

Java 20

主要语言变化

引入

主要 API 变化

更新

Java 21(LTS)

主要语言变化

引入

更新

主要 API 变化

引入

更新

主要 JVM 变化

更新

Java 22 - 25

Java 21 作为上一个 LTS 版本于 2023 年 9 月发布, 随后 Oracle 按照半年一版的节奏又接连推出了 JDK 22、23、24, 直到 2025 年 9 月, 新一代 LTS JDK 25 正式到来. 这四个版本延续了 Java 迈向 更轻、更快、更易用 的趋势, 核心主题包括:

  • Project Loom 的收尾: 虚拟线程彻底解除 synchronized 钉扎限制

  • Project Amber 的丰收: Unnamed Variables、Stream Gatherers、Flexible Constructor Bodies 等语言特性相继转正

  • Project Panama 落地: Foreign Function & Memory API 正式取代 JNI

  • Project Leyden 启航: AOT 类加载/链接让 Spring PetClinic 启动提速 42%

  • 后量子密码: ML-KEM、ML-DSA 两大算法入驻标准库

  • 低碳 JVM: 紧凑对象头 (Compact Object Headers) 降低堆内存 22%


Java 22

发布日期: 2024 年 3 月 19 日

主要语言变化

新增

  • 匿名变量与匿名模式(Unnamed Variables & Patterns): JEP 456正式版

    • 用下划线 _ 作为占位符声明 "不打算使用" 的变量或模式

    • 适用场景: catch 参数、Lambda 参数、for-each 循环变量、解构模式中的无关组件

    • 消除静态分析工具对 "未使用变量" 的误报, 使意图更加明确

    // 循环中不关心循环变量
    for (Order _ : orders) total++;
    ​
    // catch 不需要异常对象
    try {
        int i = Integer.parseInt(s);
    } catch (NumberFormatException _) {
        System.out.println("Bad number: " + s);
    }
    ​
    // Lambda 参数忽略
    stream.collect(Collectors.toMap(String::toUpperCase, _ -> "NODATA"));
    ​
    // 解构模式中忽略无关字段
    if (r instanceof ColoredPoint(Point(int x, int y), _)) {
        System.out.println(x + ", " + y);
    }
  • 构造函数体前置语句(Statements before super(...)): JEP 447预览版

    • 允许在显式构造函数调用 super(...)this(...) 之前插入语句

    • 前置语句不能引用 this, 但可以初始化字段、做参数校验

    • 从根本上解决了 "子类构造时无法提前校验入参" 的老大难问题 (详见 JDK 25 正式转正)

更新

  • 隐式声明类与实例 main 方法(Implicitly Declared Classes and Instance Main Methods): JEP 463第二次预览

    • 继续打磨降低入门门槛的语法糖, 允许省略 public static void main(String[]) 中的修饰符

    • 允许 main() 写成实例方法, 省略 publicstaticString[] 参数

  • 字符串模板(String Templates): JEP 459第二次预览

    • 允许使用模板处理器(如 STRFMT)生成字符串, 比字符串拼接更安全、更有表达力

    • ⚠️ 注意: 该特性在 JDK 23 时被撤回, 设计团队认为需要更多时间重新设计 API

主要 API 变化

引入

  • 外部函数与内存 API(Foreign Function & Memory API): JEP 454正式版

    • 历经 JDK 19(JEP 424)、20(JEP 434)、21(JEP 442) 三轮预览后终于转正

    • 提供纯 Java API 调用原生库 / 操作堆外内存, 彻底替代脆弱的 JNI 和危险的 sun.misc.Unsafe

    • 核心 API: MemorySegment(内存段)、Arena(生命周期管理)、Linker(函数链接)、SymbolLookup(符号查找)

    • 性能与 JNI 相当甚至更优, 且保证无 use-after-free 内存安全

    // 调用 C 标准库的 radixsort 函数
    Linker linker = Linker.nativeLinker();
    SymbolLookup stdlib = linker.defaultLookup();
    MethodHandle radixsort = linker.downcallHandle(stdlib.find("radixsort").get(), ...);
    ​
    try (Arena offHeap = Arena.ofConfined()) {
        MemorySegment pointers = offHeap.allocate(ValueLayout.ADDRESS, 4);
        // ... 填充数据并调用
        radixsort.invoke(pointers, 4, MemorySegment.NULL, '\0');
    }

预览/孵化

  • Stream 收集器(Stream Gatherers): JEP 461第一次预览

    • 为 Stream API 增加自定义中间操作 Stream::gather(Gatherer), 与 Stream::collect(Collector) 对终止操作的关系类似

    • 支持一对一、一对多、多对一、多对多的元素转换, 以及短路操作和并行化

  • 结构化并发(Structured Concurrency): JEP 462第二次预览

  • 作用域值(Scoped Values): JEP 464第二次预览

  • 类文件 API(Class-File API): JEP 457第一次预览

    • 提供标准 API 用于解析、生成和转换 .class 字节码文件, 取代第三方库如 ASM

主要 JVM 变化

新增

  • G1 区域钉扎(Region Pinning for G1): JEP 423

    • JNI 临界区(Critical Regions)期间 G1 不再需要暂停 GC, 降低 JNI 调用的延迟抖动

  • 多文件源码程序启动(Launch Multi-File Source-Code Programs): JEP 458

    • java 命令现在可以直接运行由多个 .java 文件组成的小程序, 无需先 javac 编译

    • 适合脚本场景和快速原型

    # 直接运行多文件程序
    java Main.java Helper.java Utils.java

孵化

  • 向量 API(Vector API): JEP 460第七次孵化

    • 持续在各平台(x64 AVX、AArch64 NEON)打磨 SIMD 向量计算 API


Java 23

发布日期: 2024 年 9 月 17 日

主要语言变化

新增(预览)

  • 模式中的原始类型(Primitive Types in Patterns, instanceof, and switch): JEP 455第一次预览

    • 将模式匹配扩展到所有原始类型(intlongdouble 等)

    • instanceofswitch 可以直接对原始类型做模式匹配

    // switch 直接匹配原始类型
    switch (x) {
        case int i when i > 0 -> System.out.println("positive: " + i);
        case int i             -> System.out.println("non-positive: " + i);
    }
    ​
    // instanceof 检查原始类型范围
    if (i instanceof byte b) { // 如果 i 能无损转换为 byte
        // 使用 b
    }
  • 模块导入声明(Module Import Declarations): JEP 476第一次预览

    • 新增 import module M; 语法, 一次导入整个模块导出的所有包

    • import module java.base; 等价于 54 个包的按需导入, 大幅减少 import 模板代码

    import module java.base;   // 一行替代 java.util.*, java.util.stream.*, java.io.* 等 54 个包
    ​
    // 现在可以直接使用 List, Map, Stream, Path 等
    var fruits = List.of("apple", "berry", "citrus");
    var result = Stream.of(fruits).collect(Collectors.joining(", "));

更新(预览)

  • 构造函数体前置语句: JEP 482第二次预览 (改名自 JEP 447)

  • 隐式声明类与实例 main 方法: JEP 477第三次预览

  • Stream Gatherers: JEP 473第二次预览

  • 结构化并发: JEP 480第三次预览

  • 作用域值: JEP 481第三次预览

  • 类文件 API: JEP 466第二次预览

主要 API 变化

更新

  • 工具(Tools):

    • Markdown 文档注释(Markdown Documentation Comments): JEP 467

      • JavaDoc 注释现在支持 CommonMark Markdown 语法, 用 /// 三斜杠开头代替传统的 /** */

      • code 替代 {@code}, 用 [链接] 替代 {@link}, 用 - item 替代 <li>

      • 极大提升文档注释的可读性和编写体验

    // 传统风格
    /**
     * Returns a hash code value for the object.
     * <ul>
     *   <li>If two objects are equal, their hash codes must be equal.</li>
     * </ul>
     * @return a hash code value
     * @see Object#equals(Object)
     */
    ​
    // Markdown 风格 (JDK 23+)
    /// Returns a hash code value for the object.
    ///
    ///   - If two objects are equal, their hash codes must be equal.
    ///
    /// @return a hash code value
    /// @see Object#equals(Object)

废弃

  • sun.misc.Unsafe 内存访问方法废弃: JEP 471

    • 标记 sun.misc.Unsafe 中的内存访问方法为 @Deprecated(forRemoval=true)

    • 替代方案: VarHandle(JDK 9)和 Foreign Function & Memory API(JDK 22 正式版)

主要 JVM 变化

更新

  • 垃圾回收(Garbage Collection)

    • ZGC 分代模式设为默认(ZGC: Generational Mode by Default): JEP 474

      • ZGC 的分代模式(JDK 21 引入)在经过充分验证后成为默认配置

      • 分代 ZGC 通过分别维护新生代和老年代对象, 降低了内存消耗并提升了吞吐量

      • 老的非分代模式仍可通过 -XX:ZGCGenerations=0 临时使用, 但已标记废弃

孵化

  • 向量 API: JEP 469第八次孵化


Java 24

发布日期: 2025 年 3 月 18 日

主要语言变化

新增(预览)

  • 原始类型模式: JEP 488第二次预览

  • 灵活构造函数体: JEP 492第三次预览

  • 模块导入声明: JEP 494第二次预览

  • 简单源文件与实例 main 方法: JEP 495第四次预览

主要 API 变化

正式转正

  • Stream 收集器(Stream Gatherers): JEP 485正式版

    • 历经 JDK 22(JEP 461)、23(JEP 473) 两轮预览后转正

    • Stream::gather(Gatherer) 成为标准 API, 支持任意自定义中间操作

    • java.util.stream.Gatherers 提供内置工厂方法: foldscanwindowFixedwindowSlidingmapConcurrent

    // 滑动窗口: 每3个元素一组
    Stream.of(1, 2, 3, 4, 5)
          .gather(Gatherers.windowSliding(3))
          .toList();
    // => [[1, 2, 3], [2, 3, 4], [3, 4, 5]]
    ​
    // 自定义: 按字符串长度去重
    stream.gather(Gatherer.ofSequential(
        HashSet::new,
        (seen, element, downstream) -> {
            if (seen.add(element.length())) downstream.push(element);
            return true;
        }
    ));
  • 类文件 API(Class-File API): JEP 484正式版

    • JDK 官方提供标准的字节码操作 API(java.lang.classfile 包), 替代 ASM 等第三方库

    • JDK 内部工具(javac、反射等)已迁移到该 API 上

引入

  • 抗量子 ML-KEM 密钥封装机制(Quantum-Resistant Module-Lattice-Based Key Encapsulation Mechanism): JEP 496

    • 基于 NIST FIPS 203 标准实现 ML-KEM(前身 Kyber), 抵御量子计算机的 Shor 算法攻击

    • 支持三种参数集: ML-KEM-512、ML-KEM-768(默认)、ML-KEM-1024

    • 提供 KeyPairGeneratorKEMKeyFactory 的 ML-KEM 实现

    // 生成密钥对
    KeyPairGenerator g = KeyPairGenerator.getInstance("ML-KEM");
    g.initialize(NamedParameterSpec.ML_KEM_768);
    KeyPair kp = g.generateKeyPair();
    ​
    // 发送方: 封装密钥
    KEM kem = KEM.getInstance("ML-KEM");
    KEM.Encapsulator enc = kem.newEncapsulator(kp.getPublic());
    KEM.Encapsulated encap = enc.encapsulate();
    byte[] msg = encap.encapsulation(); // 发送给接收方
    ​
    // 接收方: 解封装
    KEM.Decapsulator dec = kem.newDecapsulator(kp.getPrivate());
    SecretKey sharedKey = dec.decapsulate(msg);
  • 抗量子 ML-DSA 数字签名算法(Quantum-Resistant Module-Lattice-Based Digital Signature Algorithm): JEP 497

    • 基于 NIST FIPS 204 标准实现 ML-DSA(前身 Dilithium), 替代 RSA 等传统签名算法

    • 支持三种参数集: ML-DSA-44、ML-DSA-65、ML-DSA-87

预览

  • 作用域值: JEP 487第四次预览

  • 结构化并发: JEP 499第四次预览

  • 密钥派生函数 API(Key Derivation Function API): JEP 478第一次预览

主要 JVM 变化

新增

  • AOT 类加载与链接(Ahead-of-Time Class Loading & Linking): JEP 483

    • 来自 Project Leyden, 通过 "训练运行 → 创建缓存 → 使用缓存" 三步流程将类的加载/链接工作提前完成

    • HelloStream 程序(~600 个 JDK 类): 启动时间从 31ms 降至 18ms, 提升 42%

    • Spring PetClinic(~21,000 个类): 启动时间从 4.486s 降至 2.604s, 同样提升 42%

    # Step 1: 训练运行, 记录 AOT 配置
    java -XX:AOTMode=record -XX:AOTConfiguration=app.aotconf -cp app.jar com.example.App
    
    # Step 2: 创建 AOT 缓存
    java -XX:AOTMode=create -XX:AOTConfiguration=app.aotconf -XX:AOTCache=app.aot -cp app.jar
    
    # Step 3: 使用缓存运行
    java -XX:AOTCache=app.aot -cp app.jar com.example.App
  • 虚拟线程无钉扎同步(Synchronize Virtual Threads without Pinning): JEP 491

    • 彻底解决 JDK 21 虚拟线程中 synchronized 方法/块导致虚拟线程被 "钉扎" 在平台线程上的问题

    • 改造 JVM 的 monitor 实现, 使 synchronized 能追踪虚拟线程而非底层平台线程

    • 进入 synchronized 时如需阻塞, 虚拟线程可正常 unmount, 释放平台线程给其他虚拟线程

    • 现有代码无需改动即可享受虚拟线程的高扩展性, 无需再将 synchronized 替换为 ReentrantLock

  • 紧凑对象头(Compact Object Headers): JEP 450实验性

    • 来自 Project Lilliput, 将对象头从 96/128 位压缩至 64 位

    • SPECjbb2015: 堆内存减少 22%, CPU 时间减少 8%, GC 次数减少 15%

    • 通过 -XX:+UnlockExperimentalVMOptions -XX:+UseCompactObjectHeaders 开启 (JDK 25 转为正式特性)

  • 分代 Shenandoah GC(Generational Shenandoah): JEP 404实验性

    • Shenandoah 增加分代支持, 分别处理新生代与老年代, 进一步降低停顿时间

更新

  • 垃圾回收(Garbage Collection)

    • ZGC 移除非分代模式(ZGC: Remove the Non-Generational Mode): JEP 490

      • JDK 23 将分代模式设为默认后, JDK 24 正式移除非分代模式

      • ZGC 从此只支持分代模式

    • G1 延迟屏障展开(Late Barrier Expansion for G1): JEP 475

      • G1 的写屏障改为在 C2 编译后期展开, 减少 JIT 编译开销和生成代码体积

  • 安全(Security)

    • 永久禁用 Security Manager: JEP 486

      • Security Manager 自 JDK 17 起标记废弃, JDK 24 正式永久禁用

      • 调用 System.setSecurityManager() 将抛出 UnsupportedOperationException

    • 警告使用 sun.misc.Unsafe 内存访问方法: JEP 498

      • 使用 sun.misc.Unsafe 内存访问方法时发出运行时警告, 为后续彻底移除做准备

  • 限制 JNI 使用准备(Prepare to Restrict the Use of JNI): JEP 472

    • 未来版本将对未授权的 JNI 使用发出限制警告, 与 FFM API 的管控模型对齐

移除

  • 移除 Windows 32 位 x86 端口: JEP 479

    • 彻底删除 Windows 32 位 x86 平台的 JVM 移植代码


Java 25(LTS)

发布日期: 2025 年 9 月 16 日 | 长期支持版本 (LTS)

Java 25 是继 Java 21 之后的下一个 LTS 版本, 收录了 JDK 22-24 中孵化的众多特性, 同时延续了性能与可观测性方向的深耕.

主要语言变化

正式转正

  • 紧凑源文件与实例 main 方法(Compact Source Files and Instance Main Methods): JEP 512

    • 历经 JDK 21-24 四轮预览后终于在 JDK 25 LTS 中转正

    • 两大核心简化:

      1. 实例 main 方法: main() 可省略 publicstaticString[] 参数

      2. 紧凑源文件: 可以省略外层 class 声明, 直接写方法体

    • 新增 java.lang.IO 工具类, 提供 IO.println() 替代 System.out.println()

    • 紧凑源文件自动导入 java.base 及其他常用标准模块

    // Hello World 的终极简化版(JDK 25 正式版)
    void main() {
        IO.println("Hello, World!");
    }
    // 可以同时声明字段和方法, 无需 class 关键字
    String greeting = "Hello";
    
    void main() {
        IO.println(greeting + ", World!");
        printList(List.of("Java", "Go", "Rust"));
    }
    
    void printList(List<String> list) {
        list.forEach(IO::println);
    }
  • 模块导入声明(Module Import Declarations): JEP 511正式版

    • import module M; 语法正式转正, 一行导入整个模块的所有导出包

    • import module java.base; 替代 54 个 import 语句, import module java.sql; 自动传递依赖包

    • 同名类冲突时, 用更具体的单类型导入覆盖解决

  • 灵活构造函数体(Flexible Constructor Bodies): JEP 513正式版

    • super() / this() 之前可以写任意不访问 this 的语句

    • 允许在调用父类构造之前校验参数、初始化子类字段

    • 解决了 "父类构造调用子类重写方法时子类字段未初始化" 的安全漏洞

    class Employee extends Person {
        String officeID;
    
        Employee(int age, String officeID) {
            // JDK 25 之前: 必须先 super(), 无法提前校验
            // JDK 25+: 可以先校验再 super(), 甚至先初始化 officeID
            if (age < 18 || age > 67)
                throw new IllegalArgumentException("Age out of range: " + age);
            this.officeID = officeID;  // 在 super() 前初始化, 防止父类构造访问到 null
            super(age);
        }
    }
  • 作用域值(Scoped Values): JEP 506正式版

    • 历经 JDK 20-24 五轮迭代后转正, 是 ThreadLocal 的现代替代品

    • 不可变: ScopedValue 一旦绑定在作用域内不可修改, 消除 ThreadLocal 的随意 set() 问题

    • 有界生命周期: 绑定的值随 ScopedValue.where(...).run(...) 的作用域自动释放

    • 虚拟线程友好: 子线程自动继承父线程的 ScopedValue, 且共享存储无需拷贝

    // 声明
    static final ScopedValue<User> CURRENT_USER = ScopedValue.newInstance();
    
    // 框架层绑定
    ScopedValue.where(CURRENT_USER, authenticatedUser).run(() -> {
        // 在此 lambda 及其调用链中, CURRENT_USER 绑定为 authenticatedUser
        handleRequest(request, response);
    });
    
    // 业务层读取 (任意深度调用栈)
    void handleRequest(...) {
        User user = CURRENT_USER.get(); // 无需传参
        ...
    }

预览中

  • 原始类型模式: JEP 507第三次预览

  • 结构化并发: JEP 505第五次预览

    • 结构化并发 API 仍在持续打磨中, 尚未定型

主要 API 变化

正式转正

  • 密钥派生函数 API(Key Derivation Function API): JEP 510正式版

    • 标准化 KDF(Key Derivation Functions)的 Java API, 初始支持 HKDF 算法

    • 用于从主密钥派生出不同用途的子密钥

引入(预览)

  • 加密对象的 PEM 编码(PEM Encodings of Cryptographic Objects): JEP 470预览

    • 提供标准 API 用于解析和生成 PEM 格式的密钥、证书等加密对象

    • 告别手动 Base64 + 文件头/尾拼接

  • 稳定值(Stable Values): JEP 502预览

    • 提供 StableValue<T> 容器, 用于延迟初始化且只写一次的字段/集合元素

    • final 字段的 JVM 优化语义等价, 比 volatile + DCL 双重检查锁更安全易用

    // 延迟初始化单例, JVM 可像 final 字段一样优化
    private final StableValue<Connection> connection = StableValue.of();
    
    Connection getConnection() {
        return connection.orElseSet(() -> createConnection());
    }

主要 JVM 变化

性能提升

  • AOT 方法剖析(Ahead-of-Time Method Profiling): JEP 515

    • 在 AOT 训练阶段记录方法执行的剖析数据(类型反馈、分支预测等)

    • 后续运行时 JIT 编译器可立即利用历史剖析数据做激进优化, 大幅缩短 "warmup" 时间

  • AOT 命令行人体工程学(Ahead-of-Time Command-Line Ergonomics): JEP 514

    • 简化 AOT 缓存的使用, 支持通过环境变量或配置文件自动传入 AOT 参数, 无需手动拼长命令行

  • 紧凑对象头(Compact Object Headers): JEP 519产品特性

    • JDK 24 中的实验性特性升级为正式产品特性

    • 去掉 -XX:+UnlockExperimentalVMOptions 限制, 直接通过 -XX:+UseCompactObjectHeaders 启用

    • Amazon 已在数百个生产服务(包括 JDK 21/17 的反向移植版本)上大规模验证

  • 分代 Shenandoah GC(Generational Shenandoah): JEP 521正式版

    • 从实验性升级为正式特性, 分代 Shenandoah 具备更低的停顿时间和更好的吞吐量

可观测性增强

  • JFR CPU 时间剖析(JFR CPU-Time Profiling): JEP 509实验性

    • 新增基于 CPU 时间(而非挂钟时间)的 JFR 采样模式, 更精确识别 CPU 密集型热点

  • JFR 协作式采样(JFR Cooperative Sampling): JEP 518

    • JFR 在安全点之外也能采样, 提升采样精度, 消除安全点偏差问题

  • JFR 方法计时与追踪(JFR Method Timing & Tracing): JEP 520

    • 提供 JFR 原生的方法级计时和调用追踪能力, 无需 AOP 框架或字节码插桩

移除

  • 移除 32 位 x86 端口: JEP 503

    • 彻底删除 32 位 x86 平台支持(JDK 24 已废弃 -XX:+UseCompressedOops 下的相关代码)


特性演进总览

特性

JDK 22

JDK 23

JDK 24

JDK 25

Unnamed Variables (_)

✅ 正式

FFM API

✅ 正式

Stream Gatherers

预览1

预览2

✅ 正式

Class-File API

预览1

预览2

✅ 正式

Flexible Constructor Bodies

预览1

预览2

预览3

✅ 正式

Module Import Declarations

预览1

预览2

✅ 正式

Compact Source Files

预览2

预览3

预览4

✅ 正式

Scoped Values

预览2

预览3

预览4

✅ 正式

Markdown Doc Comments

✅ 正式

ZGC 分代模式

🔄 默认

旧模式移除

Virtual Thread 无钉扎

✅ 正式

AOT 类加载/链接

✅ 正式

AOT 方法剖析

✅ 正式

ML-KEM (量子安全)

✅ 正式

ML-DSA (量子安全)

✅ 正式

Compact Object Headers

实验

✅ 正式

Generational Shenandoah

实验

✅ 正式

Primitive Types in Patterns

预览1

预览2

预览3

Structured Concurrency

预览2

预览3

预览4

预览5

String Templates

预览2

❌ 撤回

✅ = 正式转正 🔄 = 行为变更 ❌ = 撤回/移除

升级Java版本的潜在问题与挑战

个人觉得现代 Java 的使用越来越偏底层, 越来越难, 很多黑科技的出现, 比如指令优化等, 大部分是高阶技能, 所以了解这些高级特性是提升个人竞争力的有效途径.

随着Java从8升级到更新的版本, 我们见证了性能、内存管理以及底层支持等多方面的显著提升. 例如, 字符串压缩、ZGC(Z Garbage Collector)和GraalVM等新特性的引入, 极大地增强了Java平台的能力. 然而, 这种进化并非没有代价, 在决定是否进行版本升级时, 开发者必须仔细考虑以下几项关键因素:

  • 废弃的API和包: 一些旧版API如 sun.misc.BASE64Encoder 已经被删除, 而像 javax 这样的包也经历了迁移或弃用. 这意味着依赖这些组件的应用程序可能需要重构以适应新的标准.

  • 内部API限制: 对某些内部API(如 Unsafe)的访问权限变得更加严格, 这可能影响那些直接使用这些API实现特定功能的应用程序.

  • 垃圾回收机制的变化: 垃圾回收策略的更新可能会改变应用程序的运行行为, 特别是对于那些高度依赖于特定GC行为的应用而言.

  • 第三方库的兼容性: 随着Java版本的迭代, 第三方库也会相应地更新. 虽然它们会添加新特性, 但同时也会停止支持旧的功能或配置. 例如, Spring Boot 3.0之后不再使用 spring.factories 文件来加载自动配置类, 如果使用的Spring Boot Starter还未适配新版, 则可能导致应用无法正常启动.

  • 注解和工具类的变动: 如 @PostConstruct 等注解的处理方式发生变化, 或者新的StackTrace API的引入, 都可能要求第三方框架做出相应的调整, 否则将导致不兼容的问题.

  • 项目复杂度的影响: 对于依赖较少的小型项目来说, 升级到更高版本的Java相对简单. 但对于大型项目, 尤其是那些包含多个公共模块的系统, 升级过程可能会非常复杂. 不仅因为内部API的变化, 还因为外部依赖关系的调整需求.

  • 生态系统的演进: 以Spring为例, 其庞大的生态系统使得全面兼容所有JDK版本变得几乎不可能. 因此, 最新的Spring版本已经明确表示不再支持Java 8, 转而拥抱更先进的Java版本, 以减轻历史包袱并推动技术进步.

为了顺利过渡到更新的Java版本, 开发团队应当提前规划, 并确保充分理解上述各项挑战. 此外, 建议对现有代码库进行全面评估, 识别出可能受到版本变更影响的部分, 并制定详细的迁移策略. 对于新的项目开发, 可以充分利用最新Java版本带来的性能优化和其他改进, 但同时也应该意识到随之而来的额外工作量和技术要求.

版本升级兼容常用技巧

添加被移除的包

比较经典就是 javax.xxx 这个包, 有些三方库底层还是用了 javax 的类, 在 JDK 8 可以运行, 升级到 9 以上时就不能用了, 因为这个 javax 已经被移除, 这时候手动引入 javax 依赖, 比如:

<dependency>
    <groupId>com.sun.mail</groupId>
    <artifactId>javax.mail</artifactId>
    <version>1.6.2</version>
</dependency>

代码层面逻辑判断兼容

如果使用了将来被移除或者重命名的类, 比如 BASE64Encoder 在 JDK 9 被移除, 为了兼容功能完整性, 需要判断当前运行的 JDK 版本从而在高版本运行环境中切换到其他实现.

String version = System.getProperty("java.version");
if (version.startsWith("1.8")) {
    // 使用 BASE64Encoder
} else {
    // 高版本实现
}

打包时通过 Profile 区别不同版本

如果一个公共类库比如 common-util 中引用了 util-a:1.0, common-util 需要在 JDK 8 及以上版本运行, 而 util-a:1.0 只能在 JDK 8 下运行, 只有 util-a:2.0 才支持更高的版本, 但是 util-a:2.0 需要 JDK 11+ 的编译环形, 所以 common-util 不能在 JDK 8 的环境中升级 util-a 到 2.0 版本.

这个 Case 有两个解决方案:

  • 方案1: 在高版本的项目中引入 common-util, 然后手动排除 util-a:1.0 并引入 2.0 版本.

    • 优点: 简单

    • 缺点: 一般一个 common-util 不可能只使用了一个三方库, 也不可能只被一两个项目引用, 所以采用此方案需要每个引用到 common-util 的项目都手动操作一边, 而且对于未来新增的类库管理不方便.

  • 方案2: 通过 Maven Profile 打包两个版本, 一个给 JDK 8用, 另外一个给更高版本使用. 下面是一个例子, 通过 mvn -Pjdk8 clean install 命令打包出一个 classifierjdk8 的版本.

<profiles>
    <profile>
        <id>default</id>
        <activation>
            <activeByDefault>true</activeByDefault>
        </activation>
        <properties>
            <JAVA_VERSION>21</JAVA_VERSION>
            <JAVA_HOME>~/.jdks/azul-21.0.1</JAVA_HOME>
        </properties>
        <dependencies>
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-classic</artifactId>
                <version>1.5.12</version>
            </dependency>
        </dependencies>
    </profile>

    <profile>
        <id>jdk8</id>
        <properties>
            <JAVA_VERSION>1.8</JAVA_VERSION>
            <JAVA_HOME>~/.jdks/azul-1.8.0_432</JAVA_HOME>
        </properties>
        <build>
            <plugins>
                <plugin>
                    <artifactId>maven-jar-plugin</artifactId>
                    <executions>
                        <execution>
                            <phase>package</phase>
                            <goals>
                                <goal>jar</goal>
                            </goals>
                            <configuration>
                                <classifier>jdk8</classifier>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
        <dependencies>
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-classic</artifactId>
                <version>1.3.14</version>
            </dependency>
        </dependencies>
    </profile>
</profiles>

其他

Java 模块化问题

遇到类似如下问题:

Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module

需要加上启动参数打开包权限(其他包的报错类似):

--add-opens java.base/java.lang=ALL-UNNAMED

maven-compiler-plugin 以及 maven-surefire-plugin 插件也可能会遇到此问题, 解决方法也是加参数:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-compiler-plugin</artifactId>
  <configuration>
      <showWarnings>true</showWarnings>
      <fork>true</fork>
      <compilerArgs>
        <!-- 根据报错添加对应的包 -->
        <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</arg>
      </compilerArgs>
  </configuration>
</plugin>

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-surefire-plugin</artifactId>
  <configuration>
      <includes>
        <include>**/*Test.java</include>
      </includes>
      <argLine>
        <!-- 根据情况添加对应的包 -->
        --add-opens java.base/java.lang=ALL-UNNAMED
      </argLine>
  </configuration>
</plugin>

Spring Boot

Spring Boot 3.0 Migration Guide

Ref